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