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

150 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:30 +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): 

32 return len(self.store) 

33 

34 def __repr__(self): 

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 sorted(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 if "$ref" in schema: 

201 resolved = validator._resolver.lookup(schema["$ref"]) 

202 evaluated_indexes.extend( 

203 find_evaluated_item_indexes_by_schema( 

204 validator.evolve( 

205 schema=resolved.contents, 

206 _resolver=resolved.resolver, 

207 ), 

208 instance, 

209 resolved.contents, 

210 ), 

211 ) 

212 

213 if "prefixItems" in schema: 

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

215 

216 if "if" in schema: 

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

218 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

220 ) 

221 if "then" in schema: 

222 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

224 ) 

225 else: 

226 if "else" in schema: 

227 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

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

229 ) 

230 

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

232 if keyword in schema: 

233 for k, v in enumerate(instance): 

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

235 evaluated_indexes.append(k) 

236 

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

238 if keyword in schema: 

239 for subschema in schema[keyword]: 

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

241 if errs is None: 

242 evaluated_indexes += find_evaluated_item_indexes_by_schema( 

243 validator, instance, subschema, 

244 ) 

245 

246 return evaluated_indexes 

247 

248 

249def find_evaluated_property_keys_by_schema(validator, instance, schema): 

250 """ 

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

252 

253 Covers all keywords related to unevaluatedProperties: properties, 

254 additionalProperties, unevaluatedProperties, patternProperties, 

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

256 """ 

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

258 return [] 

259 evaluated_keys = [] 

260 

261 if "$ref" in schema: 

262 resolved = validator._resolver.lookup(schema["$ref"]) 

263 evaluated_keys.extend( 

264 find_evaluated_property_keys_by_schema( 

265 validator.evolve( 

266 schema=resolved.contents, 

267 _resolver=resolved.resolver, 

268 ), 

269 instance, 

270 resolved.contents, 

271 ), 

272 ) 

273 

274 for keyword in [ 

275 "properties", "additionalProperties", "unevaluatedProperties", 

276 ]: 

277 if keyword in schema: 

278 schema_value = schema[keyword] 

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

280 evaluated_keys += instance.keys() 

281 

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

283 for property in schema_value: 

284 if property in instance: 

285 evaluated_keys.append(property) 

286 

287 if "patternProperties" in schema: 

288 for property in instance: 

289 for pattern in schema["patternProperties"]: 

290 if re.search(pattern, property): 

291 evaluated_keys.append(property) 

292 

293 if "dependentSchemas" in schema: 

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

295 if property not in instance: 

296 continue 

297 evaluated_keys += find_evaluated_property_keys_by_schema( 

298 validator, instance, subschema, 

299 ) 

300 

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

302 if keyword in schema: 

303 for subschema in schema[keyword]: 

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

305 if errs is None: 

306 evaluated_keys += find_evaluated_property_keys_by_schema( 

307 validator, instance, subschema, 

308 ) 

309 

310 if "if" in schema: 

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

312 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

314 ) 

315 if "then" in schema: 

316 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

318 ) 

319 else: 

320 if "else" in schema: 

321 evaluated_keys += find_evaluated_property_keys_by_schema( 

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

323 ) 

324 

325 return evaluated_keys