Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jsonschema/validators.py: 31%
319 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
1"""
2Creation and extension of validators, with implementations for existing drafts.
3"""
4from __future__ import annotations
6from collections import deque
7from collections.abc import Mapping, Sequence
8from functools import lru_cache
9from operator import methodcaller
10from urllib.parse import unquote, urldefrag, urljoin, urlsplit
11from urllib.request import urlopen
12from warnings import warn
13import contextlib
14import json
15import reprlib
16import typing
17import warnings
19from pyrsistent import m
20import attr
22from jsonschema import (
23 _format,
24 _legacy_validators,
25 _types,
26 _utils,
27 _validators,
28 exceptions,
29)
31_UNSET = _utils.Unset()
33_VALIDATORS: dict[str, typing.Any] = {}
34_META_SCHEMAS = _utils.URIDict()
35_VOCABULARIES: list[tuple[str, typing.Any]] = []
38def __getattr__(name):
39 if name == "ErrorTree":
40 warnings.warn(
41 "Importing ErrorTree from jsonschema.validators is deprecated. "
42 "Instead import it from jsonschema.exceptions.",
43 DeprecationWarning,
44 stacklevel=2,
45 )
46 from jsonschema.exceptions import ErrorTree
47 return ErrorTree
48 elif name == "validators":
49 warnings.warn(
50 "Accessing jsonschema.validators.validators is deprecated. "
51 "Use jsonschema.validators.validator_for with a given schema.",
52 DeprecationWarning,
53 stacklevel=2,
54 )
55 return _VALIDATORS
56 elif name == "meta_schemas":
57 warnings.warn(
58 "Accessing jsonschema.validators.meta_schemas is deprecated. "
59 "Use jsonschema.validators.validator_for with a given schema.",
60 DeprecationWarning,
61 stacklevel=2,
62 )
63 return _META_SCHEMAS
64 raise AttributeError(f"module {__name__} has no attribute {name}")
67def validates(version):
68 """
69 Register the decorated validator for a ``version`` of the specification.
71 Registered validators and their meta schemas will be considered when
72 parsing :kw:`$schema` keywords' URIs.
74 Arguments:
76 version (str):
78 An identifier to use as the version's name
80 Returns:
82 collections.abc.Callable:
84 a class decorator to decorate the validator with the version
85 """
87 def _validates(cls):
88 _VALIDATORS[version] = cls
89 meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
90 _META_SCHEMAS[meta_schema_id] = cls
91 return cls
92 return _validates
95def _id_of(schema):
96 """
97 Return the ID of a schema for recent JSON Schema drafts.
98 """
99 if schema is True or schema is False:
100 return ""
101 return schema.get("$id", "")
104def _store_schema_list():
105 if not _VOCABULARIES:
106 package = _utils.resources.files(__package__)
107 for version in package.joinpath("schemas", "vocabularies").iterdir():
108 for path in version.iterdir():
109 vocabulary = json.loads(path.read_text())
110 _VOCABULARIES.append((vocabulary["$id"], vocabulary))
111 return [
112 (id, validator.META_SCHEMA) for id, validator in _META_SCHEMAS.items()
113 ] + _VOCABULARIES
116def create(
117 meta_schema,
118 validators=(),
119 version=None,
120 type_checker=_types.draft202012_type_checker,
121 format_checker=_format.draft202012_format_checker,
122 id_of=_id_of,
123 applicable_validators=methodcaller("items"),
124):
125 """
126 Create a new validator class.
128 Arguments:
130 meta_schema (collections.abc.Mapping):
132 the meta schema for the new validator class
134 validators (collections.abc.Mapping):
136 a mapping from names to callables, where each callable will
137 validate the schema property with the given name.
139 Each callable should take 4 arguments:
141 1. a validator instance,
142 2. the value of the property being validated within the
143 instance
144 3. the instance
145 4. the schema
147 version (str):
149 an identifier for the version that this validator class will
150 validate. If provided, the returned validator class will
151 have its ``__name__`` set to include the version, and also
152 will have `jsonschema.validators.validates` automatically
153 called for the given version.
155 type_checker (jsonschema.TypeChecker):
157 a type checker, used when applying the :kw:`type` keyword.
159 If unprovided, a `jsonschema.TypeChecker` will be created
160 with a set of default types typical of JSON Schema drafts.
162 format_checker (jsonschema.FormatChecker):
164 a format checker, used when applying the :kw:`format` keyword.
166 If unprovided, a `jsonschema.FormatChecker` will be created
167 with a set of default formats typical of JSON Schema drafts.
169 id_of (collections.abc.Callable):
171 A function that given a schema, returns its ID.
173 applicable_validators (collections.abc.Callable):
175 A function that given a schema, returns the list of
176 applicable validators (validation keywords and callables)
177 which will be used to validate the instance.
179 Returns:
181 a new `jsonschema.protocols.Validator` class
182 """
183 # preemptively don't shadow the `Validator.format_checker` local
184 format_checker_arg = format_checker
186 @attr.s
187 class Validator:
189 VALIDATORS = dict(validators)
190 META_SCHEMA = dict(meta_schema)
191 TYPE_CHECKER = type_checker
192 FORMAT_CHECKER = format_checker_arg
193 ID_OF = staticmethod(id_of)
195 schema = attr.ib(repr=reprlib.repr)
196 resolver = attr.ib(default=None, repr=False)
197 format_checker = attr.ib(default=None)
199 def __init_subclass__(cls):
200 warnings.warn(
201 (
202 "Subclassing validator classes is not intended to "
203 "be part of their public API. A future version "
204 "will make doing so an error, as the behavior of "
205 "subclasses isn't guaranteed to stay the same "
206 "between releases of jsonschema. Instead, prefer "
207 "composition of validators, wrapping them in an object "
208 "owned entirely by the downstream library."
209 ),
210 DeprecationWarning,
211 stacklevel=2,
212 )
214 def __attrs_post_init__(self):
215 if self.resolver is None:
216 self.resolver = RefResolver.from_schema(
217 self.schema,
218 id_of=id_of,
219 )
221 @classmethod
222 def check_schema(cls, schema, format_checker=_UNSET):
223 Validator = validator_for(cls.META_SCHEMA, default=cls)
224 if format_checker is _UNSET:
225 format_checker = Validator.FORMAT_CHECKER
226 validator = Validator(
227 schema=cls.META_SCHEMA,
228 format_checker=format_checker,
229 )
230 for error in validator.iter_errors(schema):
231 raise exceptions.SchemaError.create_from(error)
233 def evolve(self, **changes):
234 # Essentially reproduces attr.evolve, but may involve instantiating
235 # a different class than this one.
236 cls = self.__class__
238 schema = changes.setdefault("schema", self.schema)
239 NewValidator = validator_for(schema, default=cls)
241 for field in attr.fields(cls):
242 if not field.init:
243 continue
244 attr_name = field.name # To deal with private attributes.
245 init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
246 if init_name not in changes:
247 changes[init_name] = getattr(self, attr_name)
249 return NewValidator(**changes)
251 def iter_errors(self, instance, _schema=None):
252 if _schema is not None:
253 warnings.warn(
254 (
255 "Passing a schema to Validator.iter_errors "
256 "is deprecated and will be removed in a future "
257 "release. Call validator.evolve(schema=new_schema)."
258 "iter_errors(...) instead."
259 ),
260 DeprecationWarning,
261 stacklevel=2,
262 )
263 else:
264 _schema = self.schema
266 if _schema is True:
267 return
268 elif _schema is False:
269 yield exceptions.ValidationError(
270 f"False schema does not allow {instance!r}",
271 validator=None,
272 validator_value=None,
273 instance=instance,
274 schema=_schema,
275 )
276 return
278 scope = id_of(_schema)
279 if scope:
280 self.resolver.push_scope(scope)
281 try:
282 for k, v in applicable_validators(_schema):
283 validator = self.VALIDATORS.get(k)
284 if validator is None:
285 continue
287 errors = validator(self, v, instance, _schema) or ()
288 for error in errors:
289 # set details if not already set by the called fn
290 error._set(
291 validator=k,
292 validator_value=v,
293 instance=instance,
294 schema=_schema,
295 type_checker=self.TYPE_CHECKER,
296 )
297 if k not in {"if", "$ref"}:
298 error.schema_path.appendleft(k)
299 yield error
300 finally:
301 if scope:
302 self.resolver.pop_scope()
304 def descend(self, instance, schema, path=None, schema_path=None):
305 for error in self.evolve(schema=schema).iter_errors(instance):
306 if path is not None:
307 error.path.appendleft(path)
308 if schema_path is not None:
309 error.schema_path.appendleft(schema_path)
310 yield error
312 def validate(self, *args, **kwargs):
313 for error in self.iter_errors(*args, **kwargs):
314 raise error
316 def is_type(self, instance, type):
317 try:
318 return self.TYPE_CHECKER.is_type(instance, type)
319 except exceptions.UndefinedTypeCheck:
320 raise exceptions.UnknownType(type, instance, self.schema)
322 def is_valid(self, instance, _schema=None):
323 if _schema is not None:
324 warnings.warn(
325 (
326 "Passing a schema to Validator.is_valid is deprecated "
327 "and will be removed in a future release. Call "
328 "validator.evolve(schema=new_schema).is_valid(...) "
329 "instead."
330 ),
331 DeprecationWarning,
332 stacklevel=2,
333 )
334 self = self.evolve(schema=_schema)
336 error = next(self.iter_errors(instance), None)
337 return error is None
339 if version is not None:
340 safe = version.title().replace(" ", "").replace("-", "")
341 Validator.__name__ = Validator.__qualname__ = f"{safe}Validator"
342 Validator = validates(version)(Validator)
344 return Validator
347def extend(
348 validator,
349 validators=(),
350 version=None,
351 type_checker=None,
352 format_checker=None,
353):
354 """
355 Create a new validator class by extending an existing one.
357 Arguments:
359 validator (jsonschema.protocols.Validator):
361 an existing validator class
363 validators (collections.abc.Mapping):
365 a mapping of new validator callables to extend with, whose
366 structure is as in `create`.
368 .. note::
370 Any validator callables with the same name as an
371 existing one will (silently) replace the old validator
372 callable entirely, effectively overriding any validation
373 done in the "parent" validator class.
375 If you wish to instead extend the behavior of a parent's
376 validator callable, delegate and call it directly in
377 the new validator function by retrieving it using
378 ``OldValidator.VALIDATORS["validation_keyword_name"]``.
380 version (str):
382 a version for the new validator class
384 type_checker (jsonschema.TypeChecker):
386 a type checker, used when applying the :kw:`type` keyword.
388 If unprovided, the type checker of the extended
389 `jsonschema.protocols.Validator` will be carried along.
391 format_checker (jsonschema.FormatChecker):
393 a format checker, used when applying the :kw:`format` keyword.
395 If unprovided, the format checker of the extended
396 `jsonschema.protocols.Validator` will be carried along.
398 Returns:
400 a new `jsonschema.protocols.Validator` class extending the one
401 provided
403 .. note:: Meta Schemas
405 The new validator class will have its parent's meta schema.
407 If you wish to change or extend the meta schema in the new
408 validator class, modify ``META_SCHEMA`` directly on the returned
409 class. Note that no implicit copying is done, so a copy should
410 likely be made before modifying it, in order to not affect the
411 old validator.
412 """
414 all_validators = dict(validator.VALIDATORS)
415 all_validators.update(validators)
417 if type_checker is None:
418 type_checker = validator.TYPE_CHECKER
419 if format_checker is None:
420 format_checker = validator.FORMAT_CHECKER
421 return create(
422 meta_schema=validator.META_SCHEMA,
423 validators=all_validators,
424 version=version,
425 type_checker=type_checker,
426 format_checker=format_checker,
427 id_of=validator.ID_OF,
428 )
431Draft3Validator = create(
432 meta_schema=_utils.load_schema("draft3"),
433 validators={
434 "$ref": _validators.ref,
435 "additionalItems": _validators.additionalItems,
436 "additionalProperties": _validators.additionalProperties,
437 "dependencies": _legacy_validators.dependencies_draft3,
438 "disallow": _legacy_validators.disallow_draft3,
439 "divisibleBy": _validators.multipleOf,
440 "enum": _validators.enum,
441 "extends": _legacy_validators.extends_draft3,
442 "format": _validators.format,
443 "items": _legacy_validators.items_draft3_draft4,
444 "maxItems": _validators.maxItems,
445 "maxLength": _validators.maxLength,
446 "maximum": _legacy_validators.maximum_draft3_draft4,
447 "minItems": _validators.minItems,
448 "minLength": _validators.minLength,
449 "minimum": _legacy_validators.minimum_draft3_draft4,
450 "pattern": _validators.pattern,
451 "patternProperties": _validators.patternProperties,
452 "properties": _legacy_validators.properties_draft3,
453 "type": _legacy_validators.type_draft3,
454 "uniqueItems": _validators.uniqueItems,
455 },
456 type_checker=_types.draft3_type_checker,
457 format_checker=_format.draft3_format_checker,
458 version="draft3",
459 id_of=_legacy_validators.id_of_ignore_ref(property="id"),
460 applicable_validators=_legacy_validators.ignore_ref_siblings,
461)
463Draft4Validator = create(
464 meta_schema=_utils.load_schema("draft4"),
465 validators={
466 "$ref": _validators.ref,
467 "additionalItems": _validators.additionalItems,
468 "additionalProperties": _validators.additionalProperties,
469 "allOf": _validators.allOf,
470 "anyOf": _validators.anyOf,
471 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7,
472 "enum": _validators.enum,
473 "format": _validators.format,
474 "items": _legacy_validators.items_draft3_draft4,
475 "maxItems": _validators.maxItems,
476 "maxLength": _validators.maxLength,
477 "maxProperties": _validators.maxProperties,
478 "maximum": _legacy_validators.maximum_draft3_draft4,
479 "minItems": _validators.minItems,
480 "minLength": _validators.minLength,
481 "minProperties": _validators.minProperties,
482 "minimum": _legacy_validators.minimum_draft3_draft4,
483 "multipleOf": _validators.multipleOf,
484 "not": _validators.not_,
485 "oneOf": _validators.oneOf,
486 "pattern": _validators.pattern,
487 "patternProperties": _validators.patternProperties,
488 "properties": _validators.properties,
489 "required": _validators.required,
490 "type": _validators.type,
491 "uniqueItems": _validators.uniqueItems,
492 },
493 type_checker=_types.draft4_type_checker,
494 format_checker=_format.draft4_format_checker,
495 version="draft4",
496 id_of=_legacy_validators.id_of_ignore_ref(property="id"),
497 applicable_validators=_legacy_validators.ignore_ref_siblings,
498)
500Draft6Validator = create(
501 meta_schema=_utils.load_schema("draft6"),
502 validators={
503 "$ref": _validators.ref,
504 "additionalItems": _validators.additionalItems,
505 "additionalProperties": _validators.additionalProperties,
506 "allOf": _validators.allOf,
507 "anyOf": _validators.anyOf,
508 "const": _validators.const,
509 "contains": _legacy_validators.contains_draft6_draft7,
510 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7,
511 "enum": _validators.enum,
512 "exclusiveMaximum": _validators.exclusiveMaximum,
513 "exclusiveMinimum": _validators.exclusiveMinimum,
514 "format": _validators.format,
515 "items": _legacy_validators.items_draft6_draft7_draft201909,
516 "maxItems": _validators.maxItems,
517 "maxLength": _validators.maxLength,
518 "maxProperties": _validators.maxProperties,
519 "maximum": _validators.maximum,
520 "minItems": _validators.minItems,
521 "minLength": _validators.minLength,
522 "minProperties": _validators.minProperties,
523 "minimum": _validators.minimum,
524 "multipleOf": _validators.multipleOf,
525 "not": _validators.not_,
526 "oneOf": _validators.oneOf,
527 "pattern": _validators.pattern,
528 "patternProperties": _validators.patternProperties,
529 "properties": _validators.properties,
530 "propertyNames": _validators.propertyNames,
531 "required": _validators.required,
532 "type": _validators.type,
533 "uniqueItems": _validators.uniqueItems,
534 },
535 type_checker=_types.draft6_type_checker,
536 format_checker=_format.draft6_format_checker,
537 version="draft6",
538 id_of=_legacy_validators.id_of_ignore_ref(),
539 applicable_validators=_legacy_validators.ignore_ref_siblings,
540)
542Draft7Validator = create(
543 meta_schema=_utils.load_schema("draft7"),
544 validators={
545 "$ref": _validators.ref,
546 "additionalItems": _validators.additionalItems,
547 "additionalProperties": _validators.additionalProperties,
548 "allOf": _validators.allOf,
549 "anyOf": _validators.anyOf,
550 "const": _validators.const,
551 "contains": _legacy_validators.contains_draft6_draft7,
552 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7,
553 "enum": _validators.enum,
554 "exclusiveMaximum": _validators.exclusiveMaximum,
555 "exclusiveMinimum": _validators.exclusiveMinimum,
556 "format": _validators.format,
557 "if": _validators.if_,
558 "items": _legacy_validators.items_draft6_draft7_draft201909,
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 "properties": _validators.properties,
573 "propertyNames": _validators.propertyNames,
574 "required": _validators.required,
575 "type": _validators.type,
576 "uniqueItems": _validators.uniqueItems,
577 },
578 type_checker=_types.draft7_type_checker,
579 format_checker=_format.draft7_format_checker,
580 version="draft7",
581 id_of=_legacy_validators.id_of_ignore_ref(),
582 applicable_validators=_legacy_validators.ignore_ref_siblings,
583)
585Draft201909Validator = create(
586 meta_schema=_utils.load_schema("draft2019-09"),
587 validators={
588 "$recursiveRef": _legacy_validators.recursiveRef,
589 "$ref": _validators.ref,
590 "additionalItems": _validators.additionalItems,
591 "additionalProperties": _validators.additionalProperties,
592 "allOf": _validators.allOf,
593 "anyOf": _validators.anyOf,
594 "const": _validators.const,
595 "contains": _validators.contains,
596 "dependentRequired": _validators.dependentRequired,
597 "dependentSchemas": _validators.dependentSchemas,
598 "enum": _validators.enum,
599 "exclusiveMaximum": _validators.exclusiveMaximum,
600 "exclusiveMinimum": _validators.exclusiveMinimum,
601 "format": _validators.format,
602 "if": _validators.if_,
603 "items": _legacy_validators.items_draft6_draft7_draft201909,
604 "maxItems": _validators.maxItems,
605 "maxLength": _validators.maxLength,
606 "maxProperties": _validators.maxProperties,
607 "maximum": _validators.maximum,
608 "minItems": _validators.minItems,
609 "minLength": _validators.minLength,
610 "minProperties": _validators.minProperties,
611 "minimum": _validators.minimum,
612 "multipleOf": _validators.multipleOf,
613 "not": _validators.not_,
614 "oneOf": _validators.oneOf,
615 "pattern": _validators.pattern,
616 "patternProperties": _validators.patternProperties,
617 "properties": _validators.properties,
618 "propertyNames": _validators.propertyNames,
619 "required": _validators.required,
620 "type": _validators.type,
621 "unevaluatedItems": _legacy_validators.unevaluatedItems_draft2019,
622 "unevaluatedProperties": _validators.unevaluatedProperties,
623 "uniqueItems": _validators.uniqueItems,
624 },
625 type_checker=_types.draft201909_type_checker,
626 format_checker=_format.draft201909_format_checker,
627 version="draft2019-09",
628)
630Draft202012Validator = create(
631 meta_schema=_utils.load_schema("draft2020-12"),
632 validators={
633 "$dynamicRef": _validators.dynamicRef,
634 "$ref": _validators.ref,
635 "additionalItems": _validators.additionalItems,
636 "additionalProperties": _validators.additionalProperties,
637 "allOf": _validators.allOf,
638 "anyOf": _validators.anyOf,
639 "const": _validators.const,
640 "contains": _validators.contains,
641 "dependentRequired": _validators.dependentRequired,
642 "dependentSchemas": _validators.dependentSchemas,
643 "enum": _validators.enum,
644 "exclusiveMaximum": _validators.exclusiveMaximum,
645 "exclusiveMinimum": _validators.exclusiveMinimum,
646 "format": _validators.format,
647 "if": _validators.if_,
648 "items": _validators.items,
649 "maxItems": _validators.maxItems,
650 "maxLength": _validators.maxLength,
651 "maxProperties": _validators.maxProperties,
652 "maximum": _validators.maximum,
653 "minItems": _validators.minItems,
654 "minLength": _validators.minLength,
655 "minProperties": _validators.minProperties,
656 "minimum": _validators.minimum,
657 "multipleOf": _validators.multipleOf,
658 "not": _validators.not_,
659 "oneOf": _validators.oneOf,
660 "pattern": _validators.pattern,
661 "patternProperties": _validators.patternProperties,
662 "prefixItems": _validators.prefixItems,
663 "properties": _validators.properties,
664 "propertyNames": _validators.propertyNames,
665 "required": _validators.required,
666 "type": _validators.type,
667 "unevaluatedItems": _validators.unevaluatedItems,
668 "unevaluatedProperties": _validators.unevaluatedProperties,
669 "uniqueItems": _validators.uniqueItems,
670 },
671 type_checker=_types.draft202012_type_checker,
672 format_checker=_format.draft202012_format_checker,
673 version="draft2020-12",
674)
676_LATEST_VERSION = Draft202012Validator
679class RefResolver:
680 """
681 Resolve JSON References.
683 Arguments:
685 base_uri (str):
687 The URI of the referring document
689 referrer:
691 The actual referring document
693 store (dict):
695 A mapping from URIs to documents to cache
697 cache_remote (bool):
699 Whether remote refs should be cached after first resolution
701 handlers (dict):
703 A mapping from URI schemes to functions that should be used
704 to retrieve them
706 urljoin_cache (:func:`functools.lru_cache`):
708 A cache that will be used for caching the results of joining
709 the resolution scope to subscopes.
711 remote_cache (:func:`functools.lru_cache`):
713 A cache that will be used for caching the results of
714 resolved remote URLs.
716 Attributes:
718 cache_remote (bool):
720 Whether remote refs should be cached after first resolution
721 """
723 def __init__(
724 self,
725 base_uri,
726 referrer,
727 store=m(),
728 cache_remote=True,
729 handlers=(),
730 urljoin_cache=None,
731 remote_cache=None,
732 ):
733 if urljoin_cache is None:
734 urljoin_cache = lru_cache(1024)(urljoin)
735 if remote_cache is None:
736 remote_cache = lru_cache(1024)(self.resolve_from_url)
738 self.referrer = referrer
739 self.cache_remote = cache_remote
740 self.handlers = dict(handlers)
742 self._scopes_stack = [base_uri]
744 self.store = _utils.URIDict(_store_schema_list())
745 self.store.update(store)
746 self.store.update(
747 (schema["$id"], schema)
748 for schema in store.values()
749 if isinstance(schema, Mapping) and "$id" in schema
750 )
751 self.store[base_uri] = referrer
753 self._urljoin_cache = urljoin_cache
754 self._remote_cache = remote_cache
756 @classmethod
757 def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
758 """
759 Construct a resolver from a JSON schema object.
761 Arguments:
763 schema:
765 the referring schema
767 Returns:
769 `RefResolver`
770 """
772 return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) # noqa: B026, E501
774 def push_scope(self, scope):
775 """
776 Enter a given sub-scope.
778 Treats further dereferences as being performed underneath the
779 given scope.
780 """
781 self._scopes_stack.append(
782 self._urljoin_cache(self.resolution_scope, scope),
783 )
785 def pop_scope(self):
786 """
787 Exit the most recent entered scope.
789 Treats further dereferences as being performed underneath the
790 original scope.
792 Don't call this method more times than `push_scope` has been
793 called.
794 """
795 try:
796 self._scopes_stack.pop()
797 except IndexError:
798 raise exceptions.RefResolutionError(
799 "Failed to pop the scope from an empty stack. "
800 "`pop_scope()` should only be called once for every "
801 "`push_scope()`",
802 )
804 @property
805 def resolution_scope(self):
806 """
807 Retrieve the current resolution scope.
808 """
809 return self._scopes_stack[-1]
811 @property
812 def base_uri(self):
813 """
814 Retrieve the current base URI, not including any fragment.
815 """
816 uri, _ = urldefrag(self.resolution_scope)
817 return uri
819 @contextlib.contextmanager
820 def in_scope(self, scope):
821 """
822 Temporarily enter the given scope for the duration of the context.
824 .. deprecated:: v4.0.0
825 """
826 warnings.warn(
827 "jsonschema.RefResolver.in_scope is deprecated and will be "
828 "removed in a future release.",
829 DeprecationWarning,
830 stacklevel=3,
831 )
832 self.push_scope(scope)
833 try:
834 yield
835 finally:
836 self.pop_scope()
838 @contextlib.contextmanager
839 def resolving(self, ref):
840 """
841 Resolve the given ``ref`` and enter its resolution scope.
843 Exits the scope on exit of this context manager.
845 Arguments:
847 ref (str):
849 The reference to resolve
850 """
852 url, resolved = self.resolve(ref)
853 self.push_scope(url)
854 try:
855 yield resolved
856 finally:
857 self.pop_scope()
859 def _find_in_referrer(self, key):
860 return self._get_subschemas_cache()[key]
862 @lru_cache() # noqa: B019
863 def _get_subschemas_cache(self):
864 cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS}
865 for keyword, subschema in _search_schema(
866 self.referrer, _match_subschema_keywords,
867 ):
868 cache[keyword].append(subschema)
869 return cache
871 @lru_cache() # noqa: B019
872 def _find_in_subschemas(self, url):
873 subschemas = self._get_subschemas_cache()["$id"]
874 if not subschemas:
875 return None
876 uri, fragment = urldefrag(url)
877 for subschema in subschemas:
878 target_uri = self._urljoin_cache(
879 self.resolution_scope, subschema["$id"],
880 )
881 if target_uri.rstrip("/") == uri.rstrip("/"):
882 if fragment:
883 subschema = self.resolve_fragment(subschema, fragment)
884 self.store[url] = subschema
885 return url, subschema
886 return None
888 def resolve(self, ref):
889 """
890 Resolve the given reference.
891 """
892 url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/")
894 match = self._find_in_subschemas(url)
895 if match is not None:
896 return match
898 return url, self._remote_cache(url)
900 def resolve_from_url(self, url):
901 """
902 Resolve the given URL.
903 """
904 url, fragment = urldefrag(url)
905 if not url:
906 url = self.base_uri
908 try:
909 document = self.store[url]
910 except KeyError:
911 try:
912 document = self.resolve_remote(url)
913 except Exception as exc:
914 raise exceptions.RefResolutionError(exc)
916 return self.resolve_fragment(document, fragment)
918 def resolve_fragment(self, document, fragment):
919 """
920 Resolve a ``fragment`` within the referenced ``document``.
922 Arguments:
924 document:
926 The referent document
928 fragment (str):
930 a URI fragment to resolve within it
931 """
933 fragment = fragment.lstrip("/")
935 if not fragment:
936 return document
938 if document is self.referrer:
939 find = self._find_in_referrer
940 else:
942 def find(key):
943 yield from _search_schema(document, _match_keyword(key))
945 for keyword in ["$anchor", "$dynamicAnchor"]:
946 for subschema in find(keyword):
947 if fragment == subschema[keyword]:
948 return subschema
949 for keyword in ["id", "$id"]:
950 for subschema in find(keyword):
951 if "#" + fragment == subschema[keyword]:
952 return subschema
954 # Resolve via path
955 parts = unquote(fragment).split("/") if fragment else []
956 for part in parts:
957 part = part.replace("~1", "/").replace("~0", "~")
959 if isinstance(document, Sequence):
960 # Array indexes should be turned into integers
961 try:
962 part = int(part)
963 except ValueError:
964 pass
965 try:
966 document = document[part]
967 except (TypeError, LookupError):
968 raise exceptions.RefResolutionError(
969 f"Unresolvable JSON pointer: {fragment!r}",
970 )
972 return document
974 def resolve_remote(self, uri):
975 """
976 Resolve a remote ``uri``.
978 If called directly, does not check the store first, but after
979 retrieving the document at the specified URI it will be saved in
980 the store if :attr:`cache_remote` is True.
982 .. note::
984 If the requests_ library is present, ``jsonschema`` will use it to
985 request the remote ``uri``, so that the correct encoding is
986 detected and used.
988 If it isn't, or if the scheme of the ``uri`` is not ``http`` or
989 ``https``, UTF-8 is assumed.
991 Arguments:
993 uri (str):
995 The URI to resolve
997 Returns:
999 The retrieved document
1001 .. _requests: https://pypi.org/project/requests/
1002 """
1003 try:
1004 import requests
1005 except ImportError:
1006 requests = None
1008 scheme = urlsplit(uri).scheme
1010 if scheme in self.handlers:
1011 result = self.handlers[scheme](uri)
1012 elif scheme in ["http", "https"] and requests:
1013 # Requests has support for detecting the correct encoding of
1014 # json over http
1015 result = requests.get(uri).json()
1016 else:
1017 # Otherwise, pass off to urllib and assume utf-8
1018 with urlopen(uri) as url:
1019 result = json.loads(url.read().decode("utf-8"))
1021 if self.cache_remote:
1022 self.store[uri] = result
1023 return result
1026_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor")
1029def _match_keyword(keyword):
1031 def matcher(value):
1032 if keyword in value:
1033 yield value
1035 return matcher
1038def _match_subschema_keywords(value):
1039 for keyword in _SUBSCHEMAS_KEYWORDS:
1040 if keyword in value:
1041 yield keyword, value
1044def _search_schema(schema, matcher):
1045 """Breadth-first search routine."""
1046 values = deque([schema])
1047 while values:
1048 value = values.pop()
1049 if not isinstance(value, dict):
1050 continue
1051 yield from matcher(value)
1052 values.extendleft(value.values())
1055def validate(instance, schema, cls=None, *args, **kwargs):
1056 """
1057 Validate an instance under the given schema.
1059 >>> validate([2, 3, 4], {"maxItems": 2})
1060 Traceback (most recent call last):
1061 ...
1062 ValidationError: [2, 3, 4] is too long
1064 :func:`~jsonschema.validators.validate` will first verify that the
1065 provided schema is itself valid, since not doing so can lead to less
1066 obvious error messages and fail in less obvious or consistent ways.
1068 If you know you have a valid schema already, especially
1069 if you intend to validate multiple instances with
1070 the same schema, you likely would prefer using the
1071 `jsonschema.protocols.Validator.validate` method directly on a
1072 specific validator (e.g. ``Draft20212Validator.validate``).
1075 Arguments:
1077 instance:
1079 The instance to validate
1081 schema:
1083 The schema to validate with
1085 cls (jsonschema.protocols.Validator):
1087 The class that will be used to validate the instance.
1089 If the ``cls`` argument is not provided, two things will happen
1090 in accordance with the specification. First, if the schema has a
1091 :kw:`$schema` keyword containing a known meta-schema [#]_ then the
1092 proper validator will be used. The specification recommends that
1093 all schemas contain :kw:`$schema` properties for this reason. If no
1094 :kw:`$schema` property is found, the default validator class is the
1095 latest released draft.
1097 Any other provided positional and keyword arguments will be passed
1098 on when instantiating the ``cls``.
1100 Raises:
1102 `jsonschema.exceptions.ValidationError`:
1104 if the instance is invalid
1106 `jsonschema.exceptions.SchemaError`:
1108 if the schema itself is invalid
1110 .. rubric:: Footnotes
1111 .. [#] known by a validator registered with
1112 `jsonschema.validators.validates`
1113 """
1114 if cls is None:
1115 cls = validator_for(schema)
1117 cls.check_schema(schema)
1118 validator = cls(schema, *args, **kwargs)
1119 error = exceptions.best_match(validator.iter_errors(instance))
1120 if error is not None:
1121 raise error
1124def validator_for(schema, default=_UNSET):
1125 """
1126 Retrieve the validator class appropriate for validating the given schema.
1128 Uses the :kw:`$schema` keyword that should be present in the given
1129 schema to look up the appropriate validator class.
1131 Arguments:
1133 schema (collections.abc.Mapping or bool):
1135 the schema to look at
1137 default:
1139 the default to return if the appropriate validator class
1140 cannot be determined.
1142 If unprovided, the default is to return the latest supported
1143 draft.
1144 """
1146 DefaultValidator = _LATEST_VERSION if default is _UNSET else default
1148 if schema is True or schema is False or "$schema" not in schema:
1149 return DefaultValidator
1150 if schema["$schema"] not in _META_SCHEMAS:
1151 if default is _UNSET:
1152 warn(
1153 (
1154 "The metaschema specified by $schema was not found. "
1155 "Using the latest draft to validate, but this will raise "
1156 "an error in the future."
1157 ),
1158 DeprecationWarning,
1159 stacklevel=2,
1160 )
1161 return _META_SCHEMAS.get(schema["$schema"], DefaultValidator)