Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jsonschema/_utils.py: 22%
167 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
1from collections.abc import Mapping, MutableMapping, Sequence
2from urllib.parse import urlsplit
3import itertools
4import json
5import re
6import sys
8# The files() API was added in Python 3.9.
9if sys.version_info >= (3, 9): # pragma: no cover
10 from importlib import resources
11else: # pragma: no cover
12 import importlib_resources as resources # type: ignore
15class URIDict(MutableMapping):
16 """
17 Dictionary which uses normalized URIs as keys.
18 """
20 def normalize(self, uri):
21 return urlsplit(uri).geturl()
23 def __init__(self, *args, **kwargs):
24 self.store = dict()
25 self.store.update(*args, **kwargs)
27 def __getitem__(self, uri):
28 return self.store[self.normalize(uri)]
30 def __setitem__(self, uri, value):
31 self.store[self.normalize(uri)] = value
33 def __delitem__(self, uri):
34 del self.store[self.normalize(uri)]
36 def __iter__(self):
37 return iter(self.store)
39 def __len__(self):
40 return len(self.store)
42 def __repr__(self):
43 return repr(self.store)
46class Unset:
47 """
48 An as-of-yet unset attribute or unprovided default parameter.
49 """
51 def __repr__(self):
52 return "<unset>"
55def load_schema(name):
56 """
57 Load a schema from ./schemas/``name``.json and return it.
58 """
60 path = resources.files(__package__).joinpath(f"schemas/{name}.json")
61 data = path.read_text(encoding="utf-8")
62 return json.loads(data)
65def format_as_index(container, indices):
66 """
67 Construct a single string containing indexing operations for the indices.
69 For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"]
71 Arguments:
73 container (str):
75 A word to use for the thing being indexed
77 indices (sequence):
79 The indices to format.
80 """
82 if not indices:
83 return container
84 return f"{container}[{']['.join(repr(index) for index in indices)}]"
87def find_additional_properties(instance, schema):
88 """
89 Return the set of additional properties for the given ``instance``.
91 Weeds out properties that should have been validated by ``properties`` and
92 / or ``patternProperties``.
94 Assumes ``instance`` is dict-like already.
95 """
97 properties = schema.get("properties", {})
98 patterns = "|".join(schema.get("patternProperties", {}))
99 for property in instance:
100 if property not in properties:
101 if patterns and re.search(patterns, property):
102 continue
103 yield property
106def extras_msg(extras):
107 """
108 Create an error message for extra items or properties.
109 """
111 if len(extras) == 1:
112 verb = "was"
113 else:
114 verb = "were"
115 return ", ".join(repr(extra) for extra in sorted(extras)), verb
118def ensure_list(thing):
119 """
120 Wrap ``thing`` in a list if it's a single str.
122 Otherwise, return it unchanged.
123 """
125 if isinstance(thing, str):
126 return [thing]
127 return thing
130def _mapping_equal(one, two):
131 """
132 Check if two mappings are equal using the semantics of `equal`.
133 """
134 if len(one) != len(two):
135 return False
136 return all(
137 key in two and equal(value, two[key])
138 for key, value in one.items()
139 )
142def _sequence_equal(one, two):
143 """
144 Check if two sequences are equal using the semantics of `equal`.
145 """
146 if len(one) != len(two):
147 return False
148 return all(equal(i, j) for i, j in zip(one, two))
151def equal(one, two):
152 """
153 Check if two things are equal evading some Python type hierarchy semantics.
155 Specifically in JSON Schema, evade `bool` inheriting from `int`,
156 recursing into sequences to do the same.
157 """
158 if isinstance(one, str) or isinstance(two, str):
159 return one == two
160 if isinstance(one, Sequence) and isinstance(two, Sequence):
161 return _sequence_equal(one, two)
162 if isinstance(one, Mapping) and isinstance(two, Mapping):
163 return _mapping_equal(one, two)
164 return unbool(one) == unbool(two)
167def unbool(element, true=object(), false=object()):
168 """
169 A hack to make True and 1 and False and 0 unique for ``uniq``.
170 """
172 if element is True:
173 return true
174 elif element is False:
175 return false
176 return element
179def uniq(container):
180 """
181 Check if all of a container's elements are unique.
183 Tries to rely on the container being recursively sortable, or otherwise
184 falls back on (slow) brute force.
185 """
186 try:
187 sort = sorted(unbool(i) for i in container)
188 sliced = itertools.islice(sort, 1, None)
190 for i, j in zip(sort, sliced):
191 if equal(i, j):
192 return False
194 except (NotImplementedError, TypeError):
195 seen = []
196 for e in container:
197 e = unbool(e)
199 for i in seen:
200 if equal(i, e):
201 return False
203 seen.append(e)
204 return True
207def find_evaluated_item_indexes_by_schema(validator, instance, schema):
208 """
209 Get all indexes of items that get evaluated under the current schema
211 Covers all keywords related to unevaluatedItems: items, prefixItems, if,
212 then, else, contains, unevaluatedItems, allOf, oneOf, anyOf
213 """
214 if validator.is_type(schema, "boolean"):
215 return []
216 evaluated_indexes = []
218 if "items" in schema:
219 return list(range(0, len(instance)))
221 if "$ref" in schema:
222 scope, resolved = validator.resolver.resolve(schema["$ref"])
223 validator.resolver.push_scope(scope)
225 try:
226 evaluated_indexes += find_evaluated_item_indexes_by_schema(
227 validator, instance, resolved,
228 )
229 finally:
230 validator.resolver.pop_scope()
232 if "prefixItems" in schema:
233 evaluated_indexes += list(range(0, len(schema["prefixItems"])))
235 if "if" in schema:
236 if validator.evolve(schema=schema["if"]).is_valid(instance):
237 evaluated_indexes += find_evaluated_item_indexes_by_schema(
238 validator, instance, schema["if"],
239 )
240 if "then" in schema:
241 evaluated_indexes += find_evaluated_item_indexes_by_schema(
242 validator, instance, schema["then"],
243 )
244 else:
245 if "else" in schema:
246 evaluated_indexes += find_evaluated_item_indexes_by_schema(
247 validator, instance, schema["else"],
248 )
250 for keyword in ["contains", "unevaluatedItems"]:
251 if keyword in schema:
252 for k, v in enumerate(instance):
253 if validator.evolve(schema=schema[keyword]).is_valid(v):
254 evaluated_indexes.append(k)
256 for keyword in ["allOf", "oneOf", "anyOf"]:
257 if keyword in schema:
258 for subschema in schema[keyword]:
259 errs = list(validator.descend(instance, subschema))
260 if not errs:
261 evaluated_indexes += find_evaluated_item_indexes_by_schema(
262 validator, instance, subschema,
263 )
265 return evaluated_indexes
268def find_evaluated_property_keys_by_schema(validator, instance, schema):
269 """
270 Get all keys of items that get evaluated under the current schema
272 Covers all keywords related to unevaluatedProperties: properties,
273 additionalProperties, unevaluatedProperties, patternProperties,
274 dependentSchemas, allOf, oneOf, anyOf, if, then, else
275 """
276 if validator.is_type(schema, "boolean"):
277 return []
278 evaluated_keys = []
280 if "$ref" in schema:
281 scope, resolved = validator.resolver.resolve(schema["$ref"])
282 validator.resolver.push_scope(scope)
284 try:
285 evaluated_keys += find_evaluated_property_keys_by_schema(
286 validator, instance, resolved,
287 )
288 finally:
289 validator.resolver.pop_scope()
291 for keyword in [
292 "properties", "additionalProperties", "unevaluatedProperties",
293 ]:
294 if keyword in schema:
295 if validator.is_type(schema[keyword], "boolean"):
296 for property, value in instance.items():
297 if validator.evolve(schema=schema[keyword]).is_valid(
298 {property: value},
299 ):
300 evaluated_keys.append(property)
302 if validator.is_type(schema[keyword], "object"):
303 for property, subschema in schema[keyword].items():
304 if property in instance and validator.evolve(
305 schema=subschema,
306 ).is_valid(instance[property]):
307 evaluated_keys.append(property)
309 if "patternProperties" in schema:
310 for property, value in instance.items():
311 for pattern, _ in schema["patternProperties"].items():
312 if re.search(pattern, property) and validator.evolve(
313 schema=schema["patternProperties"],
314 ).is_valid({property: value}):
315 evaluated_keys.append(property)
317 if "dependentSchemas" in schema:
318 for property, subschema in schema["dependentSchemas"].items():
319 if property not in instance:
320 continue
321 evaluated_keys += find_evaluated_property_keys_by_schema(
322 validator, instance, subschema,
323 )
325 for keyword in ["allOf", "oneOf", "anyOf"]:
326 if keyword in schema:
327 for subschema in schema[keyword]:
328 errs = list(validator.descend(instance, subschema))
329 if not errs:
330 evaluated_keys += find_evaluated_property_keys_by_schema(
331 validator, instance, subschema,
332 )
334 if "if" in schema:
335 if validator.evolve(schema=schema["if"]).is_valid(instance):
336 evaluated_keys += find_evaluated_property_keys_by_schema(
337 validator, instance, schema["if"],
338 )
339 if "then" in schema:
340 evaluated_keys += find_evaluated_property_keys_by_schema(
341 validator, instance, schema["then"],
342 )
343 else:
344 if "else" in schema:
345 evaluated_keys += find_evaluated_property_keys_by_schema(
346 validator, instance, schema["else"],
347 )
349 return evaluated_keys