1""" 2Creation and extension of validators, with implementations for existing drafts. 3""" 4from collections import deque 5from collections.abc import Sequence 6from functools import lru_cache 7from urllib.parse import unquote, urldefrag, urljoin, urlsplit 8from urllib.request import urlopen 9from warnings import warn 10import contextlib 11import json 12import reprlib 13import warnings 14 15import attr 16 17from jsonschema import ( 18 _legacy_validators, 19 _types, 20 _utils, 21 _validators, 22 exceptions, 23) 24 25_VALIDATORS = {} 26_META_SCHEMAS = _utils.URIDict() 27_VOCABULARIES = [] 28 29 30def __getattr__(name): 31 if name == "ErrorTree": 32 warnings.warn( 33 "Importing ErrorTree from jsonschema.validators is deprecated. " 34 "Instead import it from jsonschema.exceptions.", 35 DeprecationWarning, 36 ) 37 from jsonschema.exceptions import ErrorTree 38 return ErrorTree 39 elif name == "validators": 40 warnings.warn( 41 "Accessing jsonschema.validators.validators is deprecated. " 42 "Use jsonschema.validators.validator_for with a given schema.", 43 DeprecationWarning, 44 ) 45 return _VALIDATORS 46 elif name == "meta_schemas": 47 warnings.warn( 48 "Accessing jsonschema.validators.meta_schemas is deprecated. " 49 "Use jsonschema.validators.validator_for with a given schema.", 50 DeprecationWarning, 51 ) 52 return _META_SCHEMAS 53 raise AttributeError(f"module {__name__} has no attribute {name}") 54 55 56def validates(version): 57 """ 58 Register the decorated validator for a ``version`` of the specification. 59 60 Registered validators and their meta schemas will be considered when 61 parsing ``$schema`` properties' URIs. 62 63 Arguments: 64 65 version (str): 66 67 An identifier to use as the version's name 68 69 Returns: 70 71 collections.abc.Callable: 72 73 a class decorator to decorate the validator with the version 74 """ 75 76 def _validates(cls): 77 _VALIDATORS[version] = cls 78 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 79 _META_SCHEMAS[meta_schema_id] = cls 80 return cls 81 return _validates 82 83 84def _id_of(schema): 85 """ 86 Return the ID of a schema for recent JSON Schema drafts. 87 """ 88 if schema is True or schema is False: 89 return "" 90 return schema.get("$id", "") 91 92 93def _store_schema_list(): 94 if not _VOCABULARIES: 95 _VOCABULARIES.extend(_utils.load_schema("vocabularies").items()) 96 return [ 97 (id, validator.META_SCHEMA) for id, validator in _META_SCHEMAS.items() 98 ] + _VOCABULARIES 99 100 101def create( 102 meta_schema, 103 validators=(), 104 version=None, 105 type_checker=_types.draft7_type_checker, 106 id_of=_id_of, 107 applicable_validators=lambda schema: schema.items(), 108): 109 """ 110 Create a new validator class. 111 112 Arguments: 113 114 meta_schema (collections.abc.Mapping): 115 116 the meta schema for the new validator class 117 118 validators (collections.abc.Mapping): 119 120 a mapping from names to callables, where each callable will 121 validate the schema property with the given name. 122 123 Each callable should take 4 arguments: 124 125 1. a validator instance, 126 2. the value of the property being validated within the 127 instance 128 3. the instance 129 4. the schema 130 131 version (str): 132 133 an identifier for the version that this validator class will 134 validate. If provided, the returned validator class will 135 have its ``__name__`` set to include the version, and also 136 will have `jsonschema.validators.validates` automatically 137 called for the given version. 138 139 type_checker (jsonschema.TypeChecker): 140 141 a type checker, used when applying the :validator:`type` validator. 142 143 If unprovided, a `jsonschema.TypeChecker` will be created 144 with a set of default types typical of JSON Schema drafts. 145 146 id_of (collections.abc.Callable): 147 148 A function that given a schema, returns its ID. 149 150 applicable_validators (collections.abc.Callable): 151 152 A function that given a schema, returns the list of applicable 153 validators (names and callables) which will be called to 154 validate the instance. 155 156 Returns: 157 158 a new `jsonschema.IValidator` class 159 """ 160 161 @attr.s 162 class Validator: 163 164 VALIDATORS = dict(validators) 165 META_SCHEMA = dict(meta_schema) 166 TYPE_CHECKER = type_checker 167 ID_OF = staticmethod(id_of) 168 169 schema = attr.ib(repr=reprlib.repr) 170 resolver = attr.ib(default=None, repr=False) 171 format_checker = attr.ib(default=None) 172 173 def __attrs_post_init__(self): 174 if self.resolver is None: 175 self.resolver = RefResolver.from_schema( 176 self.schema, 177 id_of=id_of, 178 ) 179 180 @classmethod 181 def check_schema(cls, schema): 182 for error in cls(cls.META_SCHEMA).iter_errors(schema): 183 raise exceptions.SchemaError.create_from(error) 184 185 def evolve(self, **kwargs): 186 return attr.evolve(self, **kwargs) 187 188 def iter_errors(self, instance, _schema=None): 189 if _schema is not None: 190 warnings.warn( 191 ( 192 "Passing a schema to Validator.iter_errors " 193 "is deprecated and will be removed in a future " 194 "release. Call validator.evolve(schema=new_schema)." 195 "iter_errors(...) instead." 196 ), 197 DeprecationWarning, 198 ) 199 else: 200 _schema = self.schema 201 202 if _schema is True: 203 return 204 elif _schema is False: 205 yield exceptions.ValidationError( 206 f"False schema does not allow {instance!r}", 207 validator=None, 208 validator_value=None, 209 instance=instance, 210 schema=_schema, 211 ) 212 return 213 214 scope = id_of(_schema) 215 if scope: 216 self.resolver.push_scope(scope) 217 try: 218 for k, v in applicable_validators(_schema): 219 validator = self.VALIDATORS.get(k) 220 if validator is None: 221 continue 222 223 errors = validator(self, v, instance, _schema) or () 224 for error in errors: 225 # set details if not already set by the called fn 226 error._set( 227 validator=k, 228 validator_value=v, 229 instance=instance, 230 schema=_schema, 231 ) 232 if k not in {"if", "$ref"}: 233 error.schema_path.appendleft(k) 234 yield error 235 finally: 236 if scope: 237 self.resolver.pop_scope() 238 239 def descend(self, instance, schema, path=None, schema_path=None): 240 for error in self.evolve(schema=schema).iter_errors(instance): 241 if path is not None: 242 error.path.appendleft(path) 243 if schema_path is not None: 244 error.schema_path.appendleft(schema_path) 245 yield error 246 247 def validate(self, *args, **kwargs): 248 for error in self.iter_errors(*args, **kwargs): 249 raise error 250 251 def is_type(self, instance, type): 252 try: 253 return self.TYPE_CHECKER.is_type(instance, type) 254 except exceptions.UndefinedTypeCheck: 255 raise exceptions.UnknownType(type, instance, self.schema) 256 257 def is_valid(self, instance, _schema=None): 258 if _schema is not None: 259 warnings.warn( 260 ( 261 "Passing a schema to Validator.is_valid is deprecated " 262 "and will be removed in a future release. Call " 263 "validator.evolve(schema=new_schema).is_valid(...) " 264 "instead." 265 ), 266 DeprecationWarning, 267 ) 268 self = self.evolve(schema=_schema) 269 270 error = next(self.iter_errors(instance), None) 271 return error is None 272 273 if version is not None: 274 safe = version.title().replace(" ", "").replace("-", "") 275 Validator.__name__ = Validator.__qualname__ = f"{safe}Validator" 276 Validator = validates(version)(Validator) 277 278 return Validator 279 280 281def extend(validator, validators=(), version=None, type_checker=None): 282 """ 283 Create a new validator class by extending an existing one. 284 285 Arguments: 286 287 validator (jsonschema.IValidator): 288 289 an existing validator class 290 291 validators (collections.abc.Mapping): 292 293 a mapping of new validator callables to extend with, whose 294 structure is as in `create`. 295 296 .. note:: 297 298 Any validator callables with the same name as an 299 existing one will (silently) replace the old validator 300 callable entirely, effectively overriding any validation 301 done in the "parent" validator class. 302 303 If you wish to instead extend the behavior of a parent's 304 validator callable, delegate and call it directly in 305 the new validator function by retrieving it using 306 ``OldValidator.VALIDATORS["validator_name"]``. 307 308 version (str): 309 310 a version for the new validator class 311 312 type_checker (jsonschema.TypeChecker): 313 314 a type checker, used when applying the :validator:`type` validator. 315 316 If unprovided, the type checker of the extended 317 `jsonschema.IValidator` will be carried along. 318 319 Returns: 320 321 a new `jsonschema.IValidator` class extending the one provided 322 323 .. note:: Meta Schemas 324 325 The new validator class will have its parent's meta schema. 326 327 If you wish to change or extend the meta schema in the new 328 validator class, modify ``META_SCHEMA`` directly on the returned 329 class. Note that no implicit copying is done, so a copy should 330 likely be made before modifying it, in order to not affect the 331 old validator. 332 """ 333 334 all_validators = dict(validator.VALIDATORS) 335 all_validators.update(validators) 336 337 if type_checker is None: 338 type_checker = validator.TYPE_CHECKER 339 return create( 340 meta_schema=validator.META_SCHEMA, 341 validators=all_validators, 342 version=version, 343 type_checker=type_checker, 344 id_of=validator.ID_OF, 345 ) 346 347 348Draft3Validator = create( 349 meta_schema=_utils.load_schema("draft3"), 350 validators={ 351 "$ref": _validators.ref, 352 "additionalItems": _validators.additionalItems, 353 "additionalProperties": _validators.additionalProperties, 354 "dependencies": _legacy_validators.dependencies_draft3, 355 "disallow": _legacy_validators.disallow_draft3, 356 "divisibleBy": _validators.multipleOf, 357 "enum": _validators.enum, 358 "extends": _legacy_validators.extends_draft3, 359 "format": _validators.format, 360 "items": _legacy_validators.items_draft3_draft4, 361 "maxItems": _validators.maxItems, 362 "maxLength": _validators.maxLength, 363 "maximum": _legacy_validators.maximum_draft3_draft4, 364 "minItems": _validators.minItems, 365 "minLength": _validators.minLength, 366 "minimum": _legacy_validators.minimum_draft3_draft4, 367 "pattern": _validators.pattern, 368 "patternProperties": _validators.patternProperties, 369 "properties": _legacy_validators.properties_draft3, 370 "type": _legacy_validators.type_draft3, 371 "uniqueItems": _validators.uniqueItems, 372 }, 373 type_checker=_types.draft3_type_checker, 374 version="draft3", 375 id_of=lambda schema: schema.get("id", ""), 376 applicable_validators=_legacy_validators.ignore_ref_siblings, 377) 378 379Draft4Validator = create( 380 meta_schema=_utils.load_schema("draft4"), 381 validators={ 382 "$ref": _validators.ref, 383 "additionalItems": _validators.additionalItems, 384 "additionalProperties": _validators.additionalProperties, 385 "allOf": _validators.allOf, 386 "anyOf": _validators.anyOf, 387 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 388 "enum": _validators.enum, 389 "format": _validators.format, 390 "items": _legacy_validators.items_draft3_draft4, 391 "maxItems": _validators.maxItems, 392 "maxLength": _validators.maxLength, 393 "maxProperties": _validators.maxProperties, 394 "maximum": _legacy_validators.maximum_draft3_draft4, 395 "minItems": _validators.minItems, 396 "minLength": _validators.minLength, 397 "minProperties": _validators.minProperties, 398 "minimum": _legacy_validators.minimum_draft3_draft4, 399 "multipleOf": _validators.multipleOf, 400 "not": _validators.not_, 401 "oneOf": _validators.oneOf, 402 "pattern": _validators.pattern, 403 "patternProperties": _validators.patternProperties, 404 "properties": _validators.properties, 405 "required": _validators.required, 406 "type": _validators.type, 407 "uniqueItems": _validators.uniqueItems, 408 }, 409 type_checker=_types.draft4_type_checker, 410 version="draft4", 411 id_of=lambda schema: schema.get("id", ""), 412 applicable_validators=_legacy_validators.ignore_ref_siblings, 413) 414 415Draft6Validator = create( 416 meta_schema=_utils.load_schema("draft6"), 417 validators={ 418 "$ref": _validators.ref, 419 "additionalItems": _validators.additionalItems, 420 "additionalProperties": _validators.additionalProperties, 421 "allOf": _validators.allOf, 422 "anyOf": _validators.anyOf, 423 "const": _validators.const, 424 "contains": _legacy_validators.contains_draft6_draft7, 425 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 426 "enum": _validators.enum, 427 "exclusiveMaximum": _validators.exclusiveMaximum, 428 "exclusiveMinimum": _validators.exclusiveMinimum, 429 "format": _validators.format, 430 "items": _legacy_validators.items_draft6_draft7_draft201909, 431 "maxItems": _validators.maxItems, 432 "maxLength": _validators.maxLength, 433 "maxProperties": _validators.maxProperties, 434 "maximum": _validators.maximum, 435 "minItems": _validators.minItems, 436 "minLength": _validators.minLength, 437 "minProperties": _validators.minProperties, 438 "minimum": _validators.minimum, 439 "multipleOf": _validators.multipleOf, 440 "not": _validators.not_, 441 "oneOf": _validators.oneOf, 442 "pattern": _validators.pattern, 443 "patternProperties": _validators.patternProperties, 444 "properties": _validators.properties, 445 "propertyNames": _validators.propertyNames, 446 "required": _validators.required, 447 "type": _validators.type, 448 "uniqueItems": _validators.uniqueItems, 449 }, 450 type_checker=_types.draft6_type_checker, 451 version="draft6", 452 applicable_validators=_legacy_validators.ignore_ref_siblings, 453) 454 455Draft7Validator = create( 456 meta_schema=_utils.load_schema("draft7"), 457 validators={ 458 "$ref": _validators.ref, 459 "additionalItems": _validators.additionalItems, 460 "additionalProperties": _validators.additionalProperties, 461 "allOf": _validators.allOf, 462 "anyOf": _validators.anyOf, 463 "const": _validators.const, 464 "contains": _legacy_validators.contains_draft6_draft7, 465 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 466 "enum": _validators.enum, 467 "exclusiveMaximum": _validators.exclusiveMaximum, 468 "exclusiveMinimum": _validators.exclusiveMinimum, 469 "format": _validators.format, 470 "if": _validators.if_, 471 "items": _legacy_validators.items_draft6_draft7_draft201909, 472 "maxItems": _validators.maxItems, 473 "maxLength": _validators.maxLength, 474 "maxProperties": _validators.maxProperties, 475 "maximum": _validators.maximum, 476 "minItems": _validators.minItems, 477 "minLength": _validators.minLength, 478 "minProperties": _validators.minProperties, 479 "minimum": _validators.minimum, 480 "multipleOf": _validators.multipleOf, 481 "not": _validators.not_, 482 "oneOf": _validators.oneOf, 483 "pattern": _validators.pattern, 484 "patternProperties": _validators.patternProperties, 485 "properties": _validators.properties, 486 "propertyNames": _validators.propertyNames, 487 "required": _validators.required, 488 "type": _validators.type, 489 "uniqueItems": _validators.uniqueItems, 490 }, 491 type_checker=_types.draft7_type_checker, 492 version="draft7", 493 applicable_validators=_legacy_validators.ignore_ref_siblings, 494) 495 496Draft201909Validator = create( 497 meta_schema=_utils.load_schema("draft2019-09"), 498 validators={ 499 "$recursiveRef": _legacy_validators.recursiveRef, 500 "$ref": _validators.ref, 501 "additionalItems": _validators.additionalItems, 502 "additionalProperties": _validators.additionalProperties, 503 "allOf": _validators.allOf, 504 "anyOf": _validators.anyOf, 505 "const": _validators.const, 506 "contains": _validators.contains, 507 "dependentRequired": _validators.dependentRequired, 508 "dependentSchemas": _validators.dependentSchemas, 509 "enum": _validators.enum, 510 "exclusiveMaximum": _validators.exclusiveMaximum, 511 "exclusiveMinimum": _validators.exclusiveMinimum, 512 "format": _validators.format, 513 "if": _validators.if_, 514 "items": _legacy_validators.items_draft6_draft7_draft201909, 515 "maxItems": _validators.maxItems, 516 "maxLength": _validators.maxLength, 517 "maxProperties": _validators.maxProperties, 518 "maximum": _validators.maximum, 519 "minItems": _validators.minItems, 520 "minLength": _validators.minLength, 521 "minProperties": _validators.minProperties, 522 "minimum": _validators.minimum, 523 "multipleOf": _validators.multipleOf, 524 "not": _validators.not_, 525 "oneOf": _validators.oneOf, 526 "pattern": _validators.pattern, 527 "patternProperties": _validators.patternProperties, 528 "properties": _validators.properties, 529 "propertyNames": _validators.propertyNames, 530 "required": _validators.required, 531 "type": _validators.type, 532 "unevaluatedItems": _validators.unevaluatedItems, 533 "unevaluatedProperties": _validators.unevaluatedProperties, 534 "uniqueItems": _validators.uniqueItems, 535 }, 536 type_checker=_types.draft201909_type_checker, 537 version="draft2019-09", 538) 539 540Draft202012Validator = create( 541 meta_schema=_utils.load_schema("draft2020-12"), 542 validators={ 543 "$dynamicRef": _validators.dynamicRef, 544 "$ref": _validators.ref, 545 "additionalItems": _validators.additionalItems, 546 "additionalProperties": _validators.additionalProperties, 547 "allOf": _validators.allOf, 548 "anyOf": _validators.anyOf, 549 "const": _validators.const, 550 "contains": _validators.contains, 551 "dependentRequired": _validators.dependentRequired, 552 "dependentSchemas": _validators.dependentSchemas, 553 "enum": _validators.enum, 554 "exclusiveMaximum": _validators.exclusiveMaximum, 555 "exclusiveMinimum": _validators.exclusiveMinimum, 556 "format": _validators.format, 557 "if": _validators.if_, 558 "items": _validators.items, 559 "maxItems": _validators.maxItems, 560 "maxLength": _validators.maxLength, 561 "maxProperties": _validators.maxProperties, 562 "maximum": _validators.maximum, 563 "minItems": _validators.minItems, 564 "minLength": _validators.minLength, 565 "minProperties": _validators.minProperties, 566 "minimum": _validators.minimum, 567 "multipleOf": _validators.multipleOf, 568 "not": _validators.not_, 569 "oneOf": _validators.oneOf, 570 "pattern": _validators.pattern, 571 "patternProperties": _validators.patternProperties, 572 "prefixItems": _validators.prefixItems, 573 "properties": _validators.properties, 574 "propertyNames": _validators.propertyNames, 575 "required": _validators.required, 576 "type": _validators.type, 577 "unevaluatedItems": _validators.unevaluatedItems, 578 "unevaluatedProperties": _validators.unevaluatedProperties, 579 "uniqueItems": _validators.uniqueItems, 580 }, 581 type_checker=_types.draft202012_type_checker, 582 version="draft2020-12", 583) 584 585_LATEST_VERSION = Draft202012Validator 586 587 588class RefResolver(object): 589 """ 590 Resolve JSON References. 591 592 Arguments: 593 594 base_uri (str): 595 596 The URI of the referring document 597 598 referrer: 599 600 The actual referring document 601 602 store (dict): 603 604 A mapping from URIs to documents to cache 605 606 cache_remote (bool): 607 608 Whether remote refs should be cached after first resolution 609 610 handlers (dict): 611 612 A mapping from URI schemes to functions that should be used 613 to retrieve them 614 615 urljoin_cache (:func:`functools.lru_cache`): 616 617 A cache that will be used for caching the results of joining 618 the resolution scope to subscopes. 619 620 remote_cache (:func:`functools.lru_cache`): 621 622 A cache that will be used for caching the results of 623 resolved remote URLs. 624 625 Attributes: 626 627 cache_remote (bool): 628 629 Whether remote refs should be cached after first resolution 630 """ 631 632 def __init__( 633 self, 634 base_uri, 635 referrer, 636 store=(), 637 cache_remote=True, 638 handlers=(), 639 urljoin_cache=None, 640 remote_cache=None, 641 ): 642 if urljoin_cache is None: 643 urljoin_cache = lru_cache(1024)(urljoin) 644 if remote_cache is None: 645 remote_cache = lru_cache(1024)(self.resolve_from_url) 646 647 self.referrer = referrer 648 self.cache_remote = cache_remote 649 self.handlers = dict(handlers) 650 651 self._scopes_stack = [base_uri] 652 self.store = _utils.URIDict(_store_schema_list()) 653 self.store.update(store) 654 self.store[base_uri] = referrer 655 656 self._urljoin_cache = urljoin_cache 657 self._remote_cache = remote_cache 658 659 @classmethod 660 def from_schema(cls, schema, id_of=_id_of, *args, **kwargs): 661 """ 662 Construct a resolver from a JSON schema object. 663 664 Arguments: 665 666 schema: 667 668 the referring schema 669 670 Returns: 671 672 `RefResolver` 673 """ 674 675 return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) 676 677 def push_scope(self, scope): 678 """ 679 Enter a given sub-scope. 680 681 Treats further dereferences as being performed underneath the 682 given scope. 683 """ 684 self._scopes_stack.append( 685 self._urljoin_cache(self.resolution_scope, scope), 686 ) 687 688 def pop_scope(self): 689 """ 690 Exit the most recent entered scope. 691 692 Treats further dereferences as being performed underneath the 693 original scope. 694 695 Don't call this method more times than `push_scope` has been 696 called. 697 """ 698 try: 699 self._scopes_stack.pop() 700 except IndexError: 701 raise exceptions.RefResolutionError( 702 "Failed to pop the scope from an empty stack. " 703 "`pop_scope()` should only be called once for every " 704 "`push_scope()`", 705 ) 706 707 @property 708 def resolution_scope(self): 709 """ 710 Retrieve the current resolution scope. 711 """ 712 return self._scopes_stack[-1] 713 714 @property 715 def base_uri(self): 716 """ 717 Retrieve the current base URI, not including any fragment. 718 """ 719 uri, _ = urldefrag(self.resolution_scope) 720 return uri 721 722 @contextlib.contextmanager 723 def in_scope(self, scope): 724 """ 725 Temporarily enter the given scope for the duration of the context. 726 """ 727 warnings.warn( 728 "jsonschema.RefResolver.in_scope is deprecated and will be " 729 "removed in a future release.", 730 DeprecationWarning, 731 ) 732 self.push_scope(scope) 733 try: 734 yield 735 finally: 736 self.pop_scope() 737 738 @contextlib.contextmanager 739 def resolving(self, ref): 740 """ 741 Resolve the given ``ref`` and enter its resolution scope. 742 743 Exits the scope on exit of this context manager. 744 745 Arguments: 746 747 ref (str): 748 749 The reference to resolve 750 """ 751 752 url, resolved = self.resolve(ref) 753 self.push_scope(url) 754 try: 755 yield resolved 756 finally: 757 self.pop_scope() 758 759 def _finditem(self, schema, key): 760 values = deque([schema]) 761 while values: 762 each = values.pop() 763 if not isinstance(each, dict): 764 continue 765 if key in each: 766 yield each 767 values.extendleft(each.values()) 768 769 def resolve(self, ref): 770 """ 771 Resolve the given reference. 772 """ 773 url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/") 774 775 uri, fragment = urldefrag(url) 776 777 for subschema in self._finditem(self.referrer, "$id"): 778 target_uri = self._urljoin_cache( 779 self.resolution_scope, subschema["$id"], 780 ) 781 if target_uri.rstrip("/") == uri.rstrip("/"): 782 if fragment: 783 subschema = self.resolve_fragment(subschema, fragment) 784 return url, subschema 785 786 return url, self._remote_cache(url) 787 788 def resolve_from_url(self, url): 789 """ 790 Resolve the given remote URL. 791 """ 792 url, fragment = urldefrag(url) 793 try: 794 document = self.store[url] 795 except KeyError: 796 try: 797 document = self.resolve_remote(url) 798 except Exception as exc: 799 raise exceptions.RefResolutionError(exc) 800 801 return self.resolve_fragment(document, fragment) 802 803 def resolve_fragment(self, document, fragment): 804 """ 805 Resolve a ``fragment`` within the referenced ``document``. 806 807 Arguments: 808 809 document: 810 811 The referent document 812 813 fragment (str): 814 815 a URI fragment to resolve within it 816 """ 817 818 fragment = fragment.lstrip("/") 819 820 if not fragment: 821 return document 822 823 for keyword in ["$anchor", "$dynamicAnchor"]: 824 for subschema in self._finditem(document, keyword): 825 if fragment == subschema[keyword]: 826 return subschema 827 for keyword in ["id", "$id"]: 828 for subschema in self._finditem(document, keyword): 829 if "#" + fragment == subschema[keyword]: 830 return subschema 831 832 # Resolve via path 833 parts = unquote(fragment).split("/") if fragment else [] 834 for part in parts: 835 part = part.replace("~1", "/").replace("~0", "~") 836 837 if isinstance(document, Sequence): 838 # Array indexes should be turned into integers 839 try: 840 part = int(part) 841 except ValueError: 842 pass 843 try: 844 document = document[part] 845 except (TypeError, LookupError): 846 raise exceptions.RefResolutionError( 847 f"Unresolvable JSON pointer: {fragment!r}", 848 ) 849 850 return document 851 852 def resolve_remote(self, uri): 853 """ 854 Resolve a remote ``uri``. 855 856 If called directly, does not check the store first, but after 857 retrieving the document at the specified URI it will be saved in 858 the store if :attr:`cache_remote` is True. 859 860 .. note:: 861 862 If the requests_ library is present, ``jsonschema`` will use it to 863 request the remote ``uri``, so that the correct encoding is 864 detected and used. 865 866 If it isn't, or if the scheme of the ``uri`` is not ``http`` or 867 ``https``, UTF-8 is assumed. 868 869 Arguments: 870 871 uri (str): 872 873 The URI to resolve 874 875 Returns: 876 877 The retrieved document 878 879 .. _requests: https://pypi.org/project/requests/ 880 """ 881 try: 882 import requests 883 except ImportError: 884 requests = None 885 886 scheme = urlsplit(uri).scheme 887 888 if scheme in self.handlers: 889 result = self.handlers[scheme](uri) 890 elif scheme in ["http", "https"] and requests: 891 # Requests has support for detecting the correct encoding of 892 # json over http 893 result = requests.get(uri).json() 894 else: 895 # Otherwise, pass off to urllib and assume utf-8 896 with urlopen(uri) as url: 897 result = json.loads(url.read().decode("utf-8")) 898 899 if self.cache_remote: 900 self.store[uri] = result 901 return result 902 903 904def validate(instance, schema, cls=None, *args, **kwargs): 905 """ 906 Validate an instance under the given schema. 907 908 >>> validate([2, 3, 4], {"maxItems": 2}) 909 Traceback (most recent call last): 910 ... 911 ValidationError: [2, 3, 4] is too long 912 913 :func:`validate` will first verify that the provided schema is 914 itself valid, since not doing so can lead to less obvious error 915 messages and fail in less obvious or consistent ways. 916 917 If you know you have a valid schema already, especially if you 918 intend to validate multiple instances with the same schema, you 919 likely would prefer using the `IValidator.validate` method directly 920 on a specific validator (e.g. ``Draft7Validator.validate``). 921 922 923 Arguments: 924 925 instance: 926 927 The instance to validate 928 929 schema: 930 931 The schema to validate with 932 933 cls (IValidator): 934 935 The class that will be used to validate the instance. 936 937 If the ``cls`` argument is not provided, two things will happen 938 in accordance with the specification. First, if the schema has a 939 :validator:`$schema` property containing a known meta-schema [#]_ 940 then the proper validator will be used. The specification recommends 941 that all schemas contain :validator:`$schema` properties for this 942 reason. If no :validator:`$schema` property is found, the default 943 validator class is the latest released draft. 944 945 Any other provided positional and keyword arguments will be passed 946 on when instantiating the ``cls``. 947 948 Raises: 949 950 `jsonschema.exceptions.ValidationError` if the instance 951 is invalid 952 953 `jsonschema.exceptions.SchemaError` if the schema itself 954 is invalid 955 956 .. rubric:: Footnotes 957 .. [#] known by a validator registered with 958 `jsonschema.validators.validates` 959 """ 960 if cls is None: 961 cls = validator_for(schema) 962 963 cls.check_schema(schema) 964 validator = cls(schema, *args, **kwargs) 965 error = exceptions.best_match(validator.iter_errors(instance)) 966 if error is not None: 967 raise error 968 969 970def validator_for(schema, default=_LATEST_VERSION): 971 """ 972 Retrieve the validator class appropriate for validating the given schema. 973 974 Uses the :validator:`$schema` property that should be present in the 975 given schema to look up the appropriate validator class. 976 977 Arguments: 978 979 schema (collections.abc.Mapping or bool): 980 981 the schema to look at 982 983 default: 984 985 the default to return if the appropriate validator class 986 cannot be determined. 987 988 If unprovided, the default is to return the latest supported 989 draft. 990 """ 991 if schema is True or schema is False or "$schema" not in schema: 992 return default 993 if schema["$schema"] not in _META_SCHEMAS: 994 warn( 995 ( 996 "The metaschema specified by $schema was not found. " 997 "Using the latest draft to validate, but this will raise " 998 "an error in the future." 999 ), 1000 DeprecationWarning, 1001 stacklevel=2, 1002 ) 1003 return _META_SCHEMAS.get(schema["$schema"], _LATEST_VERSION) 1004