Skip to content

Add support for referencing subschemas by id. #371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
39 changes: 39 additions & 0 deletions jsonschema/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,42 @@ def uniq(container):
return False
seen.append(e)
return True


def subschemas(schema, key=u'id'):
"""
Finds all explicitly identified (sub)schemas contained in the provided ``schema``, including the root schema.
The optional ``key`` parameter is to allow for easier interop between draft-03/04 and draft-06+.

Arguments:

schema (dict):

The schema to extract all constituent (sub)schemas from.

key (str):

The keyword used to define a URI reference for a schema. id for draft-03/04 and $id for draft-06+.

Returns:

list: A list of the (sub)schemas defined within the provided ``schema``.

"""
schemas = list()
if not isinstance(schema, dict):
return schemas
schema_id = schema.get(key, None)
if schema_id is not None and isinstance(schema_id, str_types):
schemas.append(schema)
for key, value in schema.items():
# Recurse if the value is a dictionary.
if isinstance(value, dict):
schemas.extend(subschemas(value))
# Check all list members. We don't have to worry about iterables due to raw schema type mapping.
if isinstance(value, list):
for item in value:
# Recurse if the value is a dictionary.
if isinstance(item, dict):
schemas.extend(subschemas(item))
return schemas
6 changes: 4 additions & 2 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,8 @@ def test_custom_uri_scheme_handlers(self):

def test_cache_remote_on(self):
ref = "foo://bar"
foo_handler = mock.Mock()
foo_handler = mock.MagicMock(spec=dict())
foo_handler.__getitem__.side_effect = ''
resolver = RefResolver(
"", {}, cache_remote=True, handlers={"foo": foo_handler},
)
Expand All @@ -909,7 +910,8 @@ def test_cache_remote_on(self):

def test_cache_remote_off(self):
ref = "foo://bar"
foo_handler = mock.Mock()
foo_handler = mock.MagicMock(spec=dict())
foo_handler.__getitem__.side_effect = ''
resolver = RefResolver(
"", {}, cache_remote=False, handlers={"foo": foo_handler},
)
Expand Down
18 changes: 18 additions & 0 deletions jsonschema/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ def __init__(
)
self.store.update(store)
self.store[base_uri] = referrer
self.subschema_store = self.extract_store_subschemas()

self._urljoin_cache = urljoin_cache
self._remote_cache = remote_cache
Expand All @@ -322,6 +323,14 @@ def from_schema(cls, schema, *args, **kwargs):

return cls(schema.get(u"id", u""), schema, *args, **kwargs)

def extract_store_subschemas(self):
subschemas = dict()
for base_id, schema in self.store.items():
subschemas[base_id] = {
subschema[u"id"].lstrip(u'#'): subschema for subschema in _utils.subschemas(schema)
}
return subschemas

def push_scope(self, scope):
self._scopes_stack.append(
self._urljoin_cache(self.resolution_scope, scope),
Expand Down Expand Up @@ -407,6 +416,12 @@ def resolve_fragment(self, document, fragment):

"""

# First try looking up the fragment schema reference in the subschema_store.
try:
return self.subschema_store[document[u'id']][fragment]
except (KeyError, LookupError, TypeError):
pass

fragment = fragment.lstrip(u"/")
parts = unquote(fragment).split(u"/") if fragment else []

Expand Down Expand Up @@ -480,6 +495,9 @@ def resolve_remote(self, uri):

if self.cache_remote:
self.store[uri] = result
self.subschema_store[uri] = {
subschema[u"id"].lstrip(u'#'): subschema for subschema in _utils.subschemas(result)
}
return result


Expand Down