summaryrefslogtreecommitdiff
path: root/jsonschema/validators.py
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2023-02-16 12:32:00 +0200
committerJulian Berman <Julian@GrayVines.com>2023-02-21 09:58:40 +0200
commite8266294408521daf38d879ba35c45a4b0ef5180 (patch)
tree0dba5b9aafc21b65d0a48b744cea021770e8e9d9 /jsonschema/validators.py
parenta39e5c953a559b287c753bd604e3e11d218c29cf (diff)
downloadjsonschema-e8266294408521daf38d879ba35c45a4b0ef5180.tar.gz
Resolve $ref using the referencing library.
Passes all the remaining referencing tests across all drafts, hooray! Makes Validators take a referencing.Registry argument which users should use to customize preloaded schemas, or to configure remote reference retrieval. This fully obsoletes jsonschema.RefResolver, which has already been deprecated in a previous commit. Users should move to instead loading schemas into referencing.Registry objects. See the referencing documentation at https://referencing.rtfd.io/ for details (with more jsonschema-specific information to be added shortly). Note that the interface for resolving references on a Validator is not yet public (and hidden behind _resolver and _validate_reference attributes). One or both of these are likely to become public after some period of stabilization. Feedback is of course welcome!
Diffstat (limited to 'jsonschema/validators.py')
-rw-r--r--jsonschema/validators.py98
1 files changed, 66 insertions, 32 deletions
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index 5a0cd43..b802856 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -17,6 +17,7 @@ import warnings
from jsonschema_specifications import REGISTRY as SPECIFICATIONS
from pyrsistent import m
+from referencing import Specification
import attr
import referencing.jsonschema
@@ -170,6 +171,11 @@ def create(
# 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),
+ default=Specification.OPAQUE,
+ )
+
@attr.s
class Validator:
@@ -182,6 +188,19 @@ def create(
schema = attr.ib(repr=reprlib.repr)
_ref_resolver = attr.ib(default=None, repr=False, alias="resolver")
format_checker = attr.ib(default=None)
+ # TODO: include new meta-schemas added at runtime
+ _registry = attr.ib(
+ default=SPECIFICATIONS,
+ converter=SPECIFICATIONS.combine, # type: ignore[misc]
+ kw_only=True,
+ repr=False,
+ )
+ _resolver = attr.ib(
+ alias="_resolver",
+ default=None,
+ kw_only=True,
+ repr=False,
+ )
def __init_subclass__(cls):
warnings.warn(
@@ -198,6 +217,12 @@ def create(
stacklevel=2,
)
+ def __attrs_post_init__(self):
+ if self._resolver is None:
+ self._resolver = self._registry.resolver_with_root(
+ resource=specification.create_resource(self.schema),
+ )
+
@classmethod
def check_schema(cls, schema, format_checker=_UNSET):
Validator = validator_for(cls.META_SCHEMA, default=cls)
@@ -276,38 +301,39 @@ def create(
)
return
- # Temporarily needed to eagerly create a resolver...
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- self.resolver
- scope = id_of(_schema)
- if scope:
- self.resolver.push_scope(scope)
- try:
- for k, v in applicable_validators(_schema):
- validator = self.VALIDATORS.get(k)
- if validator is None:
- continue
-
- 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
- finally:
- if scope:
- self.resolver.pop_scope()
-
- def descend(self, instance, schema, path=None, schema_path=None):
- for error in self.evolve(schema=schema).iter_errors(instance):
+ for k, v in applicable_validators(_schema):
+ validator = self.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ 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 resolver is None:
+ resolver = self._resolver.in_subresource(
+ specification.create_resource(schema),
+ )
+ validator = self.evolve(schema=schema, _resolver=resolver)
+ for error in validator.iter_errors(instance):
if path is not None:
error.path.appendleft(path)
if schema_path is not None:
@@ -325,6 +351,14 @@ def create(
raise exceptions.UnknownType(type, instance, self.schema)
def _validate_reference(self, ref, instance):
+ if self._ref_resolver is None:
+ resolved = self._resolver.lookup(ref)
+ 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: