Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/fastjsonschema/draft04.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

341 statements  

1import decimal 

2import re 

3 

4from .exceptions import JsonSchemaDefinitionException 

5from .generator import VALIDATION_EXCEPTIONS, CodeGenerator, enforce_list, repr_default 

6 

7 

8JSON_TYPE_TO_PYTHON_TYPE = { 

9 'null': 'NoneType', 

10 'boolean': 'bool', 

11 'number': 'int, float, Decimal', 

12 'integer': 'int', 

13 'string': 'str', 

14 'array': 'list, tuple', 

15 'object': 'dict', 

16} 

17 

18DOLLAR_FINDER = re.compile(r"(?<!\\)\$") # Finds any un-escaped $ (including inside []-sets) 

19 

20 

21# pylint: disable=too-many-instance-attributes,too-many-public-methods 

22class CodeGeneratorDraft04(CodeGenerator): 

23 # pylint: disable=line-too-long 

24 # I was thinking about using ipaddress module instead of regexps for example, but it's big 

25 # difference in performance. With a module I got this difference: over 100 ms with a module 

26 # vs. 9 ms with a regex! Other modules are also ineffective or not available in standard 

27 # library. Some regexps are not 100% precise but good enough, fast and without dependencies. 

28 FORMAT_REGEXS = { 

29 'date-time': r'^\d{4}-[01]\d-[0-3]\d(t|T)[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:[+-][0-2]\d:[0-5]\d|[+-][0-2]\d[0-5]\d|z|Z)\Z', 

30 'email': r'^(?!.*\.\..*@)[^@.][^@]*(?<!\.)@[^@]+\.[^@]+\Z', 

31 'hostname': r'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9])\Z', 

32 'ipv4': r'^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\Z', 

33 'ipv6': r'^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)\Z', 

34 'uri': r'^\w+:(\/?\/?)[^\s]+\Z', 

35 } 

36 

37 def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True, detailed_exceptions=True, fast_fail=True): 

38 super().__init__(definition, resolver, detailed_exceptions, fast_fail) 

39 self._custom_formats = formats 

40 self._use_formats = use_formats 

41 self._use_default = use_default 

42 self._json_keywords_to_function.update(( 

43 ('type', self.generate_type), 

44 ('enum', self.generate_enum), 

45 ('allOf', self.generate_all_of), 

46 ('anyOf', self.generate_any_of), 

47 ('oneOf', self.generate_one_of), 

48 ('not', self.generate_not), 

49 ('minLength', self.generate_min_length), 

50 ('maxLength', self.generate_max_length), 

51 ('pattern', self.generate_pattern), 

52 ('format', self.generate_format), 

53 ('minimum', self.generate_minimum), 

54 ('maximum', self.generate_maximum), 

55 ('multipleOf', self.generate_multiple_of), 

56 ('minItems', self.generate_min_items), 

57 ('maxItems', self.generate_max_items), 

58 ('uniqueItems', self.generate_unique_items), 

59 ('items', self.generate_items), 

60 ('minProperties', self.generate_min_properties), 

61 ('maxProperties', self.generate_max_properties), 

62 ('required', self.generate_required), 

63 # Check dependencies before properties generates default values. 

64 ('dependencies', self.generate_dependencies), 

65 ('properties', self.generate_properties), 

66 ('patternProperties', self.generate_pattern_properties), 

67 ('additionalProperties', self.generate_additional_properties), 

68 )) 

69 self._any_or_one_of_count = 0 

70 

71 @property 

72 def global_state(self): 

73 res = super().global_state 

74 res['custom_formats'] = self._custom_formats 

75 return res 

76 

77 def generate_type(self): 

78 """ 

79 Validation of type. Can be one type or list of types. 

80 

81 .. code-block:: python 

82 

83 {'type': 'string'} 

84 {'type': ['string', 'number']} 

85 """ 

86 types = enforce_list(self._definition['type']) 

87 try: 

88 python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types) 

89 except KeyError as exc: 

90 raise JsonSchemaDefinitionException('Unknown type') from exc 

91 

92 extra = '' 

93 if ('number' in types or 'integer' in types) and 'boolean' not in types: 

94 extra = ' or isinstance({variable}, bool)'.format(variable=self._variable) 

95 

96 with self.l('if not isinstance({variable}, ({})){}:', python_types, extra): 

97 self.exc('{name} must be {}', ' or '.join(types), rule='type') 

98 

99 def generate_enum(self): 

100 """ 

101 Means that only value specified in the enum is valid. 

102 

103 .. code-block:: python 

104 

105 { 

106 'enum': ['a', 'b'], 

107 } 

108 """ 

109 enum = self._definition['enum'] 

110 if not isinstance(enum, (list, tuple)): 

111 raise JsonSchemaDefinitionException('enum must be an array') 

112 matches = ' or '.join(self._enum_value_matches(self._variable, value) for value in enum) 

113 if matches: 

114 with self.l('if not ({}):', matches): 

115 self.exc('{name} must be one of {}', self.e(enum), rule='enum') 

116 else: 

117 with self.l('if True:'): 

118 self.exc('{name} must be one of {}', self.e(enum), rule='enum') 

119 

120 def _enum_value_matches(self, var, value): 

121 if isinstance(value, bool): 

122 return 'isinstance({var}, bool) and {var} is {val}'.format(var=var, val=repr(value)) 

123 if isinstance(value, (int, float)) and not isinstance(value, bool): 

124 return ( 

125 'isinstance({var}, (int, float)) and not isinstance({var}, bool) and {var} == {val}' 

126 ).format(var=var, val=repr(value)) 

127 if value is None: 

128 return '{var} is None'.format(var=var) 

129 if isinstance(value, str): 

130 return 'isinstance({var}, str) and {var} == {val}'.format(var=var, val=repr(value)) 

131 if isinstance(value, dict): 

132 if not value: 

133 return 'isinstance({var}, dict) and not {var}'.format(var=var) 

134 key_checks = ' and '.join( 

135 '{key!r} in {var} and {match}'.format( 

136 key=key, 

137 var=var, 

138 match=self._enum_value_matches('{var}[{key!r}]'.format(var=var, key=key), item), 

139 ) 

140 for key, item in value.items() 

141 ) 

142 return 'isinstance({var}, dict) and len({var}) == {size} and {checks}'.format( 

143 var=var, size=len(value), checks=key_checks, 

144 ) 

145 if isinstance(value, (list, tuple)): 

146 if not value: 

147 return 'isinstance({var}, (list, tuple)) and not {var}'.format(var=var) 

148 item_checks = ' and '.join( 

149 self._enum_value_matches('{var}[{index}]'.format(var=var, index=index), item) 

150 for index, item in enumerate(value) 

151 ) 

152 return 'isinstance({var}, (list, tuple)) and len({var}) == {size} and {checks}'.format( 

153 var=var, size=len(value), checks=item_checks, 

154 ) 

155 return '{var} == {val}'.format(var=var, val=repr(value)) 

156 

157 def generate_all_of(self): 

158 """ 

159 Means that value have to be valid by all of those definitions. It's like put it in 

160 one big definition. 

161 

162 .. code-block:: python 

163 

164 { 

165 'allOf': [ 

166 {'type': 'number'}, 

167 {'minimum': 5}, 

168 ], 

169 } 

170 

171 Valid values for this definition are 5, 6, 7, ... but not 4 or 'abc' for example. 

172 """ 

173 for definition_item in self._definition['allOf']: 

174 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True) 

175 

176 def generate_any_of(self): 

177 """ 

178 Means that value have to be valid by any of those definitions. It can also be valid 

179 by all of them. 

180 

181 .. code-block:: python 

182 

183 { 

184 'anyOf': [ 

185 {'type': 'number', 'minimum': 10}, 

186 {'type': 'number', 'maximum': 5}, 

187 ], 

188 } 

189 

190 Valid values for this definition are 3, 4, 5, 10, 11, ... but not 8 for example. 

191 """ 

192 self._any_or_one_of_count += 1 

193 count = self._any_or_one_of_count 

194 self.l('{variable}_any_of_count{count} = 0', count=count) 

195 for definition_item in self._definition['anyOf']: 

196 # When we know it's passing (at least once), we do not need to do another expensive try-except. 

197 with self.l('if not {variable}_any_of_count{count}:', count=count, optimize=False): 

198 with self.l('try:', optimize=False): 

199 with self.trial_validation(): 

200 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True) 

201 self.l('{variable}_any_of_count{count} += 1', count=count) 

202 self.l('except {}: pass', VALIDATION_EXCEPTIONS) 

203 

204 with self.l('if not {variable}_any_of_count{count}:', count=count, optimize=False): 

205 self.exc('{name} cannot be validated by any definition', rule='anyOf') 

206 

207 def generate_one_of(self): 

208 """ 

209 Means that value have to be valid by only one of those definitions. It can't be valid 

210 by two or more of them. 

211 

212 .. code-block:: python 

213 

214 { 

215 'oneOf': [ 

216 {'type': 'number', 'multipleOf': 3}, 

217 {'type': 'number', 'multipleOf': 5}, 

218 ], 

219 } 

220 

221 Valid values for this definition are 3, 5, 6, ... but not 15 for example. 

222 """ 

223 self._any_or_one_of_count += 1 

224 count = self._any_or_one_of_count 

225 self.l('{variable}_one_of_count{count} = 0', count=count) 

226 for definition_item in self._definition['oneOf']: 

227 # When we know it's failing (one of means exactly once), we do not need to do another expensive try-except. 

228 with self.l('if {variable}_one_of_count{count} < 2:', count=count, optimize=False): 

229 with self.l('try:', optimize=False): 

230 with self.trial_validation(): 

231 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True) 

232 self.l('{variable}_one_of_count{count} += 1', count=count) 

233 self.l('except {}: pass', VALIDATION_EXCEPTIONS) 

234 

235 with self.l('if {variable}_one_of_count{count} != 1:', count=count): 

236 dynamic = '" (" + str({variable}_one_of_count{}) + " matches found)"' 

237 self.exc('{name} must be valid exactly by one definition', count, append_to_msg=dynamic, rule='oneOf') 

238 

239 def generate_not(self): 

240 """ 

241 Means that value have not to be valid by this definition. 

242 

243 .. code-block:: python 

244 

245 {'not': {'type': 'null'}} 

246 

247 Valid values for this definition are 'hello', 42, {} ... but not None. 

248 

249 Since draft 06 definition can be boolean. False means nothing, True 

250 means everything is invalid. 

251 """ 

252 not_definition = self._definition['not'] 

253 if not_definition is True: 

254 self.exc('{name} must not be there', rule='not') 

255 elif not_definition is False: 

256 return 

257 elif not not_definition: 

258 self.exc('{name} must NOT match a disallowed definition', rule='not') 

259 else: 

260 with self.l('try:', optimize=False): 

261 code_len = len(self._code) 

262 with self.trial_validation(): 

263 self.generate_func_code_block(not_definition, self._variable, self._variable_name, clear_variables=True) 

264 if len(self._code) == code_len: 

265 self.l('pass') 

266 self.l('except {}: pass', VALIDATION_EXCEPTIONS) 

267 with self.l('else:'): 

268 self.exc('{name} must NOT match a disallowed definition', rule='not') 

269 

270 def generate_min_length(self): 

271 with self.l('if isinstance({variable}, str):'): 

272 self.create_variable_with_length() 

273 if not isinstance(self._definition['minLength'], (int, float)): 

274 raise JsonSchemaDefinitionException('minLength must be a number') 

275 with self.l('if {variable}_len < {minLength}:'): 

276 self.exc('{name} must be longer than or equal to {minLength} characters', rule='minLength') 

277 

278 def generate_max_length(self): 

279 with self.l('if isinstance({variable}, str):'): 

280 self.create_variable_with_length() 

281 if not isinstance(self._definition['maxLength'], (int, float)): 

282 raise JsonSchemaDefinitionException('maxLength must be a number') 

283 with self.l('if {variable}_len > {maxLength}:'): 

284 self.exc('{name} must be shorter than or equal to {maxLength} characters', rule='maxLength') 

285 

286 def generate_pattern(self): 

287 with self.l('if isinstance({variable}, str):'): 

288 pattern = self._definition['pattern'] 

289 safe_pattern = pattern.replace('\\', '\\\\').replace('"', '\\"') 

290 end_of_string_fixed_pattern = DOLLAR_FINDER.sub(r'\\Z', pattern) 

291 self._compile_regexps[pattern] = re.compile(end_of_string_fixed_pattern) 

292 with self.l('if not REGEX_PATTERNS[{}].search({variable}):', repr(pattern)): 

293 self.exc('{name} must match pattern {}', safe_pattern, rule='pattern') 

294 

295 def generate_format(self): 

296 """ 

297 Means that value have to be in specified format. For example date, email or other. 

298 

299 .. code-block:: python 

300 

301 {'format': 'email'} 

302 

303 Valid value for this definition is user@example.com but not @username 

304 """ 

305 if not self._use_formats: 

306 return 

307 format_ = self._definition['format'] 

308 if format_ not in self._custom_formats and format_ not in self.FORMAT_REGEXS and format_ != 'regex': 

309 return 

310 with self.l('if isinstance({variable}, str):'): 

311 # Checking custom formats - user is allowed to override default formats. 

312 if format_ in self._custom_formats: 

313 custom_format = self._custom_formats[format_] 

314 if isinstance(custom_format, str): 

315 self._generate_format(format_, format_ + '_re_pattern', custom_format) 

316 else: 

317 with self.l('if not custom_formats["{}"]({variable}):', format_): 

318 self.exc('{name} must be {}', format_, rule='format') 

319 elif format_ in self.FORMAT_REGEXS: 

320 format_regex = self.FORMAT_REGEXS[format_] 

321 self._generate_format(format_, format_ + '_re_pattern', format_regex) 

322 # Format regex is used only in meta schemas. 

323 elif format_ == 'regex': 

324 self._extra_imports_lines = ['import re'] 

325 with self.l('try:', optimize=False): 

326 self.l('re.compile({variable})') 

327 with self.l('except Exception:'): 

328 self.exc('{name} must be a valid regex', rule='format') 

329 

330 

331 def _generate_format(self, format_name, regexp_name, regexp): 

332 if self._definition['format'] == format_name: 

333 if not regexp_name in self._compile_regexps: 

334 self._compile_regexps[regexp_name] = re.compile(regexp) 

335 with self.l('if not REGEX_PATTERNS["{}"].match({variable}):', regexp_name): 

336 self.exc('{name} must be {}', format_name, rule='format') 

337 

338 def generate_minimum(self): 

339 with self.l('if isinstance({variable}, (int, float, Decimal)):'): 

340 if not isinstance(self._definition['minimum'], (int, float, decimal.Decimal)): 

341 raise JsonSchemaDefinitionException('minimum must be a number') 

342 if self._definition.get('exclusiveMinimum', False): 

343 with self.l('if {variable} <= {minimum}:'): 

344 self.exc('{name} must be bigger than {minimum}', rule='minimum') 

345 else: 

346 with self.l('if {variable} < {minimum}:'): 

347 self.exc('{name} must be bigger than or equal to {minimum}', rule='minimum') 

348 

349 def generate_maximum(self): 

350 with self.l('if isinstance({variable}, (int, float, Decimal)):'): 

351 if not isinstance(self._definition['maximum'], (int, float, decimal.Decimal)): 

352 raise JsonSchemaDefinitionException('maximum must be a number') 

353 if self._definition.get('exclusiveMaximum', False): 

354 with self.l('if {variable} >= {maximum}:'): 

355 self.exc('{name} must be smaller than {maximum}', rule='maximum') 

356 else: 

357 with self.l('if {variable} > {maximum}:'): 

358 self.exc('{name} must be smaller than or equal to {maximum}', rule='maximum') 

359 

360 def generate_multiple_of(self): 

361 with self.l('if isinstance({variable}, (int, float, Decimal)):'): 

362 if not isinstance(self._definition['multipleOf'], (int, float, decimal.Decimal)): 

363 raise JsonSchemaDefinitionException('multipleOf must be a number') 

364 # For proper multiplication check of floats we need to use decimals, 

365 # because for example 19.01 / 0.01 = 1901.0000000000002. 

366 if isinstance(self._definition['multipleOf'], float): 

367 self.l('quotient = Decimal(repr({variable})) / Decimal(repr({multipleOf}))') 

368 else: 

369 self.l('quotient = {variable} / {multipleOf}') 

370 with self.l('if int(quotient) != quotient:'): 

371 self.exc('{name} must be multiple of {multipleOf}', rule='multipleOf') 

372 # For example, 1e308 / 0.123456789 

373 with self.l('if {variable} / {multipleOf} == float("inf"):'): 

374 self.exc('inifinity reached', rule='multipleOf') 

375 

376 def generate_min_items(self): 

377 self.create_variable_is_list() 

378 with self.l('if {variable}_is_list:'): 

379 if not isinstance(self._definition['minItems'], (int, float)): 

380 raise JsonSchemaDefinitionException('minItems must be a number') 

381 self.create_variable_with_length() 

382 with self.l('if {variable}_len < {minItems}:'): 

383 self.exc('{name} must contain at least {minItems} items', rule='minItems') 

384 

385 def generate_max_items(self): 

386 self.create_variable_is_list() 

387 with self.l('if {variable}_is_list:'): 

388 if not isinstance(self._definition['maxItems'], (int, float)): 

389 raise JsonSchemaDefinitionException('maxItems must be a number') 

390 self.create_variable_with_length() 

391 with self.l('if {variable}_len > {maxItems}:'): 

392 self.exc('{name} must contain less than or equal to {maxItems} items', rule='maxItems') 

393 

394 def generate_unique_items(self): 

395 """ 

396 With Python 3.4 module ``timeit`` recommended this solutions: 

397 

398 .. code-block:: python 

399 

400 >>> timeit.timeit("len(x) > len(set(x))", "x=range(100)+range(100)", number=100000) 

401 0.5839540958404541 

402 >>> timeit.timeit("len({}.fromkeys(x)) == len(x)", "x=range(100)+range(100)", number=100000) 

403 0.7094449996948242 

404 >>> timeit.timeit("seen = set(); any(i in seen or seen.add(i) for i in x)", "x=range(100)+range(100)", number=100000) 

405 2.0819358825683594 

406 >>> timeit.timeit("np.unique(x).size == len(x)", "x=range(100)+range(100); import numpy as np", number=100000) 

407 2.1439831256866455 

408 """ 

409 unique_definition = self._definition['uniqueItems'] 

410 if not unique_definition: 

411 return 

412 

413 self.create_variable_is_list() 

414 with self.l('if {variable}_is_list:'): 

415 self.l( 

416 'def fn(var): ' 

417 'return frozenset(dict((k, fn(v)) ' 

418 'for k, v in var.items()).items()) ' 

419 'if hasattr(var, "items") else tuple(fn(v) ' 

420 'for v in var) ' 

421 'if isinstance(var, (dict, list)) else str(var) ' 

422 'if isinstance(var, bool) else var') 

423 self.create_variable_with_length() 

424 with self.l('if {variable}_len > len(set(fn({variable}_x) for {variable}_x in {variable})):'): 

425 self.exc('{name} must contain unique items', rule='uniqueItems') 

426 

427 def generate_items(self): 

428 """ 

429 Means array is valid only when all items are valid by this definition. 

430 

431 .. code-block:: python 

432 

433 { 

434 'items': [ 

435 {'type': 'integer'}, 

436 {'type': 'string'}, 

437 ], 

438 } 

439 

440 Valid arrays are those with integers or strings, nothing else. 

441 

442 Since draft 06 definition can be also boolean. True means nothing, False 

443 means everything is invalid. 

444 """ 

445 items_definition = self._definition['items'] 

446 if items_definition is True: 

447 return 

448 

449 self.create_variable_is_list() 

450 with self.l('if {variable}_is_list:'): 

451 self.create_variable_with_length() 

452 if items_definition is False: 

453 with self.l('if {variable}:'): 

454 self.exc('{name} must not be there', rule='items') 

455 elif isinstance(items_definition, list): 

456 for idx, item_definition in enumerate(items_definition): 

457 with self.l('if {variable}_len > {}:', idx): 

458 self.l('{variable}__{0} = {variable}[{0}]', idx) 

459 self.generate_func_code_block( 

460 item_definition, 

461 '{}__{}'.format(self._variable, idx), 

462 '{}[{}]'.format(self._variable_name, idx), 

463 ) 

464 if self._use_default and isinstance(item_definition, dict) and 'default' in item_definition: 

465 self.l('else: {variable}.append({})', repr_default(item_definition['default'])) 

466 

467 if 'additionalItems' in self._definition: 

468 if self._definition['additionalItems'] is False: 

469 with self.l('if {variable}_len > {}:', len(items_definition)): 

470 self.exc('{name} must contain only specified items', rule='items') 

471 else: 

472 with self.l('for {variable}_x, {variable}_item in enumerate({variable}[{0}:], {0}):', len(items_definition)): 

473 code_len = len(self._code) 

474 self.generate_func_code_block( 

475 self._definition['additionalItems'], 

476 '{}_item'.format(self._variable), 

477 '{}[{{{}_x}}]'.format(self._variable_name, self._variable), 

478 ) 

479 if len(self._code) == code_len: 

480 self.l('pass') 

481 else: 

482 if items_definition: 

483 with self.l('for {variable}_x, {variable}_item in enumerate({variable}):'): 

484 code_len = len(self._code) 

485 self.generate_func_code_block( 

486 items_definition, 

487 '{}_item'.format(self._variable), 

488 '{}[{{{}_x}}]'.format(self._variable_name, self._variable), 

489 ) 

490 if len(self._code) == code_len: 

491 self.l('pass') 

492 

493 def generate_min_properties(self): 

494 self.create_variable_is_dict() 

495 with self.l('if {variable}_is_dict:'): 

496 if not isinstance(self._definition['minProperties'], (int, float)): 

497 raise JsonSchemaDefinitionException('minProperties must be a number') 

498 self.create_variable_with_length() 

499 with self.l('if {variable}_len < {minProperties}:'): 

500 self.exc('{name} must contain at least {minProperties} properties', rule='minProperties') 

501 

502 def generate_max_properties(self): 

503 self.create_variable_is_dict() 

504 with self.l('if {variable}_is_dict:'): 

505 if not isinstance(self._definition['maxProperties'], (int, float)): 

506 raise JsonSchemaDefinitionException('maxProperties must be a number') 

507 self.create_variable_with_length() 

508 with self.l('if {variable}_len > {maxProperties}:'): 

509 self.exc('{name} must contain less than or equal to {maxProperties} properties', rule='maxProperties') 

510 

511 def generate_required(self): 

512 self.create_variable_is_dict() 

513 with self.l('if {variable}_is_dict:'): 

514 if not isinstance(self._definition['required'], (list, tuple)): 

515 raise JsonSchemaDefinitionException('required must be an array') 

516 if len(self._definition['required']) != len(set(self._definition['required'])): 

517 raise JsonSchemaDefinitionException('required must contain unique elements') 

518 if not self._definition.get('additionalProperties', True): 

519 not_possible = [ 

520 prop 

521 for prop in self._definition['required'] 

522 if 

523 prop not in self._definition.get('properties', {}) 

524 and not any(re.search(regex, prop) for regex in self._definition.get('patternProperties', {})) 

525 ] 

526 if not_possible: 

527 raise JsonSchemaDefinitionException('{}: items {} are required but not allowed'.format(self._variable, not_possible)) 

528 self.l('{variable}__missing_keys = set({required}) - {variable}.keys()') 

529 with self.l('if {variable}__missing_keys:'): 

530 dynamic = 'str(sorted({variable}__missing_keys)) + " properties"' 

531 self.exc('{name} must contain ', self.e(self._definition['required']), rule='required', append_to_msg=dynamic) 

532 

533 def generate_properties(self): 

534 """ 

535 Means object with defined keys. 

536 

537 .. code-block:: python 

538 

539 { 

540 'properties': { 

541 'key': {'type': 'number'}, 

542 }, 

543 } 

544 

545 Valid object is containing key called 'key' and value any number. 

546 """ 

547 self.create_variable_is_dict() 

548 with self.l('if {variable}_is_dict:'): 

549 self.create_variable_keys() 

550 for key, prop_definition in self._definition['properties'].items(): 

551 key_name = re.sub(r'($[^a-zA-Z]|[^a-zA-Z0-9])', '', key) 

552 if not isinstance(prop_definition, (dict, bool)): 

553 raise JsonSchemaDefinitionException('{}[{}] must be object'.format(self._variable, key_name)) 

554 with self.l('if "{}" in {variable}_keys:', self.e(key)): 

555 self.l('{variable}_keys.remove("{}")', self.e(key)) 

556 self.l('{variable}__{0} = {variable}["{1}"]', key_name, self.e(key)) 

557 self.generate_func_code_block( 

558 prop_definition, 

559 '{}__{}'.format(self._variable, key_name), 

560 '{}.{}'.format(self._variable_name, self.e(key)), 

561 clear_variables=True, 

562 ) 

563 if self._use_default and isinstance(prop_definition, dict) and 'default' in prop_definition: 

564 self.l('else: {variable}["{}"] = {}', self.e(key), repr_default(prop_definition['default'])) 

565 

566 def generate_pattern_properties(self): 

567 """ 

568 Means object with defined keys as patterns. 

569 

570 .. code-block:: python 

571 

572 { 

573 'patternProperties': { 

574 '^x': {'type': 'number'}, 

575 }, 

576 } 

577 

578 Valid object is containing key starting with a 'x' and value any number. 

579 """ 

580 self.create_variable_is_dict() 

581 with self.l('if {variable}_is_dict:'): 

582 self.create_variable_keys() 

583 pattern_prop_definition = self._definition['patternProperties'] 

584 if pattern_prop_definition == {}: 

585 return 

586 for pattern, definition in pattern_prop_definition.items(): 

587 self._compile_regexps[pattern] = re.compile(pattern) 

588 with self.l('for {variable}_key, {variable}_val in {variable}.items():'): 

589 for pattern, definition in self._definition['patternProperties'].items(): 

590 with self.l('if REGEX_PATTERNS[{}].search({variable}_key):', repr(pattern)): 

591 with self.l('if {variable}_key in {variable}_keys:'): 

592 self.l('{variable}_keys.remove({variable}_key)') 

593 self.generate_func_code_block( 

594 definition, 

595 '{}_val'.format(self._variable), 

596 '{}.{{{}_key}}'.format(self._variable_name, self._variable), 

597 clear_variables=True, 

598 ) 

599 

600 def generate_additional_properties(self): 

601 """ 

602 Means object with keys with values defined by definition. 

603 

604 .. code-block:: python 

605 

606 { 

607 'properties': { 

608 'key': {'type': 'number'}, 

609 } 

610 'additionalProperties': {'type': 'string'}, 

611 } 

612 

613 Valid object is containing key called 'key' and it's value any number and 

614 any other key with any string. 

615 """ 

616 self.create_variable_is_dict() 

617 with self.l('if {variable}_is_dict:'): 

618 self.create_variable_keys() 

619 add_prop_definition = self._definition["additionalProperties"] 

620 if add_prop_definition is True or add_prop_definition == {}: 

621 return 

622 if add_prop_definition: 

623 properties_keys = list(self._definition.get("properties", {}).keys()) 

624 with self.l('for {variable}_key in {variable}_keys:'): 

625 with self.l('if {variable}_key not in {}:', properties_keys): 

626 self.l('{variable}_value = {variable}.get({variable}_key)') 

627 self.generate_func_code_block( 

628 add_prop_definition, 

629 '{}_value'.format(self._variable), 

630 '{}.{{{}_key}}'.format(self._variable_name, self._variable), 

631 ) 

632 else: 

633 with self.l('if {variable}_keys:'): 

634 self.exc('{name} must not contain "+str({variable}_keys)+" properties', rule='additionalProperties') 

635 

636 def generate_dependencies(self): 

637 """ 

638 Means when object has property, it needs to have also other property. 

639 

640 .. code-block:: python 

641 

642 { 

643 'dependencies': { 

644 'bar': ['foo'], 

645 }, 

646 } 

647 

648 Valid object is containing only foo, both bar and foo or none of them, but not 

649 object with only bar. 

650 

651 Since draft 06 definition can be boolean or empty array. True and empty array 

652 means nothing, False means that key cannot be there at all. 

653 """ 

654 self.create_variable_is_dict() 

655 with self.l('if {variable}_is_dict:'): 

656 is_empty = True 

657 for key, values in self._definition["dependencies"].items(): 

658 if values == [] or values is True: 

659 continue 

660 is_empty = False 

661 with self.l('if "{}" in {variable}:', self.e(key)): 

662 if values is False: 

663 self.exc('{} in {name} must not be there', self.e(key), rule='dependencies') 

664 elif isinstance(values, list): 

665 for value in values: 

666 with self.l('if "{}" not in {variable}:', self.e(value)): 

667 self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule='dependencies') 

668 else: 

669 code_len = len(self._code) 

670 self.generate_func_code_block(values, self._variable, self._variable_name, clear_variables=True) 

671 if len(self._code) == code_len: 

672 self.l('pass') 

673 if is_empty: 

674 self.l('pass')