From 9da55dfa02c54b8e638a35362fb99755e5d46ce5 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Wed, 15 Feb 2023 12:25:58 +0200 Subject: Move reference resolution to a (private) Validator method. This will make it easier to swap over to referencing's Resolver (while preserving backwards compat for anyone who passes a RefResolver to a Validator). At some point in the future this method may become public (which will make it easier for external dialects to resolve references) but let's keep it private for a bit until it's clear that the interface is stable -- a future draft might do crazy things like have adjacent properties to $ref affect the resolution behavior, which would make this method need to take more than just the $ref value. --- jsonschema/_validators.py | 27 +------------ jsonschema/tests/test_jsonschema_test_suite.py | 52 +++++++++++++++++--------- jsonschema/validators.py | 34 +++++++++++++---- 3 files changed, 63 insertions(+), 50 deletions(-) (limited to 'jsonschema') diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py index 8542a87..7351c48 100644 --- a/jsonschema/_validators.py +++ b/jsonschema/_validators.py @@ -1,5 +1,4 @@ from fractions import Fraction -from urllib.parse import urldefrag, urljoin import re from jsonschema._utils import ( @@ -286,33 +285,11 @@ def enum(validator, enums, instance, schema): def ref(validator, ref, instance, schema): - resolve = getattr(validator.resolver, "resolve", None) - if resolve is None: - with validator.resolver.resolving(ref) as resolved: - yield from validator.descend(instance, resolved) - else: - scope, resolved = validator.resolver.resolve(ref) - validator.resolver.push_scope(scope) - - try: - yield from validator.descend(instance, resolved) - finally: - validator.resolver.pop_scope() + yield from validator._validate_reference(ref=ref, instance=instance) def dynamicRef(validator, dynamicRef, instance, schema): - _, fragment = urldefrag(dynamicRef) - - for url in validator.resolver._scopes_stack: - lookup_url = urljoin(url, dynamicRef) - with validator.resolver.resolving(lookup_url) as subschema: - if ("$dynamicAnchor" in subschema - and fragment == subschema["$dynamicAnchor"]): - yield from validator.descend(instance, subschema) - break - else: - with validator.resolver.resolving(dynamicRef) as subschema: - yield from validator.descend(instance, subschema) + yield from validator._validate_reference(ref=dynamicRef, instance=instance) def type(validator, types, instance, schema): diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py index 5ebd7ed..3b602aa 100644 --- a/jsonschema/tests/test_jsonschema_test_suite.py +++ b/jsonschema/tests/test_jsonschema_test_suite.py @@ -419,23 +419,6 @@ TestDraft202012 = DRAFT202012.to_unittest_testcase( "$defs first" ), )(test) - or skip( - message="dynamicRef support isn't fully working yet.", - subject="dynamicRef", - description="correct extended schema", - case_description=( - "$ref and $dynamicAnchor are independent of order - " - "$defs first" - ), - )(test) - or skip( - message="dynamicRef support isn't fully working yet.", - subject="dynamicRef", - description="correct extended schema", - case_description=( - "$ref and $dynamicAnchor are independent of order - $ref first" - ), - )(test) or skip( message="dynamicRef support isn't fully working yet.", subject="dynamicRef", @@ -482,6 +465,41 @@ TestDraft202012 = DRAFT202012.to_unittest_testcase( subject="anchor", case_description="same $anchor with different base uri", )(test) + or skip( + message="dynamicRef support isn't fully working yet.", + subject="dynamicRef", + description="instance with misspelled field", + case_description=( + "strict-tree schema, guards against misspelled properties" + ), + )(test) + or skip( + message="dynamicRef support isn't fully working yet.", + subject="dynamicRef", + description="An array containing non-strings is invalid", + case_description=( + "A $dynamicRef resolves to the first $dynamicAnchor still " + "in scope that is encountered when the schema is evaluated" + ), + )(test) + or skip( + message="dynamicRef support isn't fully working yet.", + subject="dynamicRef", + description="An array containing non-strings is invalid", + case_description=( + "A $dynamicRef with intermediate scopes that don't include a " + "matching $dynamicAnchor does not affect dynamic scope " + "resolution" + ), + )(test) + or skip( + message="dynamicRef support isn't fully working yet.", + subject="dynamicRef", + description="incorrect extended schema", + case_description=( + "tests for implementation dynamic anchor and reference link" + ), + )(test) or skip( message="Vocabulary support is still in-progress.", subject="vocabulary", diff --git a/jsonschema/validators.py b/jsonschema/validators.py index b86f376..be5ff18 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -180,7 +180,7 @@ def create( ID_OF = staticmethod(id_of) schema = attr.ib(repr=reprlib.repr) - resolver = attr.ib(default=None, repr=False) + _resolver = attr.ib(default=None, repr=False) format_checker = attr.ib(default=None) def __init_subclass__(cls): @@ -198,13 +198,6 @@ def create( stacklevel=2, ) - def __attrs_post_init__(self): - if self.resolver is None: - self.resolver = _RefResolver.from_schema( - self.schema, - id_of=id_of, - ) - @classmethod def check_schema(cls, schema, format_checker=_UNSET): Validator = validator_for(cls.META_SCHEMA, default=cls) @@ -217,6 +210,15 @@ def create( for error in validator.iter_errors(schema): raise exceptions.SchemaError.create_from(error) + @property + def resolver(self): + if self._resolver is None: + self._resolver = _RefResolver.from_schema( + self.schema, + id_of=id_of, + ) + return self._resolver + def evolve(self, **changes): # Essentially reproduces attr.evolve, but may involve instantiating # a different class than this one. @@ -262,6 +264,8 @@ def create( ) return + # Temporarily needed to eagerly create a resolver... + self.resolver scope = id_of(_schema) if scope: self.resolver.push_scope(scope) @@ -306,6 +310,20 @@ def create( except exceptions.UndefinedTypeCheck: raise exceptions.UnknownType(type, instance, self.schema) + def _validate_reference(self, ref, instance): + resolve = getattr(self.resolver, "resolve", None) + if resolve is None: + with self.resolver.resolving(ref) as resolved: + yield from self.descend(instance, resolved) + else: + scope, resolved = self.resolver.resolve(ref) + self.resolver.push_scope(scope) + + try: + yield from self.descend(instance, resolved) + finally: + self.resolver.pop_scope() + def is_valid(self, instance, _schema=None): if _schema is not None: warnings.warn( -- cgit v1.2.1