aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/jsonschema/validators.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/jsonschema/validators.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are hereHEADmaster
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)