about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/jsonschema/validators.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/jsonschema/validators.py')
-rw-r--r--.venv/lib/python3.12/site-packages/jsonschema/validators.py1410
1 files changed, 1410 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/jsonschema/validators.py b/.venv/lib/python3.12/site-packages/jsonschema/validators.py
new file mode 100644
index 00000000..85c39160
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/jsonschema/validators.py
@@ -0,0 +1,1410 @@
+"""
+Creation and extension of validators, with implementations for existing drafts.
+"""
+from __future__ import annotations
+
+from collections import deque
+from collections.abc import Iterable, Mapping, Sequence
+from functools import lru_cache
+from operator import methodcaller
+from typing import TYPE_CHECKING
+from urllib.parse import unquote, urldefrag, urljoin, urlsplit
+from urllib.request import urlopen
+from warnings import warn
+import contextlib
+import json
+import reprlib
+import warnings
+
+from attrs import define, field, fields
+from jsonschema_specifications import REGISTRY as SPECIFICATIONS
+from rpds import HashTrieMap
+import referencing.exceptions
+import referencing.jsonschema
+
+from jsonschema import (
+    _format,
+    _keywords,
+    _legacy_keywords,
+    _types,
+    _typing,
+    _utils,
+    exceptions,
+)
+
+if TYPE_CHECKING:
+    from jsonschema.protocols import Validator
+
+_UNSET = _utils.Unset()
+
+_VALIDATORS: dict[str, Validator] = {}
+_META_SCHEMAS = _utils.URIDict()
+
+
+def __getattr__(name):
+    if name == "ErrorTree":
+        warnings.warn(
+            "Importing ErrorTree from jsonschema.validators is deprecated. "
+            "Instead import it from jsonschema.exceptions.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        from jsonschema.exceptions import ErrorTree
+        return ErrorTree
+    elif name == "validators":
+        warnings.warn(
+            "Accessing jsonschema.validators.validators is deprecated. "
+            "Use jsonschema.validators.validator_for with a given schema.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return _VALIDATORS
+    elif name == "meta_schemas":
+        warnings.warn(
+            "Accessing jsonschema.validators.meta_schemas is deprecated. "
+            "Use jsonschema.validators.validator_for with a given schema.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return _META_SCHEMAS
+    elif name == "RefResolver":
+        warnings.warn(
+            _RefResolver._DEPRECATION_MESSAGE,
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return _RefResolver
+    raise AttributeError(f"module {__name__} has no attribute {name}")
+
+
+def validates(version):
+    """
+    Register the decorated validator for a ``version`` of the specification.
+
+    Registered validators and their meta schemas will be considered when
+    parsing :kw:`$schema` keywords' URIs.
+
+    Arguments:
+
+        version (str):
+
+            An identifier to use as the version's name
+
+    Returns:
+
+        collections.abc.Callable:
+
+            a class decorator to decorate the validator with the version
+
+    """
+
+    def _validates(cls):
+        _VALIDATORS[version] = cls
+        meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
+        _META_SCHEMAS[meta_schema_id] = cls
+        return cls
+    return _validates
+
+
+def _warn_for_remote_retrieve(uri: str):
+    from urllib.request import Request, urlopen
+    headers = {"User-Agent": "python-jsonschema (deprecated $ref resolution)"}
+    request = Request(uri, headers=headers)  # noqa: S310
+    with urlopen(request) as response:  # noqa: S310
+        warnings.warn(
+            "Automatically retrieving remote references can be a security "
+            "vulnerability and is discouraged by the JSON Schema "
+            "specifications. Relying on this behavior is deprecated "
+            "and will shortly become an error. If you are sure you want to "
+            "remotely retrieve your reference and that it is safe to do so, "
+            "you can find instructions for doing so via referencing.Registry "
+            "in the referencing documentation "
+            "(https://referencing.readthedocs.org).",
+            DeprecationWarning,
+            stacklevel=9,  # Ha ha ha ha magic numbers :/
+        )
+        return referencing.Resource.from_contents(
+            json.load(response),
+            default_specification=referencing.jsonschema.DRAFT202012,
+        )
+
+
+_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine(
+    referencing.Registry(retrieve=_warn_for_remote_retrieve),  # type: ignore[call-arg]
+)
+
+
+def create(
+    meta_schema: referencing.jsonschema.ObjectSchema,
+    validators: (
+        Mapping[str, _typing.SchemaKeywordValidator]
+        | Iterable[tuple[str, _typing.SchemaKeywordValidator]]
+    ) = (),
+    version: str | None = None,
+    type_checker: _types.TypeChecker = _types.draft202012_type_checker,
+    format_checker: _format.FormatChecker = _format.draft202012_format_checker,
+    id_of: _typing.id_of = referencing.jsonschema.DRAFT202012.id_of,
+    applicable_validators: _typing.ApplicableValidators = methodcaller(
+        "items",
+    ),
+):
+    """
+    Create a new validator class.
+
+    Arguments:
+
+        meta_schema:
+
+            the meta schema for the new validator class
+
+        validators:
+
+            a mapping from names to callables, where each callable will
+            validate the schema property with the given name.
+
+            Each callable should take 4 arguments:
+
+                1. a validator instance,
+                2. the value of the property being validated within the
+                   instance
+                3. the instance
+                4. the schema
+
+        version:
+
+            an identifier for the version that this validator class will
+            validate. If provided, the returned validator class will
+            have its ``__name__`` set to include the version, and also
+            will have `jsonschema.validators.validates` automatically
+            called for the given version.
+
+        type_checker:
+
+            a type checker, used when applying the :kw:`type` keyword.
+
+            If unprovided, a `jsonschema.TypeChecker` will be created
+            with a set of default types typical of JSON Schema drafts.
+
+        format_checker:
+
+            a format checker, used when applying the :kw:`format` keyword.
+
+            If unprovided, a `jsonschema.FormatChecker` will be created
+            with a set of default formats typical of JSON Schema drafts.
+
+        id_of:
+
+            A function that given a schema, returns its ID.
+
+        applicable_validators:
+
+            A function that, given a schema, returns the list of
+            applicable schema keywords and associated values
+            which will be used to validate the instance.
+            This is mostly used to support pre-draft 7 versions of JSON Schema
+            which specified behavior around ignoring keywords if they were
+            siblings of a ``$ref`` keyword. If you're not attempting to
+            implement similar behavior, you can typically ignore this argument
+            and leave it at its default.
+
+    Returns:
+
+        a new `jsonschema.protocols.Validator` class
+
+    """
+    # preemptively don't shadow the `Validator.format_checker` local
+    format_checker_arg = format_checker
+
+    specification = referencing.jsonschema.specification_with(
+        dialect_id=id_of(meta_schema) or "urn:unknown-dialect",
+        default=referencing.Specification.OPAQUE,
+    )
+
+    @define
+    class Validator:
+
+        VALIDATORS = dict(validators)  # noqa: RUF012
+        META_SCHEMA = dict(meta_schema)  # noqa: RUF012
+        TYPE_CHECKER = type_checker
+        FORMAT_CHECKER = format_checker_arg
+        ID_OF = staticmethod(id_of)
+
+        _APPLICABLE_VALIDATORS = applicable_validators
+        _validators = field(init=False, repr=False, eq=False)
+
+        schema: referencing.jsonschema.Schema = field(repr=reprlib.repr)
+        _ref_resolver = field(default=None, repr=False, alias="resolver")
+        format_checker: _format.FormatChecker | None = field(default=None)
+        # TODO: include new meta-schemas added at runtime
+        _registry: referencing.jsonschema.SchemaRegistry = field(
+            default=_REMOTE_WARNING_REGISTRY,
+            kw_only=True,
+            repr=False,
+        )
+        _resolver = field(
+            alias="_resolver",
+            default=None,
+            kw_only=True,
+            repr=False,
+        )
+
+        def __init_subclass__(cls):
+            warnings.warn(
+                (
+                    "Subclassing validator classes is not intended to "
+                    "be part of their public API. A future version "
+                    "will make doing so an error, as the behavior of "
+                    "subclasses isn't guaranteed to stay the same "
+                    "between releases of jsonschema. Instead, prefer "
+                    "composition of validators, wrapping them in an object "
+                    "owned entirely by the downstream library."
+                ),
+                DeprecationWarning,
+                stacklevel=2,
+            )
+
+            def evolve(self, **changes):
+                cls = self.__class__
+                schema = changes.setdefault("schema", self.schema)
+                NewValidator = validator_for(schema, default=cls)
+
+                for field in fields(cls):  # noqa: F402
+                    if not field.init:
+                        continue
+                    attr_name = field.name
+                    init_name = field.alias
+                    if init_name not in changes:
+                        changes[init_name] = getattr(self, attr_name)
+
+                return NewValidator(**changes)
+
+            cls.evolve = evolve
+
+        def __attrs_post_init__(self):
+            if self._resolver is None:
+                registry = self._registry
+                if registry is not _REMOTE_WARNING_REGISTRY:
+                    registry = SPECIFICATIONS.combine(registry)
+                resource = specification.create_resource(self.schema)
+                self._resolver = registry.resolver_with_root(resource)
+
+            if self.schema is True or self.schema is False:
+                self._validators = []
+            else:
+                self._validators = [
+                    (self.VALIDATORS[k], k, v)
+                    for k, v in applicable_validators(self.schema)
+                    if k in self.VALIDATORS
+                ]
+
+            # REMOVEME: Legacy ref resolution state management.
+            push_scope = getattr(self._ref_resolver, "push_scope", None)
+            if push_scope is not None:
+                id = id_of(self.schema)
+                if id is not None:
+                    push_scope(id)
+
+        @classmethod
+        def check_schema(cls, schema, format_checker=_UNSET):
+            Validator = validator_for(cls.META_SCHEMA, default=cls)
+            if format_checker is _UNSET:
+                format_checker = Validator.FORMAT_CHECKER
+            validator = Validator(
+                schema=cls.META_SCHEMA,
+                format_checker=format_checker,
+            )
+            for error in validator.iter_errors(schema):
+                raise exceptions.SchemaError.create_from(error)
+
+        @property
+        def resolver(self):
+            warnings.warn(
+                (
+                    f"Accessing {self.__class__.__name__}.resolver is "
+                    "deprecated as of v4.18.0, in favor of the "
+                    "https://github.com/python-jsonschema/referencing "
+                    "library, which provides more compliant referencing "
+                    "behavior as well as more flexible APIs for "
+                    "customization."
+                ),
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            if self._ref_resolver is None:
+                self._ref_resolver = _RefResolver.from_schema(
+                    self.schema,
+                    id_of=id_of,
+                )
+            return self._ref_resolver
+
+        def evolve(self, **changes):
+            schema = changes.setdefault("schema", self.schema)
+            NewValidator = validator_for(schema, default=self.__class__)
+
+            for (attr_name, init_name) in evolve_fields:
+                if init_name not in changes:
+                    changes[init_name] = getattr(self, attr_name)
+
+            return NewValidator(**changes)
+
+        def iter_errors(self, instance, _schema=None):
+            if _schema is not None:
+                warnings.warn(
+                    (
+                        "Passing a schema to Validator.iter_errors "
+                        "is deprecated and will be removed in a future "
+                        "release. Call validator.evolve(schema=new_schema)."
+                        "iter_errors(...) instead."
+                    ),
+                    DeprecationWarning,
+                    stacklevel=2,
+                )
+                validators = [
+                    (self.VALIDATORS[k], k, v)
+                    for k, v in applicable_validators(_schema)
+                    if k in self.VALIDATORS
+                ]
+            else:
+                _schema, validators = self.schema, self._validators
+
+            if _schema is True:
+                return
+            elif _schema is False:
+                yield exceptions.ValidationError(
+                    f"False schema does not allow {instance!r}",
+                    validator=None,
+                    validator_value=None,
+                    instance=instance,
+                    schema=_schema,
+                )
+                return
+
+            for validator, k, v in validators:
+                errors = validator(self, v, instance, _schema) or ()
+                for error in errors:
+                    # set details if not already set by the called fn
+                    error._set(
+                        validator=k,
+                        validator_value=v,
+                        instance=instance,
+                        schema=_schema,
+                        type_checker=self.TYPE_CHECKER,
+                    )
+                    if k not in {"if", "$ref"}:
+                        error.schema_path.appendleft(k)
+                    yield error
+
+        def descend(
+            self,
+            instance,
+            schema,
+            path=None,
+            schema_path=None,
+            resolver=None,
+        ):
+            if schema is True:
+                return
+            elif schema is False:
+                yield exceptions.ValidationError(
+                    f"False schema does not allow {instance!r}",
+                    validator=None,
+                    validator_value=None,
+                    instance=instance,
+                    schema=schema,
+                )
+                return
+
+            if self._ref_resolver is not None:
+                evolved = self.evolve(schema=schema)
+            else:
+                if resolver is None:
+                    resolver = self._resolver.in_subresource(
+                        specification.create_resource(schema),
+                    )
+                evolved = self.evolve(schema=schema, _resolver=resolver)
+
+            for k, v in applicable_validators(schema):
+                validator = evolved.VALIDATORS.get(k)
+                if validator is None:
+                    continue
+
+                errors = validator(evolved, v, instance, schema) or ()
+                for error in errors:
+                    # set details if not already set by the called fn
+                    error._set(
+                        validator=k,
+                        validator_value=v,
+                        instance=instance,
+                        schema=schema,
+                        type_checker=evolved.TYPE_CHECKER,
+                    )
+                    if k not in {"if", "$ref"}:
+                        error.schema_path.appendleft(k)
+                    if path is not None:
+                        error.path.appendleft(path)
+                    if schema_path is not None:
+                        error.schema_path.appendleft(schema_path)
+                    yield error
+
+        def validate(self, *args, **kwargs):
+            for error in self.iter_errors(*args, **kwargs):
+                raise error
+
+        def is_type(self, instance, type):
+            try:
+                return self.TYPE_CHECKER.is_type(instance, type)
+            except exceptions.UndefinedTypeCheck:
+                exc = exceptions.UnknownType(type, instance, self.schema)
+                raise exc from None
+
+        def _validate_reference(self, ref, instance):
+            if self._ref_resolver is None:
+                try:
+                    resolved = self._resolver.lookup(ref)
+                except referencing.exceptions.Unresolvable as err:
+                    raise exceptions._WrappedReferencingError(err) from err
+
+                return self.descend(
+                    instance,
+                    resolved.contents,
+                    resolver=resolved.resolver,
+                )
+            else:
+                resolve = getattr(self._ref_resolver, "resolve", None)
+                if resolve is None:
+                    with self._ref_resolver.resolving(ref) as resolved:
+                        return self.descend(instance, resolved)
+                else:
+                    scope, resolved = resolve(ref)
+                    self._ref_resolver.push_scope(scope)
+
+                    try:
+                        return list(self.descend(instance, resolved))
+                    finally:
+                        self._ref_resolver.pop_scope()
+
+        def is_valid(self, instance, _schema=None):
+            if _schema is not None:
+                warnings.warn(
+                    (
+                        "Passing a schema to Validator.is_valid is deprecated "
+                        "and will be removed in a future release. Call "
+                        "validator.evolve(schema=new_schema).is_valid(...) "
+                        "instead."
+                    ),
+                    DeprecationWarning,
+                    stacklevel=2,
+                )
+                self = self.evolve(schema=_schema)
+
+            error = next(self.iter_errors(instance), None)
+            return error is None
+
+    evolve_fields = [
+        (field.name, field.alias)
+        for field in fields(Validator)
+        if field.init
+    ]
+
+    if version is not None:
+        safe = version.title().replace(" ", "").replace("-", "")
+        Validator.__name__ = Validator.__qualname__ = f"{safe}Validator"
+        Validator = validates(version)(Validator)  # type: ignore[misc]
+
+    return Validator
+
+
+def extend(
+    validator,
+    validators=(),
+    version=None,
+    type_checker=None,
+    format_checker=None,
+):
+    """
+    Create a new validator class by extending an existing one.
+
+    Arguments:
+
+        validator (jsonschema.protocols.Validator):
+
+            an existing validator class
+
+        validators (collections.abc.Mapping):
+
+            a mapping of new validator callables to extend with, whose
+            structure is as in `create`.
+
+            .. note::
+
+                Any validator callables with the same name as an
+                existing one will (silently) replace the old validator
+                callable entirely, effectively overriding any validation
+                done in the "parent" validator class.
+
+                If you wish to instead extend the behavior of a parent's
+                validator callable, delegate and call it directly in
+                the new validator function by retrieving it using
+                ``OldValidator.VALIDATORS["validation_keyword_name"]``.
+
+        version (str):
+
+            a version for the new validator class
+
+        type_checker (jsonschema.TypeChecker):
+
+            a type checker, used when applying the :kw:`type` keyword.
+
+            If unprovided, the type checker of the extended
+            `jsonschema.protocols.Validator` will be carried along.
+
+        format_checker (jsonschema.FormatChecker):
+
+            a format checker, used when applying the :kw:`format` keyword.
+
+            If unprovided, the format checker of the extended
+            `jsonschema.protocols.Validator` will be carried along.
+
+    Returns:
+
+        a new `jsonschema.protocols.Validator` class extending the one
+        provided
+
+    .. note:: Meta Schemas
+
+        The new validator class will have its parent's meta schema.
+
+        If you wish to change or extend the meta schema in the new
+        validator class, modify ``META_SCHEMA`` directly on the returned
+        class. Note that no implicit copying is done, so a copy should
+        likely be made before modifying it, in order to not affect the
+        old validator.
+
+    """
+    all_validators = dict(validator.VALIDATORS)
+    all_validators.update(validators)
+
+    if type_checker is None:
+        type_checker = validator.TYPE_CHECKER
+    if format_checker is None:
+        format_checker = validator.FORMAT_CHECKER
+    return create(
+        meta_schema=validator.META_SCHEMA,
+        validators=all_validators,
+        version=version,
+        type_checker=type_checker,
+        format_checker=format_checker,
+        id_of=validator.ID_OF,
+        applicable_validators=validator._APPLICABLE_VALIDATORS,
+    )
+
+
+Draft3Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "http://json-schema.org/draft-03/schema#",
+    ),
+    validators={
+        "$ref": _keywords.ref,
+        "additionalItems": _legacy_keywords.additionalItems,
+        "additionalProperties": _keywords.additionalProperties,
+        "dependencies": _legacy_keywords.dependencies_draft3,
+        "disallow": _legacy_keywords.disallow_draft3,
+        "divisibleBy": _keywords.multipleOf,
+        "enum": _keywords.enum,
+        "extends": _legacy_keywords.extends_draft3,
+        "format": _keywords.format,
+        "items": _legacy_keywords.items_draft3_draft4,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maximum": _legacy_keywords.maximum_draft3_draft4,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minimum": _legacy_keywords.minimum_draft3_draft4,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "properties": _legacy_keywords.properties_draft3,
+        "type": _legacy_keywords.type_draft3,
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft3_type_checker,
+    format_checker=_format.draft3_format_checker,
+    version="draft3",
+    id_of=referencing.jsonschema.DRAFT3.id_of,
+    applicable_validators=_legacy_keywords.ignore_ref_siblings,
+)
+
+Draft4Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "http://json-schema.org/draft-04/schema#",
+    ),
+    validators={
+        "$ref": _keywords.ref,
+        "additionalItems": _legacy_keywords.additionalItems,
+        "additionalProperties": _keywords.additionalProperties,
+        "allOf": _keywords.allOf,
+        "anyOf": _keywords.anyOf,
+        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7,
+        "enum": _keywords.enum,
+        "format": _keywords.format,
+        "items": _legacy_keywords.items_draft3_draft4,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maxProperties": _keywords.maxProperties,
+        "maximum": _legacy_keywords.maximum_draft3_draft4,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minProperties": _keywords.minProperties,
+        "minimum": _legacy_keywords.minimum_draft3_draft4,
+        "multipleOf": _keywords.multipleOf,
+        "not": _keywords.not_,
+        "oneOf": _keywords.oneOf,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "properties": _keywords.properties,
+        "required": _keywords.required,
+        "type": _keywords.type,
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft4_type_checker,
+    format_checker=_format.draft4_format_checker,
+    version="draft4",
+    id_of=referencing.jsonschema.DRAFT4.id_of,
+    applicable_validators=_legacy_keywords.ignore_ref_siblings,
+)
+
+Draft6Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "http://json-schema.org/draft-06/schema#",
+    ),
+    validators={
+        "$ref": _keywords.ref,
+        "additionalItems": _legacy_keywords.additionalItems,
+        "additionalProperties": _keywords.additionalProperties,
+        "allOf": _keywords.allOf,
+        "anyOf": _keywords.anyOf,
+        "const": _keywords.const,
+        "contains": _legacy_keywords.contains_draft6_draft7,
+        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7,
+        "enum": _keywords.enum,
+        "exclusiveMaximum": _keywords.exclusiveMaximum,
+        "exclusiveMinimum": _keywords.exclusiveMinimum,
+        "format": _keywords.format,
+        "items": _legacy_keywords.items_draft6_draft7_draft201909,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maxProperties": _keywords.maxProperties,
+        "maximum": _keywords.maximum,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minProperties": _keywords.minProperties,
+        "minimum": _keywords.minimum,
+        "multipleOf": _keywords.multipleOf,
+        "not": _keywords.not_,
+        "oneOf": _keywords.oneOf,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "properties": _keywords.properties,
+        "propertyNames": _keywords.propertyNames,
+        "required": _keywords.required,
+        "type": _keywords.type,
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft6_type_checker,
+    format_checker=_format.draft6_format_checker,
+    version="draft6",
+    id_of=referencing.jsonschema.DRAFT6.id_of,
+    applicable_validators=_legacy_keywords.ignore_ref_siblings,
+)
+
+Draft7Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "http://json-schema.org/draft-07/schema#",
+    ),
+    validators={
+        "$ref": _keywords.ref,
+        "additionalItems": _legacy_keywords.additionalItems,
+        "additionalProperties": _keywords.additionalProperties,
+        "allOf": _keywords.allOf,
+        "anyOf": _keywords.anyOf,
+        "const": _keywords.const,
+        "contains": _legacy_keywords.contains_draft6_draft7,
+        "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7,
+        "enum": _keywords.enum,
+        "exclusiveMaximum": _keywords.exclusiveMaximum,
+        "exclusiveMinimum": _keywords.exclusiveMinimum,
+        "format": _keywords.format,
+        "if": _keywords.if_,
+        "items": _legacy_keywords.items_draft6_draft7_draft201909,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maxProperties": _keywords.maxProperties,
+        "maximum": _keywords.maximum,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minProperties": _keywords.minProperties,
+        "minimum": _keywords.minimum,
+        "multipleOf": _keywords.multipleOf,
+        "not": _keywords.not_,
+        "oneOf": _keywords.oneOf,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "properties": _keywords.properties,
+        "propertyNames": _keywords.propertyNames,
+        "required": _keywords.required,
+        "type": _keywords.type,
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft7_type_checker,
+    format_checker=_format.draft7_format_checker,
+    version="draft7",
+    id_of=referencing.jsonschema.DRAFT7.id_of,
+    applicable_validators=_legacy_keywords.ignore_ref_siblings,
+)
+
+Draft201909Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "https://json-schema.org/draft/2019-09/schema",
+    ),
+    validators={
+        "$recursiveRef": _legacy_keywords.recursiveRef,
+        "$ref": _keywords.ref,
+        "additionalItems": _legacy_keywords.additionalItems,
+        "additionalProperties": _keywords.additionalProperties,
+        "allOf": _keywords.allOf,
+        "anyOf": _keywords.anyOf,
+        "const": _keywords.const,
+        "contains": _keywords.contains,
+        "dependentRequired": _keywords.dependentRequired,
+        "dependentSchemas": _keywords.dependentSchemas,
+        "enum": _keywords.enum,
+        "exclusiveMaximum": _keywords.exclusiveMaximum,
+        "exclusiveMinimum": _keywords.exclusiveMinimum,
+        "format": _keywords.format,
+        "if": _keywords.if_,
+        "items": _legacy_keywords.items_draft6_draft7_draft201909,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maxProperties": _keywords.maxProperties,
+        "maximum": _keywords.maximum,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minProperties": _keywords.minProperties,
+        "minimum": _keywords.minimum,
+        "multipleOf": _keywords.multipleOf,
+        "not": _keywords.not_,
+        "oneOf": _keywords.oneOf,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "properties": _keywords.properties,
+        "propertyNames": _keywords.propertyNames,
+        "required": _keywords.required,
+        "type": _keywords.type,
+        "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019,
+        "unevaluatedProperties": (
+            _legacy_keywords.unevaluatedProperties_draft2019
+        ),
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft201909_type_checker,
+    format_checker=_format.draft201909_format_checker,
+    version="draft2019-09",
+)
+
+Draft202012Validator = create(
+    meta_schema=SPECIFICATIONS.contents(
+        "https://json-schema.org/draft/2020-12/schema",
+    ),
+    validators={
+        "$dynamicRef": _keywords.dynamicRef,
+        "$ref": _keywords.ref,
+        "additionalProperties": _keywords.additionalProperties,
+        "allOf": _keywords.allOf,
+        "anyOf": _keywords.anyOf,
+        "const": _keywords.const,
+        "contains": _keywords.contains,
+        "dependentRequired": _keywords.dependentRequired,
+        "dependentSchemas": _keywords.dependentSchemas,
+        "enum": _keywords.enum,
+        "exclusiveMaximum": _keywords.exclusiveMaximum,
+        "exclusiveMinimum": _keywords.exclusiveMinimum,
+        "format": _keywords.format,
+        "if": _keywords.if_,
+        "items": _keywords.items,
+        "maxItems": _keywords.maxItems,
+        "maxLength": _keywords.maxLength,
+        "maxProperties": _keywords.maxProperties,
+        "maximum": _keywords.maximum,
+        "minItems": _keywords.minItems,
+        "minLength": _keywords.minLength,
+        "minProperties": _keywords.minProperties,
+        "minimum": _keywords.minimum,
+        "multipleOf": _keywords.multipleOf,
+        "not": _keywords.not_,
+        "oneOf": _keywords.oneOf,
+        "pattern": _keywords.pattern,
+        "patternProperties": _keywords.patternProperties,
+        "prefixItems": _keywords.prefixItems,
+        "properties": _keywords.properties,
+        "propertyNames": _keywords.propertyNames,
+        "required": _keywords.required,
+        "type": _keywords.type,
+        "unevaluatedItems": _keywords.unevaluatedItems,
+        "unevaluatedProperties": _keywords.unevaluatedProperties,
+        "uniqueItems": _keywords.uniqueItems,
+    },
+    type_checker=_types.draft202012_type_checker,
+    format_checker=_format.draft202012_format_checker,
+    version="draft2020-12",
+)
+
+_LATEST_VERSION = Draft202012Validator
+
+
+class _RefResolver:
+    """
+    Resolve JSON References.
+
+    Arguments:
+
+        base_uri (str):
+
+            The URI of the referring document
+
+        referrer:
+
+            The actual referring document
+
+        store (dict):
+
+            A mapping from URIs to documents to cache
+
+        cache_remote (bool):
+
+            Whether remote refs should be cached after first resolution
+
+        handlers (dict):
+
+            A mapping from URI schemes to functions that should be used
+            to retrieve them
+
+        urljoin_cache (:func:`functools.lru_cache`):
+
+            A cache that will be used for caching the results of joining
+            the resolution scope to subscopes.
+
+        remote_cache (:func:`functools.lru_cache`):
+
+            A cache that will be used for caching the results of
+            resolved remote URLs.
+
+    Attributes:
+
+        cache_remote (bool):
+
+            Whether remote refs should be cached after first resolution
+
+    .. deprecated:: v4.18.0
+
+        ``RefResolver`` has been deprecated in favor of `referencing`.
+
+    """
+
+    _DEPRECATION_MESSAGE = (
+        "jsonschema.RefResolver is deprecated as of v4.18.0, in favor of the "
+        "https://github.com/python-jsonschema/referencing library, which "
+        "provides more compliant referencing behavior as well as more "
+        "flexible APIs for customization. A future release will remove "
+        "RefResolver. Please file a feature request (on referencing) if you "
+        "are missing an API for the kind of customization you need."
+    )
+
+    def __init__(
+        self,
+        base_uri,
+        referrer,
+        store=HashTrieMap(),
+        cache_remote=True,
+        handlers=(),
+        urljoin_cache=None,
+        remote_cache=None,
+    ):
+        if urljoin_cache is None:
+            urljoin_cache = lru_cache(1024)(urljoin)
+        if remote_cache is None:
+            remote_cache = lru_cache(1024)(self.resolve_from_url)
+
+        self.referrer = referrer
+        self.cache_remote = cache_remote
+        self.handlers = dict(handlers)
+
+        self._scopes_stack = [base_uri]
+
+        self.store = _utils.URIDict(
+            (uri, each.contents) for uri, each in SPECIFICATIONS.items()
+        )
+        self.store.update(
+            (id, each.META_SCHEMA) for id, each in _META_SCHEMAS.items()
+        )
+        self.store.update(store)
+        self.store.update(
+            (schema["$id"], schema)
+            for schema in store.values()
+            if isinstance(schema, Mapping) and "$id" in schema
+        )
+        self.store[base_uri] = referrer
+
+        self._urljoin_cache = urljoin_cache
+        self._remote_cache = remote_cache
+
+    @classmethod
+    def from_schema(  # noqa: D417
+        cls,
+        schema,
+        id_of=referencing.jsonschema.DRAFT202012.id_of,
+        *args,
+        **kwargs,
+    ):
+        """
+        Construct a resolver from a JSON schema object.
+
+        Arguments:
+
+            schema:
+
+                the referring schema
+
+        Returns:
+
+            `_RefResolver`
+
+        """
+        return cls(base_uri=id_of(schema) or "", referrer=schema, *args, **kwargs)  # noqa: B026, E501
+
+    def push_scope(self, scope):
+        """
+        Enter a given sub-scope.
+
+        Treats further dereferences as being performed underneath the
+        given scope.
+        """
+        self._scopes_stack.append(
+            self._urljoin_cache(self.resolution_scope, scope),
+        )
+
+    def pop_scope(self):
+        """
+        Exit the most recent entered scope.
+
+        Treats further dereferences as being performed underneath the
+        original scope.
+
+        Don't call this method more times than `push_scope` has been
+        called.
+        """
+        try:
+            self._scopes_stack.pop()
+        except IndexError:
+            raise exceptions._RefResolutionError(
+                "Failed to pop the scope from an empty stack. "
+                "`pop_scope()` should only be called once for every "
+                "`push_scope()`",
+            ) from None
+
+    @property
+    def resolution_scope(self):
+        """
+        Retrieve the current resolution scope.
+        """
+        return self._scopes_stack[-1]
+
+    @property
+    def base_uri(self):
+        """
+        Retrieve the current base URI, not including any fragment.
+        """
+        uri, _ = urldefrag(self.resolution_scope)
+        return uri
+
+    @contextlib.contextmanager
+    def in_scope(self, scope):
+        """
+        Temporarily enter the given scope for the duration of the context.
+
+        .. deprecated:: v4.0.0
+        """
+        warnings.warn(
+            "jsonschema.RefResolver.in_scope is deprecated and will be "
+            "removed in a future release.",
+            DeprecationWarning,
+            stacklevel=3,
+        )
+        self.push_scope(scope)
+        try:
+            yield
+        finally:
+            self.pop_scope()
+
+    @contextlib.contextmanager
+    def resolving(self, ref):
+        """
+        Resolve the given ``ref`` and enter its resolution scope.
+
+        Exits the scope on exit of this context manager.
+
+        Arguments:
+
+            ref (str):
+
+                The reference to resolve
+
+        """
+        url, resolved = self.resolve(ref)
+        self.push_scope(url)
+        try:
+            yield resolved
+        finally:
+            self.pop_scope()
+
+    def _find_in_referrer(self, key):
+        return self._get_subschemas_cache()[key]
+
+    @lru_cache  # noqa: B019
+    def _get_subschemas_cache(self):
+        cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS}
+        for keyword, subschema in _search_schema(
+            self.referrer, _match_subschema_keywords,
+        ):
+            cache[keyword].append(subschema)
+        return cache
+
+    @lru_cache  # noqa: B019
+    def _find_in_subschemas(self, url):
+        subschemas = self._get_subschemas_cache()["$id"]
+        if not subschemas:
+            return None
+        uri, fragment = urldefrag(url)
+        for subschema in subschemas:
+            id = subschema["$id"]
+            if not isinstance(id, str):
+                continue
+            target_uri = self._urljoin_cache(self.resolution_scope, id)
+            if target_uri.rstrip("/") == uri.rstrip("/"):
+                if fragment:
+                    subschema = self.resolve_fragment(subschema, fragment)
+                self.store[url] = subschema
+                return url, subschema
+        return None
+
+    def resolve(self, ref):
+        """
+        Resolve the given reference.
+        """
+        url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/")
+
+        match = self._find_in_subschemas(url)
+        if match is not None:
+            return match
+
+        return url, self._remote_cache(url)
+
+    def resolve_from_url(self, url):
+        """
+        Resolve the given URL.
+        """
+        url, fragment = urldefrag(url)
+        if not url:
+            url = self.base_uri
+
+        try:
+            document = self.store[url]
+        except KeyError:
+            try:
+                document = self.resolve_remote(url)
+            except Exception as exc:
+                raise exceptions._RefResolutionError(exc) from exc
+
+        return self.resolve_fragment(document, fragment)
+
+    def resolve_fragment(self, document, fragment):
+        """
+        Resolve a ``fragment`` within the referenced ``document``.
+
+        Arguments:
+
+            document:
+
+                The referent document
+
+            fragment (str):
+
+                a URI fragment to resolve within it
+
+        """
+        fragment = fragment.lstrip("/")
+
+        if not fragment:
+            return document
+
+        if document is self.referrer:
+            find = self._find_in_referrer
+        else:
+
+            def find(key):
+                yield from _search_schema(document, _match_keyword(key))
+
+        for keyword in ["$anchor", "$dynamicAnchor"]:
+            for subschema in find(keyword):
+                if fragment == subschema[keyword]:
+                    return subschema
+        for keyword in ["id", "$id"]:
+            for subschema in find(keyword):
+                if "#" + fragment == subschema[keyword]:
+                    return subschema
+
+        # Resolve via path
+        parts = unquote(fragment).split("/") if fragment else []
+        for part in parts:
+            part = part.replace("~1", "/").replace("~0", "~")
+
+            if isinstance(document, Sequence):
+                try:  # noqa: SIM105
+                    part = int(part)
+                except ValueError:
+                    pass
+            try:
+                document = document[part]
+            except (TypeError, LookupError) as err:
+                raise exceptions._RefResolutionError(
+                    f"Unresolvable JSON pointer: {fragment!r}",
+                ) from err
+
+        return document
+
+    def resolve_remote(self, uri):
+        """
+        Resolve a remote ``uri``.
+
+        If called directly, does not check the store first, but after
+        retrieving the document at the specified URI it will be saved in
+        the store if :attr:`cache_remote` is True.
+
+        .. note::
+
+            If the requests_ library is present, ``jsonschema`` will use it to
+            request the remote ``uri``, so that the correct encoding is
+            detected and used.
+
+            If it isn't, or if the scheme of the ``uri`` is not ``http`` or
+            ``https``, UTF-8 is assumed.
+
+        Arguments:
+
+            uri (str):
+
+                The URI to resolve
+
+        Returns:
+
+            The retrieved document
+
+        .. _requests: https://pypi.org/project/requests/
+
+        """
+        try:
+            import requests
+        except ImportError:
+            requests = None
+
+        scheme = urlsplit(uri).scheme
+
+        if scheme in self.handlers:
+            result = self.handlers[scheme](uri)
+        elif scheme in ["http", "https"] and requests:
+            # Requests has support for detecting the correct encoding of
+            # json over http
+            result = requests.get(uri).json()
+        else:
+            # Otherwise, pass off to urllib and assume utf-8
+            with urlopen(uri) as url:  # noqa: S310
+                result = json.loads(url.read().decode("utf-8"))
+
+        if self.cache_remote:
+            self.store[uri] = result
+        return result
+
+
+_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor")
+
+
+def _match_keyword(keyword):
+
+    def matcher(value):
+        if keyword in value:
+            yield value
+
+    return matcher
+
+
+def _match_subschema_keywords(value):
+    for keyword in _SUBSCHEMAS_KEYWORDS:
+        if keyword in value:
+            yield keyword, value
+
+
+def _search_schema(schema, matcher):
+    """Breadth-first search routine."""
+    values = deque([schema])
+    while values:
+        value = values.pop()
+        if not isinstance(value, dict):
+            continue
+        yield from matcher(value)
+        values.extendleft(value.values())
+
+
+def validate(instance, schema, cls=None, *args, **kwargs):  # noqa: D417
+    """
+    Validate an instance under the given schema.
+
+        >>> validate([2, 3, 4], {"maxItems": 2})
+        Traceback (most recent call last):
+            ...
+        ValidationError: [2, 3, 4] is too long
+
+    :func:`~jsonschema.validators.validate` will first verify that the
+    provided schema is itself valid, since not doing so can lead to less
+    obvious error messages and fail in less obvious or consistent ways.
+
+    If you know you have a valid schema already, especially
+    if you intend to validate multiple instances with
+    the same schema, you likely would prefer using the
+    `jsonschema.protocols.Validator.validate` method directly on a
+    specific validator (e.g. ``Draft202012Validator.validate``).
+
+
+    Arguments:
+
+        instance:
+
+            The instance to validate
+
+        schema:
+
+            The schema to validate with
+
+        cls (jsonschema.protocols.Validator):
+
+            The class that will be used to validate the instance.
+
+    If the ``cls`` argument is not provided, two things will happen
+    in accordance with the specification. First, if the schema has a
+    :kw:`$schema` keyword containing a known meta-schema [#]_ then the
+    proper validator will be used. The specification recommends that
+    all schemas contain :kw:`$schema` properties for this reason. If no
+    :kw:`$schema` property is found, the default validator class is the
+    latest released draft.
+
+    Any other provided positional and keyword arguments will be passed
+    on when instantiating the ``cls``.
+
+    Raises:
+
+        `jsonschema.exceptions.ValidationError`:
+
+            if the instance is invalid
+
+        `jsonschema.exceptions.SchemaError`:
+
+            if the schema itself is invalid
+
+    .. rubric:: Footnotes
+    .. [#] known by a validator registered with
+        `jsonschema.validators.validates`
+
+    """
+    if cls is None:
+        cls = validator_for(schema)
+
+    cls.check_schema(schema)
+    validator = cls(schema, *args, **kwargs)
+    error = exceptions.best_match(validator.iter_errors(instance))
+    if error is not None:
+        raise error
+
+
+def validator_for(
+    schema,
+    default: Validator | _utils.Unset = _UNSET,
+) -> type[Validator]:
+    """
+    Retrieve the validator class appropriate for validating the given schema.
+
+    Uses the :kw:`$schema` keyword that should be present in the given
+    schema to look up the appropriate validator class.
+
+    Arguments:
+
+        schema (collections.abc.Mapping or bool):
+
+            the schema to look at
+
+        default:
+
+            the default to return if the appropriate validator class
+            cannot be determined.
+
+            If unprovided, the default is to return the latest supported
+            draft.
+
+    Examples:
+
+        The :kw:`$schema` JSON Schema keyword will control which validator
+        class is returned:
+
+        >>> schema = {
+        ...     "$schema": "https://json-schema.org/draft/2020-12/schema",
+        ...     "type": "integer",
+        ... }
+        >>> jsonschema.validators.validator_for(schema)
+        <class 'jsonschema.validators.Draft202012Validator'>
+
+
+        Here, a draft 7 schema instead will return the draft 7 validator:
+
+        >>> schema = {
+        ...     "$schema": "http://json-schema.org/draft-07/schema#",
+        ...     "type": "integer",
+        ... }
+        >>> jsonschema.validators.validator_for(schema)
+        <class 'jsonschema.validators.Draft7Validator'>
+
+
+        Schemas with no ``$schema`` keyword will fallback to the default
+        argument:
+
+        >>> schema = {"type": "integer"}
+        >>> jsonschema.validators.validator_for(
+        ...     schema, default=Draft7Validator,
+        ... )
+        <class 'jsonschema.validators.Draft7Validator'>
+
+        or if none is provided, to the latest version supported.
+        Always including the keyword when authoring schemas is highly
+        recommended.
+
+    """
+    DefaultValidator = _LATEST_VERSION if default is _UNSET else default
+
+    if schema is True or schema is False or "$schema" not in schema:
+        return DefaultValidator
+    if schema["$schema"] not in _META_SCHEMAS and default is _UNSET:
+        warn(
+            (
+                "The metaschema specified by $schema was not found. "
+                "Using the latest draft to validate, but this will raise "
+                "an error in the future."
+            ),
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    return _META_SCHEMAS.get(schema["$schema"], DefaultValidator)