|
21 | 21 |
|
22 | 22 | __version__ = "0.8dev"
|
23 | 23 |
|
24 |
| -FLOAT_TOLERANCE = 10 ** -15 |
25 | 24 | PY3 = sys.version_info[0] >= 3
|
26 | 25 |
|
27 | 26 | if PY3:
|
28 | 27 | basestring = unicode = str
|
29 | 28 | iteritems = operator.methodcaller("items")
|
| 29 | + from urllib import parse as urlparse |
30 | 30 | from urllib.parse import unquote
|
31 | 31 | from urllib.request import urlopen
|
32 | 32 | else:
|
33 | 33 | from itertools import izip as zip
|
34 | 34 | iteritems = operator.methodcaller("iteritems")
|
35 | 35 | from urllib import unquote
|
36 | 36 | from urllib2 import urlopen
|
| 37 | + import urlparse |
| 38 | + |
| 39 | + |
| 40 | +FLOAT_TOLERANCE = 10 ** -15 |
| 41 | +validators = {} |
| 42 | + |
| 43 | + |
| 44 | +def validator(version): |
| 45 | + """ |
| 46 | + Register a validator for a ``version`` of the specification. |
| 47 | +
|
| 48 | + """ |
| 49 | + |
| 50 | + def _validator(cls): |
| 51 | + validators[version] = cls |
| 52 | + return cls |
| 53 | + return _validator |
37 | 54 |
|
38 | 55 |
|
39 | 56 | class UnknownType(Exception):
|
@@ -86,6 +103,7 @@ def __init__(self, message, validator=None, path=()):
|
86 | 103 | self.validator = validator
|
87 | 104 |
|
88 | 105 |
|
| 106 | +@validator("draft3") |
89 | 107 | class Draft3Validator(object):
|
90 | 108 | """
|
91 | 109 | A validator for JSON Schema draft 3.
|
@@ -123,7 +141,7 @@ def __init__(self, schema, types=(), resolver=None):
|
123 | 141 | self._types["any"] = tuple(self._types.values())
|
124 | 142 |
|
125 | 143 | if resolver is None:
|
126 |
| - resolver = RefResolver() |
| 144 | + resolver = RefResolver.from_schema(schema) |
127 | 145 |
|
128 | 146 | self.resolver = resolver
|
129 | 147 | self.schema = schema
|
@@ -412,7 +430,7 @@ def validate_extends(self, extends, instance, schema):
|
412 | 430 | yield error
|
413 | 431 |
|
414 | 432 | def validate_ref(self, ref, instance, schema):
|
415 |
| - resolved = self.resolver.resolve(self.schema, ref) |
| 433 | + resolved = self.resolver.resolve(ref) |
416 | 434 | for error in self.iter_errors(instance, resolved):
|
417 | 435 | yield error
|
418 | 436 |
|
@@ -503,50 +521,69 @@ def validate_ref(self, ref, instance, schema):
|
503 | 521 |
|
504 | 522 | class RefResolver(object):
|
505 | 523 | """
|
506 |
| - Resolve JSON Schema refs. |
| 524 | + Resolve JSON References. |
507 | 525 |
|
508 | 526 | """
|
509 | 527 |
|
510 |
| - def __init__(self, store=None, get_page=urlopen): |
511 |
| - if store is None: |
512 |
| - store = {} |
| 528 | + def __init__(self, base_uri, referrer, store=()): |
| 529 | + self.base_uri = base_uri |
| 530 | + self.referrer = referrer |
| 531 | + self.store = collections.defaultdict(dict, store, **_meta_schemas()) |
| 532 | + |
| 533 | + @classmethod |
| 534 | + def from_schema(cls, schema, *args, **kwargs): |
| 535 | + """ |
| 536 | + Construct a resolver from a JSON schema object. |
| 537 | +
|
| 538 | + """ |
513 | 539 |
|
514 |
| - self.get_page = get_page |
515 |
| - self.store = store |
| 540 | + return cls(schema.get("id", ""), schema, *args, **kwargs) |
516 | 541 |
|
517 |
| - def resolve(self, root_schema, ref): |
| 542 | + def resolve(self, ref): |
518 | 543 | """
|
519 |
| - Resolve a ``ref`` within the context of the ``root_schema``. |
| 544 | + Resolve a JSON ``ref``. |
520 | 545 |
|
521 | 546 | """
|
522 | 547 |
|
523 |
| - if ref in self.store: |
524 |
| - return self.store[ref] |
525 |
| - elif ref.startswith("#"): |
526 |
| - return self.resolve_relative(root_schema, ref) |
| 548 | + base_uri = self.base_uri |
| 549 | + uri, fragment = urlparse.urldefrag(urlparse.urljoin(base_uri, ref)) |
| 550 | + |
| 551 | + if uri in self.store: |
| 552 | + document = self.store[uri] |
| 553 | + elif not uri or uri == self.base_uri: |
| 554 | + document = self.referrer |
527 | 555 | else:
|
528 |
| - return json.load(self.get_page(ref)) |
| 556 | + document = self.resolve_remote(uri) |
| 557 | + |
| 558 | + return self.resolve_fragment(document, fragment.lstrip("/")) |
529 | 559 |
|
530 |
| - def resolve_relative(self, schema, ref): |
| 560 | + def resolve_fragment(self, document, fragment): |
531 | 561 | """
|
532 |
| - Resolve a relative ``ref`` within the given ``schema``. |
| 562 | + Resolve a ``fragment`` within the referenced ``document``. |
533 | 563 |
|
534 | 564 | """
|
535 | 565 |
|
536 |
| - if ref == "#": |
537 |
| - return schema |
| 566 | + parts = unquote(fragment).split("/") if fragment else [] |
538 | 567 |
|
539 |
| - parts = ref.lstrip("#/").split("/") |
540 |
| - parts = map(unquote, parts) |
541 |
| - parts = [part.replace('~1', '/').replace('~0', '~') for part in parts] |
| 568 | + for part in parts: |
| 569 | + part = part.replace("~1", "/").replace("~0", "~") |
542 | 570 |
|
543 |
| - try: |
544 |
| - for part in parts: |
545 |
| - schema = schema[part] |
546 |
| - except KeyError: |
547 |
| - raise InvalidRef("Unresolvable json-pointer %r" % ref) |
548 |
| - else: |
549 |
| - return schema |
| 571 | + if part not in document: |
| 572 | + raise InvalidRef("Unresolvable JSON pointer: %r" % fragment) |
| 573 | + |
| 574 | + document = document[part] |
| 575 | + |
| 576 | + return document |
| 577 | + |
| 578 | + def resolve_remote(self, uri): |
| 579 | + """ |
| 580 | + Resolve a remote ``uri``. |
| 581 | +
|
| 582 | + Does not check the store first. |
| 583 | +
|
| 584 | + """ |
| 585 | + |
| 586 | + return json.load(urlopen(uri)) |
550 | 587 |
|
551 | 588 |
|
552 | 589 | class ErrorTree(object):
|
@@ -599,6 +636,16 @@ def total_errors(self):
|
599 | 636 | return len(self.errors) + child_errors
|
600 | 637 |
|
601 | 638 |
|
| 639 | +def _meta_schemas(): |
| 640 | + """ |
| 641 | + Collect the urls and meta schemas from each known validator. |
| 642 | +
|
| 643 | + """ |
| 644 | + |
| 645 | + meta_schemas = (v.META_SCHEMA for v in validators.values()) |
| 646 | + return dict((urlparse.urldefrag(m["id"])[0], m) for m in meta_schemas) |
| 647 | + |
| 648 | + |
602 | 649 | def _find_additional_properties(instance, schema):
|
603 | 650 | """
|
604 | 651 | Return the set of additional properties for the given ``instance``.
|
|
0 commit comments