Spaces:
Runtime error
Runtime error
from collections.abc import Mapping, MutableMapping, Sequence | |
from urllib.parse import urlsplit | |
import itertools | |
import re | |
class URIDict(MutableMapping): | |
""" | |
Dictionary which uses normalized URIs as keys. | |
""" | |
def normalize(self, uri): | |
return urlsplit(uri).geturl() | |
def __init__(self, *args, **kwargs): | |
self.store = dict() | |
self.store.update(*args, **kwargs) | |
def __getitem__(self, uri): | |
return self.store[self.normalize(uri)] | |
def __setitem__(self, uri, value): | |
self.store[self.normalize(uri)] = value | |
def __delitem__(self, uri): | |
del self.store[self.normalize(uri)] | |
def __iter__(self): | |
return iter(self.store) | |
def __len__(self): # pragma: no cover -- untested, but to be removed | |
return len(self.store) | |
def __repr__(self): # pragma: no cover -- untested, but to be removed | |
return repr(self.store) | |
class Unset: | |
""" | |
An as-of-yet unset attribute or unprovided default parameter. | |
""" | |
def __repr__(self): # pragma: no cover | |
return "<unset>" | |
def format_as_index(container, indices): | |
""" | |
Construct a single string containing indexing operations for the indices. | |
For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"] | |
Arguments: | |
container (str): | |
A word to use for the thing being indexed | |
indices (sequence): | |
The indices to format. | |
""" | |
if not indices: | |
return container | |
return f"{container}[{']['.join(repr(index) for index in indices)}]" | |
def find_additional_properties(instance, schema): | |
""" | |
Return the set of additional properties for the given ``instance``. | |
Weeds out properties that should have been validated by ``properties`` and | |
/ or ``patternProperties``. | |
Assumes ``instance`` is dict-like already. | |
""" | |
properties = schema.get("properties", {}) | |
patterns = "|".join(schema.get("patternProperties", {})) | |
for property in instance: | |
if property not in properties: | |
if patterns and re.search(patterns, property): | |
continue | |
yield property | |
def extras_msg(extras): | |
""" | |
Create an error message for extra items or properties. | |
""" | |
verb = "was" if len(extras) == 1 else "were" | |
return ", ".join(repr(extra) for extra in extras), verb | |
def ensure_list(thing): | |
""" | |
Wrap ``thing`` in a list if it's a single str. | |
Otherwise, return it unchanged. | |
""" | |
if isinstance(thing, str): | |
return [thing] | |
return thing | |
def _mapping_equal(one, two): | |
""" | |
Check if two mappings are equal using the semantics of `equal`. | |
""" | |
if len(one) != len(two): | |
return False | |
return all( | |
key in two and equal(value, two[key]) | |
for key, value in one.items() | |
) | |
def _sequence_equal(one, two): | |
""" | |
Check if two sequences are equal using the semantics of `equal`. | |
""" | |
if len(one) != len(two): | |
return False | |
return all(equal(i, j) for i, j in zip(one, two)) | |
def equal(one, two): | |
""" | |
Check if two things are equal evading some Python type hierarchy semantics. | |
Specifically in JSON Schema, evade `bool` inheriting from `int`, | |
recursing into sequences to do the same. | |
""" | |
if isinstance(one, str) or isinstance(two, str): | |
return one == two | |
if isinstance(one, Sequence) and isinstance(two, Sequence): | |
return _sequence_equal(one, two) | |
if isinstance(one, Mapping) and isinstance(two, Mapping): | |
return _mapping_equal(one, two) | |
return unbool(one) == unbool(two) | |
def unbool(element, true=object(), false=object()): | |
""" | |
A hack to make True and 1 and False and 0 unique for ``uniq``. | |
""" | |
if element is True: | |
return true | |
elif element is False: | |
return false | |
return element | |
def uniq(container): | |
""" | |
Check if all of a container's elements are unique. | |
Tries to rely on the container being recursively sortable, or otherwise | |
falls back on (slow) brute force. | |
""" | |
try: | |
sort = sorted(unbool(i) for i in container) | |
sliced = itertools.islice(sort, 1, None) | |
for i, j in zip(sort, sliced): | |
if equal(i, j): | |
return False | |
except (NotImplementedError, TypeError): | |
seen = [] | |
for e in container: | |
e = unbool(e) | |
for i in seen: | |
if equal(i, e): | |
return False | |
seen.append(e) | |
return True | |
def find_evaluated_item_indexes_by_schema(validator, instance, schema): | |
""" | |
Get all indexes of items that get evaluated under the current schema. | |
Covers all keywords related to unevaluatedItems: items, prefixItems, if, | |
then, else, contains, unevaluatedItems, allOf, oneOf, anyOf | |
""" | |
if validator.is_type(schema, "boolean"): | |
return [] | |
evaluated_indexes = [] | |
if "items" in schema: | |
return list(range(len(instance))) | |
ref = schema.get("$ref") | |
if ref is not None: | |
resolved = validator._resolver.lookup(ref) | |
evaluated_indexes.extend( | |
find_evaluated_item_indexes_by_schema( | |
validator.evolve( | |
schema=resolved.contents, | |
_resolver=resolved.resolver, | |
), | |
instance, | |
resolved.contents, | |
), | |
) | |
dynamicRef = schema.get("$dynamicRef") | |
if dynamicRef is not None: | |
resolved = validator._resolver.lookup(dynamicRef) | |
evaluated_indexes.extend( | |
find_evaluated_item_indexes_by_schema( | |
validator.evolve( | |
schema=resolved.contents, | |
_resolver=resolved.resolver, | |
), | |
instance, | |
resolved.contents, | |
), | |
) | |
if "prefixItems" in schema: | |
evaluated_indexes += list(range(len(schema["prefixItems"]))) | |
if "if" in schema: | |
if validator.evolve(schema=schema["if"]).is_valid(instance): | |
evaluated_indexes += find_evaluated_item_indexes_by_schema( | |
validator, instance, schema["if"], | |
) | |
if "then" in schema: | |
evaluated_indexes += find_evaluated_item_indexes_by_schema( | |
validator, instance, schema["then"], | |
) | |
elif "else" in schema: | |
evaluated_indexes += find_evaluated_item_indexes_by_schema( | |
validator, instance, schema["else"], | |
) | |
for keyword in ["contains", "unevaluatedItems"]: | |
if keyword in schema: | |
for k, v in enumerate(instance): | |
if validator.evolve(schema=schema[keyword]).is_valid(v): | |
evaluated_indexes.append(k) | |
for keyword in ["allOf", "oneOf", "anyOf"]: | |
if keyword in schema: | |
for subschema in schema[keyword]: | |
errs = next(validator.descend(instance, subschema), None) | |
if errs is None: | |
evaluated_indexes += find_evaluated_item_indexes_by_schema( | |
validator, instance, subschema, | |
) | |
return evaluated_indexes | |
def find_evaluated_property_keys_by_schema(validator, instance, schema): | |
""" | |
Get all keys of items that get evaluated under the current schema. | |
Covers all keywords related to unevaluatedProperties: properties, | |
additionalProperties, unevaluatedProperties, patternProperties, | |
dependentSchemas, allOf, oneOf, anyOf, if, then, else | |
""" | |
if validator.is_type(schema, "boolean"): | |
return [] | |
evaluated_keys = [] | |
ref = schema.get("$ref") | |
if ref is not None: | |
resolved = validator._resolver.lookup(ref) | |
evaluated_keys.extend( | |
find_evaluated_property_keys_by_schema( | |
validator.evolve( | |
schema=resolved.contents, | |
_resolver=resolved.resolver, | |
), | |
instance, | |
resolved.contents, | |
), | |
) | |
dynamicRef = schema.get("$dynamicRef") | |
if dynamicRef is not None: | |
resolved = validator._resolver.lookup(dynamicRef) | |
evaluated_keys.extend( | |
find_evaluated_property_keys_by_schema( | |
validator.evolve( | |
schema=resolved.contents, | |
_resolver=resolved.resolver, | |
), | |
instance, | |
resolved.contents, | |
), | |
) | |
for keyword in [ | |
"properties", "additionalProperties", "unevaluatedProperties", | |
]: | |
if keyword in schema: | |
schema_value = schema[keyword] | |
if validator.is_type(schema_value, "boolean") and schema_value: | |
evaluated_keys += instance.keys() | |
elif validator.is_type(schema_value, "object"): | |
for property in schema_value: | |
if property in instance: | |
evaluated_keys.append(property) | |
if "patternProperties" in schema: | |
for property in instance: | |
for pattern in schema["patternProperties"]: | |
if re.search(pattern, property): | |
evaluated_keys.append(property) | |
if "dependentSchemas" in schema: | |
for property, subschema in schema["dependentSchemas"].items(): | |
if property not in instance: | |
continue | |
evaluated_keys += find_evaluated_property_keys_by_schema( | |
validator, instance, subschema, | |
) | |
for keyword in ["allOf", "oneOf", "anyOf"]: | |
if keyword in schema: | |
for subschema in schema[keyword]: | |
errs = next(validator.descend(instance, subschema), None) | |
if errs is None: | |
evaluated_keys += find_evaluated_property_keys_by_schema( | |
validator, instance, subschema, | |
) | |
if "if" in schema: | |
if validator.evolve(schema=schema["if"]).is_valid(instance): | |
evaluated_keys += find_evaluated_property_keys_by_schema( | |
validator, instance, schema["if"], | |
) | |
if "then" in schema: | |
evaluated_keys += find_evaluated_property_keys_by_schema( | |
validator, instance, schema["then"], | |
) | |
elif "else" in schema: | |
evaluated_keys += find_evaluated_property_keys_by_schema( | |
validator, instance, schema["else"], | |
) | |
return evaluated_keys | |