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

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

300 statements  

1import decimal 

2import re 

3 

4from .exceptions import JsonSchemaDefinitionException 

5from .generator import CodeGenerator, enforce_list 

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]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-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): 

38 super().__init__(definition, resolver) 

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: {}'.format(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 with self.l('if {variable} not in {enum}:'): 

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

114 

115 def generate_all_of(self): 

116 """ 

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

118 one big definition. 

119 

120 .. code-block:: python 

121 

122 { 

123 'allOf': [ 

124 {'type': 'number'}, 

125 {'minimum': 5}, 

126 ], 

127 } 

128 

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

130 """ 

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

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

133 

134 def generate_any_of(self): 

135 """ 

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

137 by all of them. 

138 

139 .. code-block:: python 

140 

141 { 

142 'anyOf': [ 

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

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

145 ], 

146 } 

147 

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

149 """ 

150 self._any_or_one_of_count += 1 

151 count = self._any_or_one_of_count 

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

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

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

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

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

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

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

159 self.l('except JsonSchemaValueException: pass') 

160 

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

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

163 

164 def generate_one_of(self): 

165 """ 

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

167 by two or more of them. 

168 

169 .. code-block:: python 

170 

171 { 

172 'oneOf': [ 

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

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

175 ], 

176 } 

177 

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

179 """ 

180 self._any_or_one_of_count += 1 

181 count = self._any_or_one_of_count 

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

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

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

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

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

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

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

189 self.l('except JsonSchemaValueException: pass') 

190 

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

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

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

194 

195 def generate_not(self): 

196 """ 

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

198 

199 .. code-block:: python 

200 

201 {'not': {'type': 'null'}} 

202 

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

204 

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

206 means everything is invalid. 

207 """ 

208 not_definition = self._definition['not'] 

209 if not_definition is True: 

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

211 elif not_definition is False: 

212 return 

213 elif not not_definition: 

214 with self.l('if {}:', self._variable): 

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

216 else: 

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

218 self.generate_func_code_block(not_definition, self._variable, self._variable_name) 

219 self.l('except JsonSchemaValueException: pass') 

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

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

222 

223 def generate_min_length(self): 

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

225 self.create_variable_with_length() 

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

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

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

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

230 

231 def generate_max_length(self): 

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

233 self.create_variable_with_length() 

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

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

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

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

238 

239 def generate_pattern(self): 

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

241 pattern = self._definition['pattern'] 

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

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

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

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

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

247 

248 def generate_format(self): 

249 """ 

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

251 

252 .. code-block:: python 

253 

254 {'format': 'email'} 

255 

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

257 """ 

258 if not self._use_formats: 

259 return 

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

261 format_ = self._definition['format'] 

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

263 if format_ in self._custom_formats: 

264 custom_format = self._custom_formats[format_] 

265 if isinstance(custom_format, str): 

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

267 else: 

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

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

270 elif format_ in self.FORMAT_REGEXS: 

271 format_regex = self.FORMAT_REGEXS[format_] 

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

273 # Format regex is used only in meta schemas. 

274 elif format_ == 'regex': 

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

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

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

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

279 else: 

280 raise JsonSchemaDefinitionException('Unknown format: {}'.format(format_)) 

281 

282 

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

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

285 if not regexp_name in self._compile_regexps: 

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

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

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

289 

290 def generate_minimum(self): 

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

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

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

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

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

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

297 else: 

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

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

300 

301 def generate_maximum(self): 

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

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

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

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

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

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

308 else: 

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

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

311 

312 def generate_multiple_of(self): 

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

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

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

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

317 # because for example 19.01 / 0.01 = 1901.0000000000002. 

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

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

320 else: 

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

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

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

324 

325 def generate_min_items(self): 

326 self.create_variable_is_list() 

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

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

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

330 self.create_variable_with_length() 

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

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

333 

334 def generate_max_items(self): 

335 self.create_variable_is_list() 

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

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

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

339 self.create_variable_with_length() 

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

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

342 

343 def generate_unique_items(self): 

344 """ 

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

346 

347 .. code-block:: python 

348 

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

350 0.5839540958404541 

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

352 0.7094449996948242 

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

354 2.0819358825683594 

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

356 2.1439831256866455 

357 """ 

358 unique_definition = self._definition['uniqueItems'] 

359 if not unique_definition: 

360 return 

361 

362 self.create_variable_is_list() 

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

364 self.l( 

365 'def fn(var): ' 

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

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

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

369 'for v in var) ' 

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

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

372 self.create_variable_with_length() 

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

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

375 

376 def generate_items(self): 

377 """ 

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

379 

380 .. code-block:: python 

381 

382 { 

383 'items': [ 

384 {'type': 'integer'}, 

385 {'type': 'string'}, 

386 ], 

387 } 

388 

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

390 

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

392 means everything is invalid. 

393 """ 

394 items_definition = self._definition['items'] 

395 if items_definition is True: 

396 return 

397 

398 self.create_variable_is_list() 

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

400 self.create_variable_with_length() 

401 if items_definition is False: 

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

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

404 elif isinstance(items_definition, list): 

405 for idx, item_definition in enumerate(items_definition): 

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

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

408 self.generate_func_code_block( 

409 item_definition, 

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

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

412 ) 

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

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

415 

416 if 'additionalItems' in self._definition: 

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

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

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

420 else: 

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

422 count = self.generate_func_code_block( 

423 self._definition['additionalItems'], 

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

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

426 ) 

427 if count == 0: 

428 self.l('pass') 

429 else: 

430 if items_definition: 

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

432 count = self.generate_func_code_block( 

433 items_definition, 

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

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

436 ) 

437 if count == 0: 

438 self.l('pass') 

439 

440 def generate_min_properties(self): 

441 self.create_variable_is_dict() 

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

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

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

445 self.create_variable_with_length() 

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

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

448 

449 def generate_max_properties(self): 

450 self.create_variable_is_dict() 

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

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

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

454 self.create_variable_with_length() 

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

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

457 

458 def generate_required(self): 

459 self.create_variable_is_dict() 

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

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

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

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

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

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

466 not_possible = [ 

467 prop 

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

469 if 

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

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

472 ] 

473 if not_possible: 

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

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

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

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

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

479 

480 def generate_properties(self): 

481 """ 

482 Means object with defined keys. 

483 

484 .. code-block:: python 

485 

486 { 

487 'properties': { 

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

489 }, 

490 } 

491 

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

493 """ 

494 self.create_variable_is_dict() 

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

496 self.create_variable_keys() 

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

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

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

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

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

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

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

504 self.generate_func_code_block( 

505 prop_definition, 

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

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

508 clear_variables=True, 

509 ) 

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

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

512 

513 def generate_pattern_properties(self): 

514 """ 

515 Means object with defined keys as patterns. 

516 

517 .. code-block:: python 

518 

519 { 

520 'patternProperties': { 

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

522 }, 

523 } 

524 

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

526 """ 

527 self.create_variable_is_dict() 

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

529 self.create_variable_keys() 

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

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

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

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

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

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

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

537 self.generate_func_code_block( 

538 definition, 

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

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

541 clear_variables=True, 

542 ) 

543 

544 def generate_additional_properties(self): 

545 """ 

546 Means object with keys with values defined by definition. 

547 

548 .. code-block:: python 

549 

550 { 

551 'properties': { 

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

553 } 

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

555 } 

556 

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

558 any other key with any string. 

559 """ 

560 self.create_variable_is_dict() 

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

562 self.create_variable_keys() 

563 add_prop_definition = self._definition["additionalProperties"] 

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

565 return 

566 if add_prop_definition: 

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

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

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

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

571 self.generate_func_code_block( 

572 add_prop_definition, 

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

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

575 ) 

576 else: 

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

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

579 

580 def generate_dependencies(self): 

581 """ 

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

583 

584 .. code-block:: python 

585 

586 { 

587 'dependencies': { 

588 'bar': ['foo'], 

589 }, 

590 } 

591 

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

593 object with only bar. 

594 

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

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

597 """ 

598 self.create_variable_is_dict() 

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

600 is_empty = True 

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

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

603 continue 

604 is_empty = False 

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

606 if values is False: 

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

608 elif isinstance(values, list): 

609 for value in values: 

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

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

612 else: 

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

614 if is_empty: 

615 self.l('pass')