Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jsonschema/_utils.py: 15%

156 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-22 06:29 +0000

1from collections.abc import Mapping, MutableMapping, Sequence 

2from urllib.parse import urlsplit 

3import itertools 

4import re 

5 

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)] = value 

24 

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 removed 

32 return len(self.store) 

33 

34 def __repr__(self): # pragma: no cover -- untested, but to be removed 

35 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 cover 

44 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 indexed 

58 

59 indices (sequence): 

60 

61 The indices to format. 

62 """ 

63 if not indices: 

64 return container 

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

66 

67 

68def find_additional_properties(instance, schema): 

69 """ 

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

71 

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

73 / or ``patternProperties``. 

74 

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

76 """ 

77 properties = schema.get("properties", {}) 

78 patterns = "|".join(schema.get("patternProperties", {})) 

79 for property in instance: 

80 if property not in properties: 

81 if patterns and re.search(patterns, property): 

82 continue 

83 yield property 

84 

85 

86def extras_msg(extras): 

87 """ 

88 Create an error message for extra items or properties. 

89 """ 

90 verb = "was" if len(extras) == 1 else "were" 

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

92 

93 

94def ensure_list(thing): 

95 """ 

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

97 

98 Otherwise, return it unchanged. 

99 """ 

100 if isinstance(thing, str): 

101 return [thing] 

102 return thing 

103 

104 

105def _mapping_equal(one, two): 

106 """ 

107 Check if two mappings are equal using the semantics of `equal`. 

108 """ 

109 if len(one) != len(two): 

110 return False 

111 return all( 

112 key in two and equal(value, two[key]) 

113 for key, value in one.items() 

114 ) 

115 

116 

117def _sequence_equal(one, two): 

118 """ 

119 Check if two sequences are equal using the semantics of `equal`. 

120 """ 

121 if len(one) != len(two): 

122 return False 

123 return all(equal(i, j) for i, j in zip(one, two)) 

124 

125 

126def equal(one, two): 

127 """ 

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

129 

130 Specifically in JSON Schema, evade `bool` inheriting from `int`, 

131 recursing into sequences to do the same. 

132 """ 

133 if isinstance(one, str) or isinstance(two, str): 

134 return one == two 

135 if isinstance(one, Sequence) and isinstance(two, Sequence): 

136 return _sequence_equal(one, two) 

137 if isinstance(one, Mapping) and isinstance(two, Mapping): 

138 return _mapping_equal(one, two) 

139 return unbool(one) == unbool(two) 

140 

141 

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

143 """ 

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

145 """ 

146 if element is True: 

147 return true 

148 elif element is False: 

149 return false 

150 return element 

151 

152 

153def uniq(container): 

154 """ 

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

156 

157 Tries to rely on the container being recursively sortable, or otherwise 

158 falls back on (slow) brute force. 

159 """ 

160 try: 

161 sort = sorted(unbool(i) for i in container) 

162 sliced = itertools.islice(sort, 1, None) 

163 

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

165 if equal(i, j): 

166 return False 

167 

168 except (NotImplementedError, TypeError): 

169 seen = [] 

170 for e in container: 

171 e = unbool(e) 

172 

173 for i in seen: 

174 if equal(i, e): 

175 return False 

176 

177 seen.append(e) 

178 return True 

179 

180 

181def find_evaluated_item_indexes_by_schema(validator, instance, schema): 

182 """ 

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

184 

185 Covers all keywords related to unevaluatedItems: items, prefixItems, if, 

186 then, else, contains, unevaluatedItems, allOf, oneOf, anyOf 

187 """ 

188 if validator.is_type(schema, "boolean"): 

189 return [] 

190 evaluated_indexes = [] 

191 

192 if "items" in schema: 

193 return list(range(len(instance))) 

194 

195 ref = schema.get("$ref") 

196 if ref is not None: 

197 resolved = validator._resolver.lookup(ref) 

198 evaluated_indexes.extend( 

199 find_evaluated_item_indexes_by_schema( 

200 validator.evolve( 

201 schema=resolved.contents, 

202 _resolver=resolved.resolver, 

203 ), 

204 instance, 

205 resolved.contents, 

206 ), 

207 ) 

208 

209 dynamicRef = schema.get("$dynamicRef") 

210 if dynamicRef is not None: 

211 resolved = validator._resolver.lookup(dynamicRef) 

212 evaluated_indexes.extend( 

213 find_evaluated_item_indexes_by_schema( 

214 validator.evolve( 

215 schema=resolved.contents, 

216 _resolver=resolved.resolver, 

217 ), 

218 instance, 

219 resolved.contents, 

220 ), 

221 ) 

222 

223 if "prefixItems" in schema: 

224 evaluated_indexes += list(range(len(schema["prefixItems"]))) 

225 

226 if "if" in schema: 

227 if validator.evolve(schema=schema["if"]).is_valid(instance): 

228 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

229 validator, instance, schema["if"], 

230 ) 

231 if "then" in schema: 

232 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

233 validator, instance, schema["then"], 

234 ) 

235 elif "else" in schema: 

236 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

237 validator, instance, schema["else"], 

238 ) 

239 

240 for keyword in ["contains", "unevaluatedItems"]: 

241 if keyword in schema: 

242 for k, v in enumerate(instance): 

243 if validator.evolve(schema=schema[keyword]).is_valid(v): 

244 evaluated_indexes.append(k) 

245 

246 for keyword in ["allOf", "oneOf", "anyOf"]: 

247 if keyword in schema: 

248 for subschema in schema[keyword]: 

249 errs = next(validator.descend(instance, subschema), None) 

250 if errs is None: 

251 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

252 validator, instance, subschema, 

253 ) 

254 

255 return evaluated_indexes 

256 

257 

258def find_evaluated_property_keys_by_schema(validator, instance, schema): 

259 """ 

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

261 

262 Covers all keywords related to unevaluatedProperties: properties, 

263 additionalProperties, unevaluatedProperties, patternProperties, 

264 dependentSchemas, allOf, oneOf, anyOf, if, then, else 

265 """ 

266 if validator.is_type(schema, "boolean"): 

267 return [] 

268 evaluated_keys = [] 

269 

270 ref = schema.get("$ref") 

271 if ref is not None: 

272 resolved = validator._resolver.lookup(ref) 

273 evaluated_keys.extend( 

274 find_evaluated_property_keys_by_schema( 

275 validator.evolve( 

276 schema=resolved.contents, 

277 _resolver=resolved.resolver, 

278 ), 

279 instance, 

280 resolved.contents, 

281 ), 

282 ) 

283 

284 dynamicRef = schema.get("$dynamicRef") 

285 if dynamicRef is not None: 

286 resolved = validator._resolver.lookup(dynamicRef) 

287 evaluated_keys.extend( 

288 find_evaluated_property_keys_by_schema( 

289 validator.evolve( 

290 schema=resolved.contents, 

291 _resolver=resolved.resolver, 

292 ), 

293 instance, 

294 resolved.contents, 

295 ), 

296 ) 

297 

298 for keyword in [ 

299 "properties", "additionalProperties", "unevaluatedProperties", 

300 ]: 

301 if keyword in schema: 

302 schema_value = schema[keyword] 

303 if validator.is_type(schema_value, "boolean") and schema_value: 

304 evaluated_keys += instance.keys() 

305 

306 elif validator.is_type(schema_value, "object"): 

307 for property in schema_value: 

308 if property in instance: 

309 evaluated_keys.append(property) 

310 

311 if "patternProperties" in schema: 

312 for property in instance: 

313 for pattern in schema["patternProperties"]: 

314 if re.search(pattern, property): 

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 = next(validator.descend(instance, subschema), None) 

329 if errs is None: 

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 elif "else" in schema: 

344 evaluated_keys += find_evaluated_property_keys_by_schema( 

345 validator, instance, schema["else"], 

346 ) 

347 

348 return evaluated_keys