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

156 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +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 

64 if not indices: 

65 return container 

66 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`` and 

74 / or ``patternProperties``. 

75 

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

77 """ 

78 

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

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

81 for property in instance: 

82 if property not in properties: 

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

84 continue 

85 yield property 

86 

87 

88def extras_msg(extras): 

89 """ 

90 Create an error message for extra items or properties. 

91 """ 

92 

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

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

95 

96 

97def ensure_list(thing): 

98 """ 

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

100 

101 Otherwise, return it unchanged. 

102 """ 

103 

104 if isinstance(thing, str): 

105 return [thing] 

106 return thing 

107 

108 

109def _mapping_equal(one, two): 

110 """ 

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

112 """ 

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

114 return False 

115 return all( 

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

117 for key, value in one.items() 

118 ) 

119 

120 

121def _sequence_equal(one, two): 

122 """ 

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

124 """ 

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

126 return False 

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

128 

129 

130def equal(one, two): 

131 """ 

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

133 

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

135 recursing into sequences to do the same. 

136 """ 

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

138 return one == two 

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

140 return _sequence_equal(one, two) 

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

142 return _mapping_equal(one, two) 

143 return unbool(one) == unbool(two) 

144 

145 

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

147 """ 

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

149 """ 

150 

151 if element is True: 

152 return true 

153 elif element is False: 

154 return false 

155 return element 

156 

157 

158def uniq(container): 

159 """ 

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

161 

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

163 falls back on (slow) brute force. 

164 """ 

165 try: 

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

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

168 

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

170 if equal(i, j): 

171 return False 

172 

173 except (NotImplementedError, TypeError): 

174 seen = [] 

175 for e in container: 

176 e = unbool(e) 

177 

178 for i in seen: 

179 if equal(i, e): 

180 return False 

181 

182 seen.append(e) 

183 return True 

184 

185 

186def find_evaluated_item_indexes_by_schema(validator, instance, schema): 

187 """ 

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

189 

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

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

192 """ 

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

194 return [] 

195 evaluated_indexes = [] 

196 

197 if "items" in schema: 

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

199 

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

201 if ref is not None: 

202 resolved = validator._resolver.lookup(ref) 

203 evaluated_indexes.extend( 

204 find_evaluated_item_indexes_by_schema( 

205 validator.evolve( 

206 schema=resolved.contents, 

207 _resolver=resolved.resolver, 

208 ), 

209 instance, 

210 resolved.contents, 

211 ), 

212 ) 

213 

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

215 if dynamicRef is not None: 

216 resolved = validator._resolver.lookup(dynamicRef) 

217 evaluated_indexes.extend( 

218 find_evaluated_item_indexes_by_schema( 

219 validator.evolve( 

220 schema=resolved.contents, 

221 _resolver=resolved.resolver, 

222 ), 

223 instance, 

224 resolved.contents, 

225 ), 

226 ) 

227 

228 if "prefixItems" in schema: 

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

230 

231 if "if" in schema: 

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

233 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

235 ) 

236 if "then" in schema: 

237 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

239 ) 

240 else: 

241 if "else" in schema: 

242 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

244 ) 

245 

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

247 if keyword in schema: 

248 for k, v in enumerate(instance): 

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

250 evaluated_indexes.append(k) 

251 

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

253 if keyword in schema: 

254 for subschema in schema[keyword]: 

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

256 if errs is None: 

257 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

258 validator, instance, subschema, 

259 ) 

260 

261 return evaluated_indexes 

262 

263 

264def find_evaluated_property_keys_by_schema(validator, instance, schema): 

265 """ 

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

267 

268 Covers all keywords related to unevaluatedProperties: properties, 

269 additionalProperties, unevaluatedProperties, patternProperties, 

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

271 """ 

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

273 return [] 

274 evaluated_keys = [] 

275 

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

277 if ref is not None: 

278 resolved = validator._resolver.lookup(ref) 

279 evaluated_keys.extend( 

280 find_evaluated_property_keys_by_schema( 

281 validator.evolve( 

282 schema=resolved.contents, 

283 _resolver=resolved.resolver, 

284 ), 

285 instance, 

286 resolved.contents, 

287 ), 

288 ) 

289 

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

291 if dynamicRef is not None: 

292 resolved = validator._resolver.lookup(dynamicRef) 

293 evaluated_keys.extend( 

294 find_evaluated_property_keys_by_schema( 

295 validator.evolve( 

296 schema=resolved.contents, 

297 _resolver=resolved.resolver, 

298 ), 

299 instance, 

300 resolved.contents, 

301 ), 

302 ) 

303 

304 for keyword in [ 

305 "properties", "additionalProperties", "unevaluatedProperties", 

306 ]: 

307 if keyword in schema: 

308 schema_value = schema[keyword] 

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

310 evaluated_keys += instance.keys() 

311 

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

313 for property in schema_value: 

314 if property in instance: 

315 evaluated_keys.append(property) 

316 

317 if "patternProperties" in schema: 

318 for property in instance: 

319 for pattern in schema["patternProperties"]: 

320 if re.search(pattern, property): 

321 evaluated_keys.append(property) 

322 

323 if "dependentSchemas" in schema: 

324 for property, subschema in schema["dependentSchemas"].items(): 

325 if property not in instance: 

326 continue 

327 evaluated_keys += find_evaluated_property_keys_by_schema( 

328 validator, instance, subschema, 

329 ) 

330 

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

332 if keyword in schema: 

333 for subschema in schema[keyword]: 

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

335 if errs is None: 

336 evaluated_keys += find_evaluated_property_keys_by_schema( 

337 validator, instance, subschema, 

338 ) 

339 

340 if "if" in schema: 

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

342 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

344 ) 

345 if "then" in schema: 

346 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

348 ) 

349 else: 

350 if "else" in schema: 

351 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

353 ) 

354 

355 return evaluated_keys