codekingpro/portable-devtools
114k
1from collections.abc import Mapping, MutableMapping, Sequence2from urllib.parse import urlsplit3import itertools4import re5 6 7class URIDict(MutableMapping):8 """9 Dictionary which uses normalized URIs as keys.10 """11 12 def normalize(self, uri):13 return urlsplit(uri).geturl()14 15 def __init__(self, *args, **kwargs):16 self.store = dict()17 self.store.update(*args, **kwargs)18 19 def __getitem__(self, uri):20 return self.store[self.normalize(uri)]21 22 def __setitem__(self, uri, value):23 self.store[self.normalize(uri)] = value24 25 def __delitem__(self, uri):26 del self.store[self.normalize(uri)]27 28 def __iter__(self):29 return iter(self.store)30 31 def __len__(self): # pragma: no cover -- untested, but to be removed32 return len(self.store)33 34 def __repr__(self): # pragma: no cover -- untested, but to be removed35 return repr(self.store)36 37 38class Unset:39 """40 An as-of-yet unset attribute or unprovided default parameter.41 """42 43 def __repr__(self): # pragma: no cover44 return "<unset>"45 46 47def format_as_index(container, indices):48 """49 Construct a single string containing indexing operations for the indices.50 51 For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"]52 53 Arguments:54 55 container (str):56 57 A word to use for the thing being indexed58 59 indices (sequence):60 61 The indices to format.62 63 """64 if not indices:65 return container66 return f"{container}[{']['.join(repr(index) for index in indices)}]"67 68 69def find_additional_properties(instance, schema):70 """71 Return the set of additional properties for the given ``instance``.72 73 Weeds out properties that should have been validated by ``properties`` and74 / or ``patternProperties``.75 76 Assumes ``instance`` is dict-like already.77 """78 properties = schema.get("properties", {})79 patterns = "|".join(schema.get("patternProperties", {}))80 for property in instance:81 if property not in properties:82 if patterns and re.search(patterns, property):83 continue84 yield property85 86 87def extras_msg(extras):88 """89 Create an error message for extra items or properties.90 """91 verb = "was" if len(extras) == 1 else "were"92 return ", ".join(repr(extra) for extra in extras), verb93 94 95def ensure_list(thing):96 """97 Wrap ``thing`` in a list if it's a single str.98 99 Otherwise, return it unchanged.100 """101 if isinstance(thing, str):102 return [thing]103 return thing104 105 106def _mapping_equal(one, two):107 """108 Check if two mappings are equal using the semantics of `equal`.109 """110 if len(one) != len(two):111 return False112 return all(113 key in two and equal(value, two[key])114 for key, value in one.items()115 )116 117 118def _sequence_equal(one, two):119 """120 Check if two sequences are equal using the semantics of `equal`.121 """122 if len(one) != len(two):123 return False124 return all(equal(i, j) for i, j in zip(one, two))125 126 127def equal(one, two):128 """129 Check if two things are equal evading some Python type hierarchy semantics.130 131 Specifically in JSON Schema, evade `bool` inheriting from `int`,132 recursing into sequences to do the same.133 """134 if one is two:135 return True136 if isinstance(one, str) or isinstance(two, str):137 return one == two138 if isinstance(one, Sequence) and isinstance(two, Sequence):139 return _sequence_equal(one, two)140 if isinstance(one, Mapping) and isinstance(two, Mapping):141 return _mapping_equal(one, two)142 return unbool(one) == unbool(two)143 144 145def unbool(element, true=object(), false=object()):146 """147 A hack to make True and 1 and False and 0 unique for ``uniq``.148 """149 if element is True:150 return true151 elif element is False:152 return false153 return element154 155 156def uniq(container):157 """158 Check if all of a container's elements are unique.159 160 Tries to rely on the container being recursively sortable, or otherwise161 falls back on (slow) brute force.162 """163 try:164 sort = sorted(unbool(i) for i in container)165 sliced = itertools.islice(sort, 1, None)166 167 for i, j in zip(sort, sliced):168 if equal(i, j):169 return False170 171 except (NotImplementedError, TypeError):172 seen = []173 for e in container:174 e = unbool(e)175 176 for i in seen:177 if equal(i, e):178 return False179 180 seen.append(e)181 return True182 183 184def find_evaluated_item_indexes_by_schema(validator, instance, schema):185 """186 Get all indexes of items that get evaluated under the current schema.187 188 Covers all keywords related to unevaluatedItems: items, prefixItems, if,189 then, else, contains, unevaluatedItems, allOf, oneOf, anyOf190 """191 if validator.is_type(schema, "boolean"):192 return []193 evaluated_indexes = []194 195 if "items" in schema:196 return list(range(len(instance)))197 198 ref = schema.get("$ref")199 if ref is not None:200 resolved = validator._resolver.lookup(ref)201 evaluated_indexes.extend(202 find_evaluated_item_indexes_by_schema(203 validator.evolve(204 schema=resolved.contents,205 _resolver=resolved.resolver,206 ),207 instance,208 resolved.contents,209 ),210 )211 212 dynamicRef = schema.get("$dynamicRef")213 if dynamicRef is not None:214 resolved = validator._resolver.lookup(dynamicRef)215 evaluated_indexes.extend(216 find_evaluated_item_indexes_by_schema(217 validator.evolve(218 schema=resolved.contents,219 _resolver=resolved.resolver,220 ),221 instance,222 resolved.contents,223 ),224 )225 226 if "prefixItems" in schema:227 evaluated_indexes += list(range(len(schema["prefixItems"])))228 229 if "if" in schema:230 if validator.evolve(schema=schema["if"]).is_valid(instance):231 evaluated_indexes += find_evaluated_item_indexes_by_schema(232 validator, instance, schema["if"],233 )234 if "then" in schema:235 evaluated_indexes += find_evaluated_item_indexes_by_schema(236 validator, instance, schema["then"],237 )238 elif "else" in schema:239 evaluated_indexes += find_evaluated_item_indexes_by_schema(240 validator, instance, schema["else"],241 )242 243 for keyword in ["contains", "unevaluatedItems"]:244 if keyword in schema:245 for k, v in enumerate(instance):246 if validator.evolve(schema=schema[keyword]).is_valid(v):247 evaluated_indexes.append(k)248 249 for keyword in ["allOf", "oneOf", "anyOf"]:250 if keyword in schema:251 for subschema in schema[keyword]:252 errs = next(validator.descend(instance, subschema), None)253 if errs is None:254 evaluated_indexes += find_evaluated_item_indexes_by_schema(255 validator, instance, subschema,256 )257 258 return evaluated_indexes259 260 261def find_evaluated_property_keys_by_schema(validator, instance, schema):262 """263 Get all keys of items that get evaluated under the current schema.264 265 Covers all keywords related to unevaluatedProperties: properties,266 additionalProperties, unevaluatedProperties, patternProperties,267 dependentSchemas, allOf, oneOf, anyOf, if, then, else268 """269 if validator.is_type(schema, "boolean"):270 return []271 evaluated_keys = []272 273 ref = schema.get("$ref")274 if ref is not None:275 resolved = validator._resolver.lookup(ref)276 evaluated_keys.extend(277 find_evaluated_property_keys_by_schema(278 validator.evolve(279 schema=resolved.contents,280 _resolver=resolved.resolver,281 ),282 instance,283 resolved.contents,284 ),285 )286 287 dynamicRef = schema.get("$dynamicRef")288 if dynamicRef is not None:289 resolved = validator._resolver.lookup(dynamicRef)290 evaluated_keys.extend(291 find_evaluated_property_keys_by_schema(292 validator.evolve(293 schema=resolved.contents,294 _resolver=resolved.resolver,295 ),296 instance,297 resolved.contents,298 ),299 )300 301 properties = schema.get("properties")302 if validator.is_type(properties, "object"):303 evaluated_keys += properties.keys() & instance.keys()304 305 for keyword in ["additionalProperties", "unevaluatedProperties"]:306 if (subschema := schema.get(keyword)) is None:307 continue308 evaluated_keys += (309 key310 for key, value in instance.items()311 if is_valid(validator.descend(value, subschema))312 )313 314 if "patternProperties" in schema:315 for property in instance:316 for pattern in schema["patternProperties"]:317 if re.search(pattern, property):318 evaluated_keys.append(property)319 320 if "dependentSchemas" in schema:321 for property, subschema in schema["dependentSchemas"].items():322 if property not in instance:323 continue324 evaluated_keys += find_evaluated_property_keys_by_schema(325 validator, instance, subschema,326 )327 328 for keyword in ["allOf", "oneOf", "anyOf"]:329 for subschema in schema.get(keyword, []):330 if not is_valid(validator.descend(instance, subschema)):331 continue332 evaluated_keys += find_evaluated_property_keys_by_schema(333 validator, instance, subschema,334 )335 336 if "if" in schema:337 if validator.evolve(schema=schema["if"]).is_valid(instance):338 evaluated_keys += find_evaluated_property_keys_by_schema(339 validator, instance, schema["if"],340 )341 if "then" in schema:342 evaluated_keys += find_evaluated_property_keys_by_schema(343 validator, instance, schema["then"],344 )345 elif "else" in schema:346 evaluated_keys += find_evaluated_property_keys_by_schema(347 validator, instance, schema["else"],348 )349 350 return evaluated_keys351 352 353def is_valid(errs_it):354 """Whether there are no errors in the given iterator."""355 return next(errs_it, None) is None356 