diff --git a/asdf/resource.py b/asdf/resource.py index adadfba75..f7f0daf56 100644 --- a/asdf/resource.py +++ b/asdf/resource.py @@ -4,6 +4,7 @@ """ import pkgutil from collections.abc import Mapping +import importlib_metadata from asdf_standard import DirectoryResourceMapping as _DirectoryResourceMapping @@ -168,9 +169,16 @@ def __repr__(self): return f"" -_JSONSCHEMA_URI_TO_FILENAME = { - "http://json-schema.org/draft-04/schema": "draft4.json", -} +if importlib_metadata.version('jsonschema') >= '4.18': + USE_JSONSCHEMA_SPECIFICATIONS = True + _JSONSCHEMA_URI_TO_FILENAME = { + "http://json-schema.org/draft-04/schema": "metaschema.json", + } +else: + USE_JSONSCHEMA_SPECIFICATIONS = False + _JSONSCHEMA_URI_TO_FILENAME = { + "http://json-schema.org/draft-04/schema": "draft4.json", + } class JsonschemaResourceMapping(Mapping): @@ -181,7 +189,10 @@ class JsonschemaResourceMapping(Mapping): def __getitem__(self, uri): filename = _JSONSCHEMA_URI_TO_FILENAME[uri] - return pkgutil.get_data("jsonschema", f"schemas/{filename}") + if USE_JSONSCHEMA_SPECIFICATIONS: + return pkgutil.get_data("jsonschema_specifications", f"schemas/draft4/{filename}") + else: + return pkgutil.get_data("jsonschema", f"schemas/{filename}") def __len__(self): return len(_JSONSCHEMA_URI_TO_FILENAME) diff --git a/asdf/schema.py b/asdf/schema.py index 97dc8eeec..463e830b7 100644 --- a/asdf/schema.py +++ b/asdf/schema.py @@ -11,8 +11,16 @@ import numpy as np import yaml +import importlib_metadata +if importlib_metadata.version('jsonschema') >= "4.18": + USE_REFERENCING = True + import referencing + from referencing.exceptions import Unretrievable as RefError +else: + from jsonschema.exceptions import RefResolutionError as RefError + USE_REFERENCING = False from jsonschema import validators as mvalidators -from jsonschema.exceptions import RefResolutionError, ValidationError +from jsonschema.exceptions import ValidationError from . import constants, extension, generic_io, reference, tagged, treeutil, util, versioning, yamlutil from .config import get_config @@ -215,7 +223,10 @@ def has_seen_pair(self, schema, instance): return (id(schema), id(instance)) in self._seen_id_pairs def create(self, schema, resolver): - return self._validator_class(schema, resolver=resolver) + if USE_REFERENCING: + return self._validator_class(schema, registry=resolver) + else: + return self._validator_class(schema, resolver=resolver) class _CycleCheckingValidatorProxy: @@ -384,13 +395,20 @@ def get_schema(url): # remote schemas here in `load_schema`, so we don't need # jsonschema to do it on our behalf. Setting it to `True` # counterintuitively makes things slower. - return mvalidators.RefResolver( - "", - {}, - cache_remote=False, - handlers=handlers, - urljoin_cache=urljoin_cache, - ) + if USE_REFERENCING: + def retrieve_schema(url): + schema = get_schema(url) + resource = referencing.Resource(schema, specification=referencing.jsonschema.DRAFT4) + return resource + return referencing.Registry({}, retrieve=retrieve_schema) + else: + return mvalidators.RefResolver( + "", + {}, + cache_remote=False, + handlers=handlers, + urljoin_cache=urljoin_cache, + ) @lru_cache @@ -569,11 +587,18 @@ def _iter_errors(self, instance, _schema=None): for schema_uri in schema_uris: try: - tag_schema = self._resolver.resolve(schema_uri)[1] - yield from self._json_schema_validator_factory.create(tag_schema, self._resolver).iter_errors( - node, - ) - except RefResolutionError: + if USE_REFERENCING: + tag_resource = self._resolver.get_or_retrieve(schema_uri) + resolver = tag_resource.value @ self._resolver + v = self._json_schema_validator_factory.create(tag_resource.value.contents, resolver) + v._resolver = resolver.resolver_with_root(tag_resource.value) + yield from v.iter_errors(node) + else: + tag_schema = self._resolver.resolve(schema_uri)[1] + yield from self._json_schema_validator_factory.create(tag_schema, self._resolver).iter_errors( + node, + ) + except RefError: warnings.warn(f"Unable to locate schema file for '{tag}': '{schema_uri}'", AsdfWarning) def validate(self, instance, _schema=None): @@ -803,21 +828,13 @@ def check_schema(schema, validate_default=True): validators = util.HashableDict(mvalidators.Draft4Validator.VALIDATORS.copy()) if validate_default: - # The jsonschema library doesn't validate defaults - # on its own. - instance_validator = _create_json_schema_validator_factory().create(schema, _make_resolver(None)) - instance_scope = schema.get("id", "") - def _validate_default(validator, default, instance, schema): if not validator.is_type(instance, "object"): return if "default" in instance: - instance_validator.resolver.push_scope(instance_scope) - try: - yield from instance_validator.descend(instance["default"], instance) - finally: - instance_validator.resolver.pop_scope() + get_validator(instance).validate(instance['default']) + return validators.update({"default": _validate_default}) @@ -841,5 +858,8 @@ def applicable_validators(schema): id_of=mvalidators.Draft4Validator.ID_OF, applicable_validators=applicable_validators, ) - validator = cls(meta_schema, resolver=resolver) + if USE_REFERENCING: + validator = cls(meta_schema, registry=resolver) + else: + validator = cls(meta_schema, resolver=resolver) validator.validate(schema)