1""" 
    2Creation and extension of validators, with implementations for existing drafts. 
    3""" 
    4from __future__ import annotations 
    5 
    6from collections import deque 
    7from collections.abc import Iterable, Mapping, Sequence 
    8from functools import lru_cache 
    9from operator import methodcaller 
    10from typing import TYPE_CHECKING 
    11from urllib.parse import unquote, urldefrag, urljoin, urlsplit 
    12from urllib.request import urlopen 
    13from warnings import warn 
    14import contextlib 
    15import json 
    16import reprlib 
    17import warnings 
    18 
    19from attrs import define, field, fields 
    20from jsonschema_specifications import REGISTRY as SPECIFICATIONS 
    21from rpds import HashTrieMap 
    22import referencing.exceptions 
    23import referencing.jsonschema 
    24 
    25from jsonschema import ( 
    26    _format, 
    27    _keywords, 
    28    _legacy_keywords, 
    29    _types, 
    30    _typing, 
    31    _utils, 
    32    exceptions, 
    33) 
    34 
    35if TYPE_CHECKING: 
    36    from jsonschema.protocols import Validator 
    37 
    38_UNSET = _utils.Unset() 
    39 
    40_VALIDATORS: dict[str, Validator] = {} 
    41_META_SCHEMAS = _utils.URIDict() 
    42 
    43 
    44def __getattr__(name): 
    45    if name == "ErrorTree": 
    46        warnings.warn( 
    47            "Importing ErrorTree from jsonschema.validators is deprecated. " 
    48            "Instead import it from jsonschema.exceptions.", 
    49            DeprecationWarning, 
    50            stacklevel=2, 
    51        ) 
    52        from jsonschema.exceptions import ErrorTree 
    53        return ErrorTree 
    54    elif name == "validators": 
    55        warnings.warn( 
    56            "Accessing jsonschema.validators.validators is deprecated. " 
    57            "Use jsonschema.validators.validator_for with a given schema.", 
    58            DeprecationWarning, 
    59            stacklevel=2, 
    60        ) 
    61        return _VALIDATORS 
    62    elif name == "meta_schemas": 
    63        warnings.warn( 
    64            "Accessing jsonschema.validators.meta_schemas is deprecated. " 
    65            "Use jsonschema.validators.validator_for with a given schema.", 
    66            DeprecationWarning, 
    67            stacklevel=2, 
    68        ) 
    69        return _META_SCHEMAS 
    70    elif name == "RefResolver": 
    71        warnings.warn( 
    72            _RefResolver._DEPRECATION_MESSAGE, 
    73            DeprecationWarning, 
    74            stacklevel=2, 
    75        ) 
    76        return _RefResolver 
    77    raise AttributeError(f"module {__name__} has no attribute {name}") 
    78 
    79 
    80def validates(version): 
    81    """ 
    82    Register the decorated validator for a ``version`` of the specification. 
    83 
    84    Registered validators and their meta schemas will be considered when 
    85    parsing :kw:`$schema` keywords' URIs. 
    86 
    87    Arguments: 
    88 
    89        version (str): 
    90 
    91            An identifier to use as the version's name 
    92 
    93    Returns: 
    94 
    95        collections.abc.Callable: 
    96 
    97            a class decorator to decorate the validator with the version 
    98 
    99    """ 
    100 
    101    def _validates(cls): 
    102        _VALIDATORS[version] = cls 
    103        meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 
    104        _META_SCHEMAS[meta_schema_id] = cls 
    105        return cls 
    106    return _validates 
    107 
    108 
    109def _warn_for_remote_retrieve(uri: str): 
    110    from urllib.request import Request, urlopen 
    111    headers = {"User-Agent": "python-jsonschema (deprecated $ref resolution)"} 
    112    request = Request(uri, headers=headers)  # noqa: S310 
    113    with urlopen(request) as response:  # noqa: S310 
    114        warnings.warn( 
    115            "Automatically retrieving remote references can be a security " 
    116            "vulnerability and is discouraged by the JSON Schema " 
    117            "specifications. Relying on this behavior is deprecated " 
    118            "and will shortly become an error. If you are sure you want to " 
    119            "remotely retrieve your reference and that it is safe to do so, " 
    120            "you can find instructions for doing so via referencing.Registry " 
    121            "in the referencing documentation " 
    122            "(https://referencing.readthedocs.org).", 
    123            DeprecationWarning, 
    124            stacklevel=9,  # Ha ha ha ha magic numbers :/ 
    125        ) 
    126        return referencing.Resource.from_contents( 
    127            json.load(response), 
    128            default_specification=referencing.jsonschema.DRAFT202012, 
    129        ) 
    130 
    131 
    132_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( 
    133    referencing.Registry(retrieve=_warn_for_remote_retrieve),  # type: ignore[call-arg] 
    134) 
    135 
    136 
    137def create( 
    138    meta_schema: referencing.jsonschema.ObjectSchema, 
    139    validators: ( 
    140        Mapping[str, _typing.SchemaKeywordValidator] 
    141        | Iterable[tuple[str, _typing.SchemaKeywordValidator]] 
    142    ) = (), 
    143    version: str | None = None, 
    144    type_checker: _types.TypeChecker = _types.draft202012_type_checker, 
    145    format_checker: _format.FormatChecker = _format.draft202012_format_checker, 
    146    id_of: _typing.id_of = referencing.jsonschema.DRAFT202012.id_of, 
    147    applicable_validators: _typing.ApplicableValidators = methodcaller( 
    148        "items", 
    149    ), 
    150) -> type[Validator]: 
    151    """ 
    152    Create a new validator class. 
    153 
    154    Arguments: 
    155 
    156        meta_schema: 
    157 
    158            the meta schema for the new validator class 
    159 
    160        validators: 
    161 
    162            a mapping from names to callables, where each callable will 
    163            validate the schema property with the given name. 
    164 
    165            Each callable should take 4 arguments: 
    166 
    167                1. a validator instance, 
    168                2. the value of the property being validated within the 
    169                   instance 
    170                3. the instance 
    171                4. the schema 
    172 
    173        version: 
    174 
    175            an identifier for the version that this validator class will 
    176            validate. If provided, the returned validator class will 
    177            have its ``__name__`` set to include the version, and also 
    178            will have `jsonschema.validators.validates` automatically 
    179            called for the given version. 
    180 
    181        type_checker: 
    182 
    183            a type checker, used when applying the :kw:`type` keyword. 
    184 
    185            If unprovided, a `jsonschema.TypeChecker` will be created 
    186            with a set of default types typical of JSON Schema drafts. 
    187 
    188        format_checker: 
    189 
    190            a format checker, used when applying the :kw:`format` keyword. 
    191 
    192            If unprovided, a `jsonschema.FormatChecker` will be created 
    193            with a set of default formats typical of JSON Schema drafts. 
    194 
    195        id_of: 
    196 
    197            A function that given a schema, returns its ID. 
    198 
    199        applicable_validators: 
    200 
    201            A function that, given a schema, returns the list of 
    202            applicable schema keywords and associated values 
    203            which will be used to validate the instance. 
    204            This is mostly used to support pre-draft 7 versions of JSON Schema 
    205            which specified behavior around ignoring keywords if they were 
    206            siblings of a ``$ref`` keyword. If you're not attempting to 
    207            implement similar behavior, you can typically ignore this argument 
    208            and leave it at its default. 
    209 
    210    Returns: 
    211 
    212        a new `jsonschema.protocols.Validator` class 
    213 
    214    """ 
    215    # preemptively don't shadow the `Validator.format_checker` local 
    216    format_checker_arg = format_checker 
    217 
    218    specification = referencing.jsonschema.specification_with( 
    219        dialect_id=id_of(meta_schema) or "urn:unknown-dialect", 
    220        default=referencing.Specification.OPAQUE, 
    221    ) 
    222 
    223    @define 
    224    class Validator: 
    225 
    226        VALIDATORS = dict(validators)  # noqa: RUF012 
    227        META_SCHEMA = dict(meta_schema)  # noqa: RUF012 
    228        TYPE_CHECKER = type_checker 
    229        FORMAT_CHECKER = format_checker_arg 
    230        ID_OF = staticmethod(id_of) 
    231 
    232        _APPLICABLE_VALIDATORS = applicable_validators 
    233        _validators = field(init=False, repr=False, eq=False) 
    234 
    235        schema: referencing.jsonschema.Schema = field(repr=reprlib.repr) 
    236        _ref_resolver = field(default=None, repr=False, alias="resolver") 
    237        format_checker: _format.FormatChecker | None = field(default=None) 
    238        # TODO: include new meta-schemas added at runtime 
    239        _registry: referencing.jsonschema.SchemaRegistry = field( 
    240            default=_REMOTE_WARNING_REGISTRY, 
    241            kw_only=True, 
    242            repr=False, 
    243        ) 
    244        _resolver = field( 
    245            alias="_resolver", 
    246            default=None, 
    247            kw_only=True, 
    248            repr=False, 
    249        ) 
    250 
    251        def __init_subclass__(cls): 
    252            warnings.warn( 
    253                ( 
    254                    "Subclassing validator classes is not intended to " 
    255                    "be part of their public API. A future version " 
    256                    "will make doing so an error, as the behavior of " 
    257                    "subclasses isn't guaranteed to stay the same " 
    258                    "between releases of jsonschema. Instead, prefer " 
    259                    "composition of validators, wrapping them in an object " 
    260                    "owned entirely by the downstream library." 
    261                ), 
    262                DeprecationWarning, 
    263                stacklevel=2, 
    264            ) 
    265 
    266            def evolve(self, **changes): 
    267                cls = self.__class__ 
    268                schema = changes.setdefault("schema", self.schema) 
    269                NewValidator = validator_for(schema, default=cls) 
    270 
    271                for field in fields(cls):  # noqa: F402 
    272                    if not field.init: 
    273                        continue 
    274                    attr_name = field.name 
    275                    init_name = field.alias 
    276                    if init_name not in changes: 
    277                        changes[init_name] = getattr(self, attr_name) 
    278 
    279                return NewValidator(**changes) 
    280 
    281            cls.evolve = evolve 
    282 
    283        def __attrs_post_init__(self): 
    284            if self._resolver is None: 
    285                registry = self._registry 
    286                if registry is not _REMOTE_WARNING_REGISTRY: 
    287                    registry = SPECIFICATIONS.combine(registry) 
    288                resource = specification.create_resource(self.schema) 
    289                self._resolver = registry.resolver_with_root(resource) 
    290 
    291            if self.schema is True or self.schema is False: 
    292                self._validators = [] 
    293            else: 
    294                self._validators = [ 
    295                    (self.VALIDATORS[k], k, v) 
    296                    for k, v in applicable_validators(self.schema) 
    297                    if k in self.VALIDATORS 
    298                ] 
    299 
    300            # REMOVEME: Legacy ref resolution state management. 
    301            push_scope = getattr(self._ref_resolver, "push_scope", None) 
    302            if push_scope is not None: 
    303                id = id_of(self.schema) 
    304                if id is not None: 
    305                    push_scope(id) 
    306 
    307        @classmethod 
    308        def check_schema(cls, schema, format_checker=_UNSET): 
    309            Validator = validator_for(cls.META_SCHEMA, default=cls) 
    310            if format_checker is _UNSET: 
    311                format_checker = Validator.FORMAT_CHECKER 
    312            validator = Validator( 
    313                schema=cls.META_SCHEMA, 
    314                format_checker=format_checker, 
    315            ) 
    316            for error in validator.iter_errors(schema): 
    317                raise exceptions.SchemaError.create_from(error) 
    318 
    319        @property 
    320        def resolver(self): 
    321            warnings.warn( 
    322                ( 
    323                    f"Accessing {self.__class__.__name__}.resolver is " 
    324                    "deprecated as of v4.18.0, in favor of the " 
    325                    "https://github.com/python-jsonschema/referencing " 
    326                    "library, which provides more compliant referencing " 
    327                    "behavior as well as more flexible APIs for " 
    328                    "customization." 
    329                ), 
    330                DeprecationWarning, 
    331                stacklevel=2, 
    332            ) 
    333            if self._ref_resolver is None: 
    334                self._ref_resolver = _RefResolver.from_schema( 
    335                    self.schema, 
    336                    id_of=id_of, 
    337                ) 
    338            return self._ref_resolver 
    339 
    340        def evolve(self, **changes): 
    341            schema = changes.setdefault("schema", self.schema) 
    342            NewValidator = validator_for(schema, default=self.__class__) 
    343 
    344            for (attr_name, init_name) in evolve_fields: 
    345                if init_name not in changes: 
    346                    changes[init_name] = getattr(self, attr_name) 
    347 
    348            return NewValidator(**changes) 
    349 
    350        def iter_errors(self, instance, _schema=None): 
    351            if _schema is not None: 
    352                warnings.warn( 
    353                    ( 
    354                        "Passing a schema to Validator.iter_errors " 
    355                        "is deprecated and will be removed in a future " 
    356                        "release. Call validator.evolve(schema=new_schema)." 
    357                        "iter_errors(...) instead." 
    358                    ), 
    359                    DeprecationWarning, 
    360                    stacklevel=2, 
    361                ) 
    362                validators = [ 
    363                    (self.VALIDATORS[k], k, v) 
    364                    for k, v in applicable_validators(_schema) 
    365                    if k in self.VALIDATORS 
    366                ] 
    367            else: 
    368                _schema, validators = self.schema, self._validators 
    369 
    370            if _schema is True: 
    371                return 
    372            elif _schema is False: 
    373                yield exceptions.ValidationError( 
    374                    f"False schema does not allow {instance!r}", 
    375                    validator=None, 
    376                    validator_value=None, 
    377                    instance=instance, 
    378                    schema=_schema, 
    379                ) 
    380                return 
    381 
    382            for validator, k, v in validators: 
    383                errors = validator(self, v, instance, _schema) or () 
    384                for error in errors: 
    385                    # set details if not already set by the called fn 
    386                    error._set( 
    387                        validator=k, 
    388                        validator_value=v, 
    389                        instance=instance, 
    390                        schema=_schema, 
    391                        type_checker=self.TYPE_CHECKER, 
    392                    ) 
    393                    if k not in {"if", "$ref"}: 
    394                        error.schema_path.appendleft(k) 
    395                    yield error 
    396 
    397        def descend( 
    398            self, 
    399            instance, 
    400            schema, 
    401            path=None, 
    402            schema_path=None, 
    403            resolver=None, 
    404        ): 
    405            if schema is True: 
    406                return 
    407            elif schema is False: 
    408                yield exceptions.ValidationError( 
    409                    f"False schema does not allow {instance!r}", 
    410                    validator=None, 
    411                    validator_value=None, 
    412                    instance=instance, 
    413                    schema=schema, 
    414                ) 
    415                return 
    416 
    417            if self._ref_resolver is not None: 
    418                evolved = self.evolve(schema=schema) 
    419            else: 
    420                if resolver is None: 
    421                    resolver = self._resolver.in_subresource( 
    422                        specification.create_resource(schema), 
    423                    ) 
    424                evolved = self.evolve(schema=schema, _resolver=resolver) 
    425 
    426            for k, v in applicable_validators(schema): 
    427                validator = evolved.VALIDATORS.get(k) 
    428                if validator is None: 
    429                    continue 
    430 
    431                errors = validator(evolved, v, instance, schema) or () 
    432                for error in errors: 
    433                    # set details if not already set by the called fn 
    434                    error._set( 
    435                        validator=k, 
    436                        validator_value=v, 
    437                        instance=instance, 
    438                        schema=schema, 
    439                        type_checker=evolved.TYPE_CHECKER, 
    440                    ) 
    441                    if k not in {"if", "$ref"}: 
    442                        error.schema_path.appendleft(k) 
    443                    if path is not None: 
    444                        error.path.appendleft(path) 
    445                    if schema_path is not None: 
    446                        error.schema_path.appendleft(schema_path) 
    447                    yield error 
    448 
    449        def validate(self, *args, **kwargs): 
    450            for error in self.iter_errors(*args, **kwargs): 
    451                raise error 
    452 
    453        def is_type(self, instance, type): 
    454            try: 
    455                return self.TYPE_CHECKER.is_type(instance, type) 
    456            except exceptions.UndefinedTypeCheck: 
    457                exc = exceptions.UnknownType(type, instance, self.schema) 
    458                raise exc from None 
    459 
    460        def _validate_reference(self, ref, instance): 
    461            if self._ref_resolver is None: 
    462                try: 
    463                    resolved = self._resolver.lookup(ref) 
    464                except referencing.exceptions.Unresolvable as err: 
    465                    raise exceptions._WrappedReferencingError(err) from err 
    466 
    467                return self.descend( 
    468                    instance, 
    469                    resolved.contents, 
    470                    resolver=resolved.resolver, 
    471                ) 
    472            else: 
    473                resolve = getattr(self._ref_resolver, "resolve", None) 
    474                if resolve is None: 
    475                    with self._ref_resolver.resolving(ref) as resolved: 
    476                        return self.descend(instance, resolved) 
    477                else: 
    478                    scope, resolved = resolve(ref) 
    479                    self._ref_resolver.push_scope(scope) 
    480 
    481                    try: 
    482                        return list(self.descend(instance, resolved)) 
    483                    finally: 
    484                        self._ref_resolver.pop_scope() 
    485 
    486        def is_valid(self, instance, _schema=None): 
    487            if _schema is not None: 
    488                warnings.warn( 
    489                    ( 
    490                        "Passing a schema to Validator.is_valid is deprecated " 
    491                        "and will be removed in a future release. Call " 
    492                        "validator.evolve(schema=new_schema).is_valid(...) " 
    493                        "instead." 
    494                    ), 
    495                    DeprecationWarning, 
    496                    stacklevel=2, 
    497                ) 
    498                self = self.evolve(schema=_schema) 
    499 
    500            error = next(self.iter_errors(instance), None) 
    501            return error is None 
    502 
    503    evolve_fields = [ 
    504        (field.name, field.alias) 
    505        for field in fields(Validator) 
    506        if field.init 
    507    ] 
    508 
    509    if version is not None: 
    510        safe = version.title().replace(" ", "").replace("-", "") 
    511        Validator.__name__ = Validator.__qualname__ = f"{safe}Validator" 
    512        Validator = validates(version)(Validator)  # type: ignore[misc] 
    513 
    514    return Validator  # type: ignore[return-value] 
    515 
    516 
    517def extend( 
    518    validator, 
    519    validators=(), 
    520    version=None, 
    521    type_checker=None, 
    522    format_checker=None, 
    523): 
    524    """ 
    525    Create a new validator class by extending an existing one. 
    526 
    527    Arguments: 
    528 
    529        validator (jsonschema.protocols.Validator): 
    530 
    531            an existing validator class 
    532 
    533        validators (collections.abc.Mapping): 
    534 
    535            a mapping of new validator callables to extend with, whose 
    536            structure is as in `create`. 
    537 
    538            .. note:: 
    539 
    540                Any validator callables with the same name as an 
    541                existing one will (silently) replace the old validator 
    542                callable entirely, effectively overriding any validation 
    543                done in the "parent" validator class. 
    544 
    545                If you wish to instead extend the behavior of a parent's 
    546                validator callable, delegate and call it directly in 
    547                the new validator function by retrieving it using 
    548                ``OldValidator.VALIDATORS["validation_keyword_name"]``. 
    549 
    550        version (str): 
    551 
    552            a version for the new validator class 
    553 
    554        type_checker (jsonschema.TypeChecker): 
    555 
    556            a type checker, used when applying the :kw:`type` keyword. 
    557 
    558            If unprovided, the type checker of the extended 
    559            `jsonschema.protocols.Validator` will be carried along. 
    560 
    561        format_checker (jsonschema.FormatChecker): 
    562 
    563            a format checker, used when applying the :kw:`format` keyword. 
    564 
    565            If unprovided, the format checker of the extended 
    566            `jsonschema.protocols.Validator` will be carried along. 
    567 
    568    Returns: 
    569 
    570        a new `jsonschema.protocols.Validator` class extending the one 
    571        provided 
    572 
    573    .. note:: Meta Schemas 
    574 
    575        The new validator class will have its parent's meta schema. 
    576 
    577        If you wish to change or extend the meta schema in the new 
    578        validator class, modify ``META_SCHEMA`` directly on the returned 
    579        class. Note that no implicit copying is done, so a copy should 
    580        likely be made before modifying it, in order to not affect the 
    581        old validator. 
    582 
    583    """ 
    584    all_validators = dict(validator.VALIDATORS) 
    585    all_validators.update(validators) 
    586 
    587    if type_checker is None: 
    588        type_checker = validator.TYPE_CHECKER 
    589    if format_checker is None: 
    590        format_checker = validator.FORMAT_CHECKER 
    591    return create( 
    592        meta_schema=validator.META_SCHEMA, 
    593        validators=all_validators, 
    594        version=version, 
    595        type_checker=type_checker, 
    596        format_checker=format_checker, 
    597        id_of=validator.ID_OF, 
    598        applicable_validators=validator._APPLICABLE_VALIDATORS, 
    599    ) 
    600 
    601 
    602Draft3Validator = create( 
    603    meta_schema=SPECIFICATIONS.contents( 
    604        "http://json-schema.org/draft-03/schema#", 
    605    ), 
    606    validators={ 
    607        "$ref": _keywords.ref, 
    608        "additionalItems": _legacy_keywords.additionalItems, 
    609        "additionalProperties": _keywords.additionalProperties, 
    610        "dependencies": _legacy_keywords.dependencies_draft3, 
    611        "disallow": _legacy_keywords.disallow_draft3, 
    612        "divisibleBy": _keywords.multipleOf, 
    613        "enum": _keywords.enum, 
    614        "extends": _legacy_keywords.extends_draft3, 
    615        "format": _keywords.format, 
    616        "items": _legacy_keywords.items_draft3_draft4, 
    617        "maxItems": _keywords.maxItems, 
    618        "maxLength": _keywords.maxLength, 
    619        "maximum": _legacy_keywords.maximum_draft3_draft4, 
    620        "minItems": _keywords.minItems, 
    621        "minLength": _keywords.minLength, 
    622        "minimum": _legacy_keywords.minimum_draft3_draft4, 
    623        "pattern": _keywords.pattern, 
    624        "patternProperties": _keywords.patternProperties, 
    625        "properties": _legacy_keywords.properties_draft3, 
    626        "type": _legacy_keywords.type_draft3, 
    627        "uniqueItems": _keywords.uniqueItems, 
    628    }, 
    629    type_checker=_types.draft3_type_checker, 
    630    format_checker=_format.draft3_format_checker, 
    631    version="draft3", 
    632    id_of=referencing.jsonschema.DRAFT3.id_of, 
    633    applicable_validators=_legacy_keywords.ignore_ref_siblings, 
    634) 
    635 
    636Draft4Validator = create( 
    637    meta_schema=SPECIFICATIONS.contents( 
    638        "http://json-schema.org/draft-04/schema#", 
    639    ), 
    640    validators={ 
    641        "$ref": _keywords.ref, 
    642        "additionalItems": _legacy_keywords.additionalItems, 
    643        "additionalProperties": _keywords.additionalProperties, 
    644        "allOf": _keywords.allOf, 
    645        "anyOf": _keywords.anyOf, 
    646        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 
    647        "enum": _keywords.enum, 
    648        "format": _keywords.format, 
    649        "items": _legacy_keywords.items_draft3_draft4, 
    650        "maxItems": _keywords.maxItems, 
    651        "maxLength": _keywords.maxLength, 
    652        "maxProperties": _keywords.maxProperties, 
    653        "maximum": _legacy_keywords.maximum_draft3_draft4, 
    654        "minItems": _keywords.minItems, 
    655        "minLength": _keywords.minLength, 
    656        "minProperties": _keywords.minProperties, 
    657        "minimum": _legacy_keywords.minimum_draft3_draft4, 
    658        "multipleOf": _keywords.multipleOf, 
    659        "not": _keywords.not_, 
    660        "oneOf": _keywords.oneOf, 
    661        "pattern": _keywords.pattern, 
    662        "patternProperties": _keywords.patternProperties, 
    663        "properties": _keywords.properties, 
    664        "required": _keywords.required, 
    665        "type": _keywords.type, 
    666        "uniqueItems": _keywords.uniqueItems, 
    667    }, 
    668    type_checker=_types.draft4_type_checker, 
    669    format_checker=_format.draft4_format_checker, 
    670    version="draft4", 
    671    id_of=referencing.jsonschema.DRAFT4.id_of, 
    672    applicable_validators=_legacy_keywords.ignore_ref_siblings, 
    673) 
    674 
    675Draft6Validator = create( 
    676    meta_schema=SPECIFICATIONS.contents( 
    677        "http://json-schema.org/draft-06/schema#", 
    678    ), 
    679    validators={ 
    680        "$ref": _keywords.ref, 
    681        "additionalItems": _legacy_keywords.additionalItems, 
    682        "additionalProperties": _keywords.additionalProperties, 
    683        "allOf": _keywords.allOf, 
    684        "anyOf": _keywords.anyOf, 
    685        "const": _keywords.const, 
    686        "contains": _legacy_keywords.contains_draft6_draft7, 
    687        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 
    688        "enum": _keywords.enum, 
    689        "exclusiveMaximum": _keywords.exclusiveMaximum, 
    690        "exclusiveMinimum": _keywords.exclusiveMinimum, 
    691        "format": _keywords.format, 
    692        "items": _legacy_keywords.items_draft6_draft7_draft201909, 
    693        "maxItems": _keywords.maxItems, 
    694        "maxLength": _keywords.maxLength, 
    695        "maxProperties": _keywords.maxProperties, 
    696        "maximum": _keywords.maximum, 
    697        "minItems": _keywords.minItems, 
    698        "minLength": _keywords.minLength, 
    699        "minProperties": _keywords.minProperties, 
    700        "minimum": _keywords.minimum, 
    701        "multipleOf": _keywords.multipleOf, 
    702        "not": _keywords.not_, 
    703        "oneOf": _keywords.oneOf, 
    704        "pattern": _keywords.pattern, 
    705        "patternProperties": _keywords.patternProperties, 
    706        "properties": _keywords.properties, 
    707        "propertyNames": _keywords.propertyNames, 
    708        "required": _keywords.required, 
    709        "type": _keywords.type, 
    710        "uniqueItems": _keywords.uniqueItems, 
    711    }, 
    712    type_checker=_types.draft6_type_checker, 
    713    format_checker=_format.draft6_format_checker, 
    714    version="draft6", 
    715    id_of=referencing.jsonschema.DRAFT6.id_of, 
    716    applicable_validators=_legacy_keywords.ignore_ref_siblings, 
    717) 
    718 
    719Draft7Validator = create( 
    720    meta_schema=SPECIFICATIONS.contents( 
    721        "http://json-schema.org/draft-07/schema#", 
    722    ), 
    723    validators={ 
    724        "$ref": _keywords.ref, 
    725        "additionalItems": _legacy_keywords.additionalItems, 
    726        "additionalProperties": _keywords.additionalProperties, 
    727        "allOf": _keywords.allOf, 
    728        "anyOf": _keywords.anyOf, 
    729        "const": _keywords.const, 
    730        "contains": _legacy_keywords.contains_draft6_draft7, 
    731        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 
    732        "enum": _keywords.enum, 
    733        "exclusiveMaximum": _keywords.exclusiveMaximum, 
    734        "exclusiveMinimum": _keywords.exclusiveMinimum, 
    735        "format": _keywords.format, 
    736        "if": _keywords.if_, 
    737        "items": _legacy_keywords.items_draft6_draft7_draft201909, 
    738        "maxItems": _keywords.maxItems, 
    739        "maxLength": _keywords.maxLength, 
    740        "maxProperties": _keywords.maxProperties, 
    741        "maximum": _keywords.maximum, 
    742        "minItems": _keywords.minItems, 
    743        "minLength": _keywords.minLength, 
    744        "minProperties": _keywords.minProperties, 
    745        "minimum": _keywords.minimum, 
    746        "multipleOf": _keywords.multipleOf, 
    747        "not": _keywords.not_, 
    748        "oneOf": _keywords.oneOf, 
    749        "pattern": _keywords.pattern, 
    750        "patternProperties": _keywords.patternProperties, 
    751        "properties": _keywords.properties, 
    752        "propertyNames": _keywords.propertyNames, 
    753        "required": _keywords.required, 
    754        "type": _keywords.type, 
    755        "uniqueItems": _keywords.uniqueItems, 
    756    }, 
    757    type_checker=_types.draft7_type_checker, 
    758    format_checker=_format.draft7_format_checker, 
    759    version="draft7", 
    760    id_of=referencing.jsonschema.DRAFT7.id_of, 
    761    applicable_validators=_legacy_keywords.ignore_ref_siblings, 
    762) 
    763 
    764Draft201909Validator = create( 
    765    meta_schema=SPECIFICATIONS.contents( 
    766        "https://json-schema.org/draft/2019-09/schema", 
    767    ), 
    768    validators={ 
    769        "$recursiveRef": _legacy_keywords.recursiveRef, 
    770        "$ref": _keywords.ref, 
    771        "additionalItems": _legacy_keywords.additionalItems, 
    772        "additionalProperties": _keywords.additionalProperties, 
    773        "allOf": _keywords.allOf, 
    774        "anyOf": _keywords.anyOf, 
    775        "const": _keywords.const, 
    776        "contains": _keywords.contains, 
    777        "dependentRequired": _keywords.dependentRequired, 
    778        "dependentSchemas": _keywords.dependentSchemas, 
    779        "enum": _keywords.enum, 
    780        "exclusiveMaximum": _keywords.exclusiveMaximum, 
    781        "exclusiveMinimum": _keywords.exclusiveMinimum, 
    782        "format": _keywords.format, 
    783        "if": _keywords.if_, 
    784        "items": _legacy_keywords.items_draft6_draft7_draft201909, 
    785        "maxItems": _keywords.maxItems, 
    786        "maxLength": _keywords.maxLength, 
    787        "maxProperties": _keywords.maxProperties, 
    788        "maximum": _keywords.maximum, 
    789        "minItems": _keywords.minItems, 
    790        "minLength": _keywords.minLength, 
    791        "minProperties": _keywords.minProperties, 
    792        "minimum": _keywords.minimum, 
    793        "multipleOf": _keywords.multipleOf, 
    794        "not": _keywords.not_, 
    795        "oneOf": _keywords.oneOf, 
    796        "pattern": _keywords.pattern, 
    797        "patternProperties": _keywords.patternProperties, 
    798        "properties": _keywords.properties, 
    799        "propertyNames": _keywords.propertyNames, 
    800        "required": _keywords.required, 
    801        "type": _keywords.type, 
    802        "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 
    803        "unevaluatedProperties": ( 
    804            _legacy_keywords.unevaluatedProperties_draft2019 
    805        ), 
    806        "uniqueItems": _keywords.uniqueItems, 
    807    }, 
    808    type_checker=_types.draft201909_type_checker, 
    809    format_checker=_format.draft201909_format_checker, 
    810    version="draft2019-09", 
    811) 
    812 
    813Draft202012Validator = create( 
    814    meta_schema=SPECIFICATIONS.contents( 
    815        "https://json-schema.org/draft/2020-12/schema", 
    816    ), 
    817    validators={ 
    818        "$dynamicRef": _keywords.dynamicRef, 
    819        "$ref": _keywords.ref, 
    820        "additionalProperties": _keywords.additionalProperties, 
    821        "allOf": _keywords.allOf, 
    822        "anyOf": _keywords.anyOf, 
    823        "const": _keywords.const, 
    824        "contains": _keywords.contains, 
    825        "dependentRequired": _keywords.dependentRequired, 
    826        "dependentSchemas": _keywords.dependentSchemas, 
    827        "enum": _keywords.enum, 
    828        "exclusiveMaximum": _keywords.exclusiveMaximum, 
    829        "exclusiveMinimum": _keywords.exclusiveMinimum, 
    830        "format": _keywords.format, 
    831        "if": _keywords.if_, 
    832        "items": _keywords.items, 
    833        "maxItems": _keywords.maxItems, 
    834        "maxLength": _keywords.maxLength, 
    835        "maxProperties": _keywords.maxProperties, 
    836        "maximum": _keywords.maximum, 
    837        "minItems": _keywords.minItems, 
    838        "minLength": _keywords.minLength, 
    839        "minProperties": _keywords.minProperties, 
    840        "minimum": _keywords.minimum, 
    841        "multipleOf": _keywords.multipleOf, 
    842        "not": _keywords.not_, 
    843        "oneOf": _keywords.oneOf, 
    844        "pattern": _keywords.pattern, 
    845        "patternProperties": _keywords.patternProperties, 
    846        "prefixItems": _keywords.prefixItems, 
    847        "properties": _keywords.properties, 
    848        "propertyNames": _keywords.propertyNames, 
    849        "required": _keywords.required, 
    850        "type": _keywords.type, 
    851        "unevaluatedItems": _keywords.unevaluatedItems, 
    852        "unevaluatedProperties": _keywords.unevaluatedProperties, 
    853        "uniqueItems": _keywords.uniqueItems, 
    854    }, 
    855    type_checker=_types.draft202012_type_checker, 
    856    format_checker=_format.draft202012_format_checker, 
    857    version="draft2020-12", 
    858) 
    859 
    860_LATEST_VERSION: type[Validator] = Draft202012Validator 
    861 
    862 
    863class _RefResolver: 
    864    """ 
    865    Resolve JSON References. 
    866 
    867    Arguments: 
    868 
    869        base_uri (str): 
    870 
    871            The URI of the referring document 
    872 
    873        referrer: 
    874 
    875            The actual referring document 
    876 
    877        store (dict): 
    878 
    879            A mapping from URIs to documents to cache 
    880 
    881        cache_remote (bool): 
    882 
    883            Whether remote refs should be cached after first resolution 
    884 
    885        handlers (dict): 
    886 
    887            A mapping from URI schemes to functions that should be used 
    888            to retrieve them 
    889 
    890        urljoin_cache (:func:`functools.lru_cache`): 
    891 
    892            A cache that will be used for caching the results of joining 
    893            the resolution scope to subscopes. 
    894 
    895        remote_cache (:func:`functools.lru_cache`): 
    896 
    897            A cache that will be used for caching the results of 
    898            resolved remote URLs. 
    899 
    900    Attributes: 
    901 
    902        cache_remote (bool): 
    903 
    904            Whether remote refs should be cached after first resolution 
    905 
    906    .. deprecated:: v4.18.0 
    907 
    908        ``RefResolver`` has been deprecated in favor of `referencing`. 
    909 
    910    """ 
    911 
    912    _DEPRECATION_MESSAGE = ( 
    913        "jsonschema.RefResolver is deprecated as of v4.18.0, in favor of the " 
    914        "https://github.com/python-jsonschema/referencing library, which " 
    915        "provides more compliant referencing behavior as well as more " 
    916        "flexible APIs for customization. A future release will remove " 
    917        "RefResolver. Please file a feature request (on referencing) if you " 
    918        "are missing an API for the kind of customization you need." 
    919    ) 
    920 
    921    def __init__( 
    922        self, 
    923        base_uri, 
    924        referrer, 
    925        store=HashTrieMap(), 
    926        cache_remote=True, 
    927        handlers=(), 
    928        urljoin_cache=None, 
    929        remote_cache=None, 
    930    ): 
    931        if urljoin_cache is None: 
    932            urljoin_cache = lru_cache(1024)(urljoin) 
    933        if remote_cache is None: 
    934            remote_cache = lru_cache(1024)(self.resolve_from_url) 
    935 
    936        self.referrer = referrer 
    937        self.cache_remote = cache_remote 
    938        self.handlers = dict(handlers) 
    939 
    940        self._scopes_stack = [base_uri] 
    941 
    942        self.store = _utils.URIDict( 
    943            (uri, each.contents) for uri, each in SPECIFICATIONS.items() 
    944        ) 
    945        self.store.update( 
    946            (id, each.META_SCHEMA) for id, each in _META_SCHEMAS.items() 
    947        ) 
    948        self.store.update(store) 
    949        self.store.update( 
    950            (schema["$id"], schema) 
    951            for schema in store.values() 
    952            if isinstance(schema, Mapping) and "$id" in schema 
    953        ) 
    954        self.store[base_uri] = referrer 
    955 
    956        self._urljoin_cache = urljoin_cache 
    957        self._remote_cache = remote_cache 
    958 
    959    @classmethod 
    960    def from_schema(  # noqa: D417 
    961        cls, 
    962        schema, 
    963        id_of=referencing.jsonschema.DRAFT202012.id_of, 
    964        *args, 
    965        **kwargs, 
    966    ): 
    967        """ 
    968        Construct a resolver from a JSON schema object. 
    969 
    970        Arguments: 
    971 
    972            schema: 
    973 
    974                the referring schema 
    975 
    976        Returns: 
    977 
    978            `_RefResolver` 
    979 
    980        """ 
    981        return cls(base_uri=id_of(schema) or "", referrer=schema, *args, **kwargs)  # noqa: B026, E501 
    982 
    983    def push_scope(self, scope): 
    984        """ 
    985        Enter a given sub-scope. 
    986 
    987        Treats further dereferences as being performed underneath the 
    988        given scope. 
    989        """ 
    990        self._scopes_stack.append( 
    991            self._urljoin_cache(self.resolution_scope, scope), 
    992        ) 
    993 
    994    def pop_scope(self): 
    995        """ 
    996        Exit the most recent entered scope. 
    997 
    998        Treats further dereferences as being performed underneath the 
    999        original scope. 
    1000 
    1001        Don't call this method more times than `push_scope` has been 
    1002        called. 
    1003        """ 
    1004        try: 
    1005            self._scopes_stack.pop() 
    1006        except IndexError: 
    1007            raise exceptions._RefResolutionError( 
    1008                "Failed to pop the scope from an empty stack. " 
    1009                "`pop_scope()` should only be called once for every " 
    1010                "`push_scope()`", 
    1011            ) from None 
    1012 
    1013    @property 
    1014    def resolution_scope(self): 
    1015        """ 
    1016        Retrieve the current resolution scope. 
    1017        """ 
    1018        return self._scopes_stack[-1] 
    1019 
    1020    @property 
    1021    def base_uri(self): 
    1022        """ 
    1023        Retrieve the current base URI, not including any fragment. 
    1024        """ 
    1025        uri, _ = urldefrag(self.resolution_scope) 
    1026        return uri 
    1027 
    1028    @contextlib.contextmanager 
    1029    def in_scope(self, scope): 
    1030        """ 
    1031        Temporarily enter the given scope for the duration of the context. 
    1032 
    1033        .. deprecated:: v4.0.0 
    1034        """ 
    1035        warnings.warn( 
    1036            "jsonschema.RefResolver.in_scope is deprecated and will be " 
    1037            "removed in a future release.", 
    1038            DeprecationWarning, 
    1039            stacklevel=3, 
    1040        ) 
    1041        self.push_scope(scope) 
    1042        try: 
    1043            yield 
    1044        finally: 
    1045            self.pop_scope() 
    1046 
    1047    @contextlib.contextmanager 
    1048    def resolving(self, ref): 
    1049        """ 
    1050        Resolve the given ``ref`` and enter its resolution scope. 
    1051 
    1052        Exits the scope on exit of this context manager. 
    1053 
    1054        Arguments: 
    1055 
    1056            ref (str): 
    1057 
    1058                The reference to resolve 
    1059 
    1060        """ 
    1061        url, resolved = self.resolve(ref) 
    1062        self.push_scope(url) 
    1063        try: 
    1064            yield resolved 
    1065        finally: 
    1066            self.pop_scope() 
    1067 
    1068    def _find_in_referrer(self, key): 
    1069        return self._get_subschemas_cache()[key] 
    1070 
    1071    @lru_cache  # noqa: B019 
    1072    def _get_subschemas_cache(self): 
    1073        cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS} 
    1074        for keyword, subschema in _search_schema( 
    1075            self.referrer, _match_subschema_keywords, 
    1076        ): 
    1077            cache[keyword].append(subschema) 
    1078        return cache 
    1079 
    1080    @lru_cache  # noqa: B019 
    1081    def _find_in_subschemas(self, url): 
    1082        subschemas = self._get_subschemas_cache()["$id"] 
    1083        if not subschemas: 
    1084            return None 
    1085        uri, fragment = urldefrag(url) 
    1086        for subschema in subschemas: 
    1087            id = subschema["$id"] 
    1088            if not isinstance(id, str): 
    1089                continue 
    1090            target_uri = self._urljoin_cache(self.resolution_scope, id) 
    1091            if target_uri.rstrip("/") == uri.rstrip("/"): 
    1092                if fragment: 
    1093                    subschema = self.resolve_fragment(subschema, fragment) 
    1094                self.store[url] = subschema 
    1095                return url, subschema 
    1096        return None 
    1097 
    1098    def resolve(self, ref): 
    1099        """ 
    1100        Resolve the given reference. 
    1101        """ 
    1102        url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/") 
    1103 
    1104        match = self._find_in_subschemas(url) 
    1105        if match is not None: 
    1106            return match 
    1107 
    1108        return url, self._remote_cache(url) 
    1109 
    1110    def resolve_from_url(self, url): 
    1111        """ 
    1112        Resolve the given URL. 
    1113        """ 
    1114        url, fragment = urldefrag(url) 
    1115        if not url: 
    1116            url = self.base_uri 
    1117 
    1118        try: 
    1119            document = self.store[url] 
    1120        except KeyError: 
    1121            try: 
    1122                document = self.resolve_remote(url) 
    1123            except Exception as exc: 
    1124                raise exceptions._RefResolutionError(exc) from exc 
    1125 
    1126        return self.resolve_fragment(document, fragment) 
    1127 
    1128    def resolve_fragment(self, document, fragment): 
    1129        """ 
    1130        Resolve a ``fragment`` within the referenced ``document``. 
    1131 
    1132        Arguments: 
    1133 
    1134            document: 
    1135 
    1136                The referent document 
    1137 
    1138            fragment (str): 
    1139 
    1140                a URI fragment to resolve within it 
    1141 
    1142        """ 
    1143        fragment = fragment.lstrip("/") 
    1144 
    1145        if not fragment: 
    1146            return document 
    1147 
    1148        if document is self.referrer: 
    1149            find = self._find_in_referrer 
    1150        else: 
    1151 
    1152            def find(key): 
    1153                yield from _search_schema(document, _match_keyword(key)) 
    1154 
    1155        for keyword in ["$anchor", "$dynamicAnchor"]: 
    1156            for subschema in find(keyword): 
    1157                if fragment == subschema[keyword]: 
    1158                    return subschema 
    1159        for keyword in ["id", "$id"]: 
    1160            for subschema in find(keyword): 
    1161                if "#" + fragment == subschema[keyword]: 
    1162                    return subschema 
    1163 
    1164        # Resolve via path 
    1165        parts = unquote(fragment).split("/") if fragment else [] 
    1166        for part in parts: 
    1167            part = part.replace("~1", "/").replace("~0", "~") 
    1168 
    1169            if isinstance(document, Sequence): 
    1170                try:  # noqa: SIM105 
    1171                    part = int(part) 
    1172                except ValueError: 
    1173                    pass 
    1174            try: 
    1175                document = document[part] 
    1176            except (TypeError, LookupError) as err: 
    1177                raise exceptions._RefResolutionError( 
    1178                    f"Unresolvable JSON pointer: {fragment!r}", 
    1179                ) from err 
    1180 
    1181        return document 
    1182 
    1183    def resolve_remote(self, uri): 
    1184        """ 
    1185        Resolve a remote ``uri``. 
    1186 
    1187        If called directly, does not check the store first, but after 
    1188        retrieving the document at the specified URI it will be saved in 
    1189        the store if :attr:`cache_remote` is True. 
    1190 
    1191        .. note:: 
    1192 
    1193            If the requests_ library is present, ``jsonschema`` will use it to 
    1194            request the remote ``uri``, so that the correct encoding is 
    1195            detected and used. 
    1196 
    1197            If it isn't, or if the scheme of the ``uri`` is not ``http`` or 
    1198            ``https``, UTF-8 is assumed. 
    1199 
    1200        Arguments: 
    1201 
    1202            uri (str): 
    1203 
    1204                The URI to resolve 
    1205 
    1206        Returns: 
    1207 
    1208            The retrieved document 
    1209 
    1210        .. _requests: https://pypi.org/project/requests/ 
    1211 
    1212        """ 
    1213        try: 
    1214            import requests 
    1215        except ImportError: 
    1216            requests = None 
    1217 
    1218        scheme = urlsplit(uri).scheme 
    1219 
    1220        if scheme in self.handlers: 
    1221            result = self.handlers[scheme](uri) 
    1222        elif scheme in ["http", "https"] and requests: 
    1223            # Requests has support for detecting the correct encoding of 
    1224            # json over http 
    1225            result = requests.get(uri).json() 
    1226        else: 
    1227            # Otherwise, pass off to urllib and assume utf-8 
    1228            with urlopen(uri) as url:  # noqa: S310 
    1229                result = json.loads(url.read().decode("utf-8")) 
    1230 
    1231        if self.cache_remote: 
    1232            self.store[uri] = result 
    1233        return result 
    1234 
    1235 
    1236_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor") 
    1237 
    1238 
    1239def _match_keyword(keyword): 
    1240 
    1241    def matcher(value): 
    1242        if keyword in value: 
    1243            yield value 
    1244 
    1245    return matcher 
    1246 
    1247 
    1248def _match_subschema_keywords(value): 
    1249    for keyword in _SUBSCHEMAS_KEYWORDS: 
    1250        if keyword in value: 
    1251            yield keyword, value 
    1252 
    1253 
    1254def _search_schema(schema, matcher): 
    1255    """Breadth-first search routine.""" 
    1256    values = deque([schema]) 
    1257    while values: 
    1258        value = values.pop() 
    1259        if not isinstance(value, dict): 
    1260            continue 
    1261        yield from matcher(value) 
    1262        values.extendleft(value.values()) 
    1263 
    1264 
    1265def validate(instance, schema, cls=None, *args, **kwargs):  # noqa: D417 
    1266    """ 
    1267    Validate an instance under the given schema. 
    1268 
    1269        >>> validate([2, 3, 4], {"maxItems": 2}) 
    1270        Traceback (most recent call last): 
    1271            ... 
    1272        ValidationError: [2, 3, 4] is too long 
    1273 
    1274    :func:`~jsonschema.validators.validate` will first verify that the 
    1275    provided schema is itself valid, since not doing so can lead to less 
    1276    obvious error messages and fail in less obvious or consistent ways. 
    1277 
    1278    If you know you have a valid schema already, especially 
    1279    if you intend to validate multiple instances with 
    1280    the same schema, you likely would prefer using the 
    1281    `jsonschema.protocols.Validator.validate` method directly on a 
    1282    specific validator (e.g. ``Draft202012Validator.validate``). 
    1283 
    1284 
    1285    Arguments: 
    1286 
    1287        instance: 
    1288 
    1289            The instance to validate 
    1290 
    1291        schema: 
    1292 
    1293            The schema to validate with 
    1294 
    1295        cls (jsonschema.protocols.Validator): 
    1296 
    1297            The class that will be used to validate the instance. 
    1298 
    1299    If the ``cls`` argument is not provided, two things will happen 
    1300    in accordance with the specification. First, if the schema has a 
    1301    :kw:`$schema` keyword containing a known meta-schema [#]_ then the 
    1302    proper validator will be used. The specification recommends that 
    1303    all schemas contain :kw:`$schema` properties for this reason. If no 
    1304    :kw:`$schema` property is found, the default validator class is the 
    1305    latest released draft. 
    1306 
    1307    Any other provided positional and keyword arguments will be passed 
    1308    on when instantiating the ``cls``. 
    1309 
    1310    Raises: 
    1311 
    1312        `jsonschema.exceptions.ValidationError`: 
    1313 
    1314            if the instance is invalid 
    1315 
    1316        `jsonschema.exceptions.SchemaError`: 
    1317 
    1318            if the schema itself is invalid 
    1319 
    1320    .. rubric:: Footnotes 
    1321    .. [#] known by a validator registered with 
    1322        `jsonschema.validators.validates` 
    1323 
    1324    """ 
    1325    if cls is None: 
    1326        cls = validator_for(schema) 
    1327 
    1328    cls.check_schema(schema) 
    1329    validator = cls(schema, *args, **kwargs) 
    1330    error = exceptions.best_match(validator.iter_errors(instance)) 
    1331    if error is not None: 
    1332        raise error 
    1333 
    1334 
    1335def validator_for( 
    1336    schema, 
    1337    default: type[Validator] | _utils.Unset = _UNSET, 
    1338) -> type[Validator]: 
    1339    """ 
    1340    Retrieve the validator class appropriate for validating the given schema. 
    1341 
    1342    Uses the :kw:`$schema` keyword that should be present in the given 
    1343    schema to look up the appropriate validator class. 
    1344 
    1345    Arguments: 
    1346 
    1347        schema (collections.abc.Mapping or bool): 
    1348 
    1349            the schema to look at 
    1350 
    1351        default: 
    1352 
    1353            the default to return if the appropriate validator class 
    1354            cannot be determined. 
    1355 
    1356            If unprovided, the default is to return the latest supported 
    1357            draft. 
    1358 
    1359    Examples: 
    1360 
    1361        The :kw:`$schema` JSON Schema keyword will control which validator 
    1362        class is returned: 
    1363 
    1364        >>> schema = { 
    1365        ...     "$schema": "https://json-schema.org/draft/2020-12/schema", 
    1366        ...     "type": "integer", 
    1367        ... } 
    1368        >>> jsonschema.validators.validator_for(schema) 
    1369        <class 'jsonschema.validators.Draft202012Validator'> 
    1370 
    1371 
    1372        Here, a draft 7 schema instead will return the draft 7 validator: 
    1373 
    1374        >>> schema = { 
    1375        ...     "$schema": "http://json-schema.org/draft-07/schema#", 
    1376        ...     "type": "integer", 
    1377        ... } 
    1378        >>> jsonschema.validators.validator_for(schema) 
    1379        <class 'jsonschema.validators.Draft7Validator'> 
    1380 
    1381 
    1382        Schemas with no ``$schema`` keyword will fallback to the default 
    1383        argument: 
    1384 
    1385        >>> schema = {"type": "integer"} 
    1386        >>> jsonschema.validators.validator_for( 
    1387        ...     schema, default=Draft7Validator, 
    1388        ... ) 
    1389        <class 'jsonschema.validators.Draft7Validator'> 
    1390 
    1391        or if none is provided, to the latest version supported. 
    1392        Always including the keyword when authoring schemas is highly 
    1393        recommended. 
    1394 
    1395    """ 
    1396    DefaultValidator = _LATEST_VERSION if default is _UNSET else default 
    1397 
    1398    if schema is True or schema is False or "$schema" not in schema: 
    1399        return DefaultValidator  # type: ignore[return-value] 
    1400    if schema["$schema"] not in _META_SCHEMAS and default is _UNSET: 
    1401        warn( 
    1402            ( 
    1403                "The metaschema specified by $schema was not found. " 
    1404                "Using the latest draft to validate, but this will raise " 
    1405                "an error in the future." 
    1406            ), 
    1407            DeprecationWarning, 
    1408            stacklevel=2, 
    1409        ) 
    1410    return _META_SCHEMAS.get(schema["$schema"], DefaultValidator)