Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions asdf/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import pkgutil
from collections.abc import Mapping
import importlib_metadata

from asdf_standard import DirectoryResourceMapping as _DirectoryResourceMapping

Expand Down Expand Up @@ -168,9 +169,16 @@ def __repr__(self):
return f"<ResourceManager len: {self.__len__()}>"


_JSONSCHEMA_URI_TO_FILENAME = {
"http://json-schema.org/draft-04/schema": "draft4.json",
}
if importlib_metadata.version('jsonschema') >= '4.18':
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should make this 4.18.0dev...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, another thing I didn't notice until after I merged is we're just comparing strings here. Need to use packaging.version.parse so that they get compared as version numbers.

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):
Expand All @@ -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}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhh so that's where that got to! I was super confused when I didn't see it in either referencing or jsonschema.

else:
return pkgutil.get_data("jsonschema", f"schemas/{filename}")

def __len__(self):
return len(_JSONSCHEMA_URI_TO_FILENAME)
Expand Down
70 changes: 45 additions & 25 deletions asdf/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there are actually two exceptions we need to catch here, Unretrievable and Unresolvable.

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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added when trying to debug why many tests were failing with errors like:

referencing.exceptions.Unresolvable: software-1.0.0

which was tracked down to loss of the base_uri I think here:
https://github.com/python-jsonschema/jsonschema/blob/529b57aefb4810866c3de0750349fb227fac816e/jsonschema/validators.py#L248-L252

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I believe is a symptom of this issue:

python-jsonschema/jsonschema#1061

Once we're able to specify the dialect we want, that specification object will be able to find the software schema's id field and use it as the base URI.

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):
Expand Down Expand Up @@ -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'])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well that certainly seems more better!

return

validators.update({"default": _validate_default})

Expand All @@ -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)