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

1from collections.abc import Mapping, MutableMapping, Sequence 

2from urllib.parse import urlsplit 

3import itertools 

4import json 

5import re 

6import sys 

7 

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 

13 

14 

15class URIDict(MutableMapping): 

16 """ 

17 Dictionary which uses normalized URIs as keys. 

18 """ 

19 

20 def normalize(self, uri): 

21 return urlsplit(uri).geturl() 

22 

23 def __init__(self, *args, **kwargs): 

24 self.store = dict() 

25 self.store.update(*args, **kwargs) 

26 

27 def __getitem__(self, uri): 

28 return self.store[self.normalize(uri)] 

29 

30 def __setitem__(self, uri, value): 

31 self.store[self.normalize(uri)] = value 

32 

33 def __delitem__(self, uri): 

34 del self.store[self.normalize(uri)] 

35 

36 def __iter__(self): 

37 return iter(self.store) 

38 

39 def __len__(self): 

40 return len(self.store) 

41 

42 def __repr__(self): 

43 return repr(self.store) 

44 

45 

46class Unset: 

47 """ 

48 An as-of-yet unset attribute or unprovided default parameter. 

49 """ 

50 

51 def __repr__(self): 

52 return "<unset>" 

53 

54 

55def load_schema(name): 

56 """ 

57 Load a schema from ./schemas/``name``.json and return it. 

58 """ 

59 

60 path = resources.files(__package__).joinpath(f"schemas/{name}.json") 

61 data = path.read_text(encoding="utf-8") 

62 return json.loads(data) 

63 

64 

65def format_as_index(container, indices): 

66 """ 

67 Construct a single string containing indexing operations for the indices. 

68 

69 For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"] 

70 

71 Arguments: 

72 

73 container (str): 

74 

75 A word to use for the thing being indexed 

76 

77 indices (sequence): 

78 

79 The indices to format. 

80 """ 

81 

82 if not indices: 

83 return container 

84 return f"{container}[{']['.join(repr(index) for index in indices)}]" 

85 

86 

87def find_additional_properties(instance, schema): 

88 """ 

89 Return the set of additional properties for the given ``instance``. 

90 

91 Weeds out properties that should have been validated by ``properties`` and 

92 / or ``patternProperties``. 

93 

94 Assumes ``instance`` is dict-like already. 

95 """ 

96 

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 

104 

105 

106def extras_msg(extras): 

107 """ 

108 Create an error message for extra items or properties. 

109 """ 

110 

111 if len(extras) == 1: 

112 verb = "was" 

113 else: 

114 verb = "were" 

115 return ", ".join(repr(extra) for extra in sorted(extras)), verb 

116 

117 

118def ensure_list(thing): 

119 """ 

120 Wrap ``thing`` in a list if it's a single str. 

121 

122 Otherwise, return it unchanged. 

123 """ 

124 

125 if isinstance(thing, str): 

126 return [thing] 

127 return thing 

128 

129 

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 ) 

140 

141 

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)) 

149 

150 

151def equal(one, two): 

152 """ 

153 Check if two things are equal evading some Python type hierarchy semantics. 

154 

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) 

165 

166 

167def unbool(element, true=object(), false=object()): 

168 """ 

169 A hack to make True and 1 and False and 0 unique for ``uniq``. 

170 """ 

171 

172 if element is True: 

173 return true 

174 elif element is False: 

175 return false 

176 return element 

177 

178 

179def uniq(container): 

180 """ 

181 Check if all of a container's elements are unique. 

182 

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) 

189 

190 for i, j in zip(sort, sliced): 

191 if equal(i, j): 

192 return False 

193 

194 except (NotImplementedError, TypeError): 

195 seen = [] 

196 for e in container: 

197 e = unbool(e) 

198 

199 for i in seen: 

200 if equal(i, e): 

201 return False 

202 

203 seen.append(e) 

204 return True 

205 

206 

207def find_evaluated_item_indexes_by_schema(validator, instance, schema): 

208 """ 

209 Get all indexes of items that get evaluated under the current schema 

210 

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 = [] 

217 

218 if "items" in schema: 

219 return list(range(0, len(instance))) 

220 

221 if "$ref" in schema: 

222 scope, resolved = validator.resolver.resolve(schema["$ref"]) 

223 validator.resolver.push_scope(scope) 

224 

225 try: 

226 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

227 validator, instance, resolved, 

228 ) 

229 finally: 

230 validator.resolver.pop_scope() 

231 

232 if "prefixItems" in schema: 

233 evaluated_indexes += list(range(0, len(schema["prefixItems"]))) 

234 

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 ) 

249 

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) 

255 

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 ) 

264 

265 return evaluated_indexes 

266 

267 

268def find_evaluated_property_keys_by_schema(validator, instance, schema): 

269 """ 

270 Get all keys of items that get evaluated under the current schema 

271 

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 = [] 

279 

280 if "$ref" in schema: 

281 scope, resolved = validator.resolver.resolve(schema["$ref"]) 

282 validator.resolver.push_scope(scope) 

283 

284 try: 

285 evaluated_keys += find_evaluated_property_keys_by_schema( 

286 validator, instance, resolved, 

287 ) 

288 finally: 

289 validator.resolver.pop_scope() 

290 

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) 

301 

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) 

308 

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) 

316 

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 ) 

324 

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 ) 

333 

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 ) 

348 

349 return evaluated_keys