Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fastjsonschema/draft04.py: 12%
294 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 06:05 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 06:05 +0000
1import decimal
2import re
4from .exceptions import JsonSchemaDefinitionException
5from .generator import CodeGenerator, enforce_list
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}
18DOLLAR_FINDER = re.compile(r"(?<!\\)\$") # Finds any un-escaped $ (including inside []-sets)
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 }
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
71 @property
72 def global_state(self):
73 res = super().global_state
74 res['custom_formats'] = self._custom_formats
75 return res
77 def generate_type(self):
78 """
79 Validation of type. Can be one type or list of types.
81 .. code-block:: python
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))
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)
96 with self.l('if not isinstance({variable}, ({})){}:', python_types, extra):
97 self.exc('{name} must be {}', ' or '.join(types), rule='type')
99 def generate_enum(self):
100 """
101 Means that only value specified in the enum is valid.
103 .. code-block:: python
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')
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.
120 .. code-block:: python
122 {
123 'allOf': [
124 {'type': 'number'},
125 {'minimum': 5},
126 ],
127 }
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)
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.
139 .. code-block:: python
141 {
142 'anyOf': [
143 {'type': 'number', 'minimum': 10},
144 {'type': 'number', 'maximum': 5},
145 ],
146 }
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')
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')
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.
169 .. code-block:: python
171 {
172 'oneOf': [
173 {'type': 'number', 'multipleOf': 3},
174 {'type': 'number', 'multipleOf': 5},
175 ],
176 }
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')
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')
195 def generate_not(self):
196 """
197 Means that value have not to be valid by this definition.
199 .. code-block:: python
201 {'not': {'type': 'null'}}
203 Valid values for this definition are 'hello', 42, {} ... but not None.
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')
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')
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')
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')
248 def generate_format(self):
249 """
250 Means that value have to be in specified format. For example date, email or other.
252 .. code-block:: python
254 {'format': 'email'}
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_))
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')
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')
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')
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')
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')
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')
343 def generate_unique_items(self):
344 """
345 With Python 3.4 module ``timeit`` recommended this solutions:
347 .. code-block:: python
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
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')
376 def generate_items(self):
377 """
378 Means array is valid only when all items are valid by this definition.
380 .. code-block:: python
382 {
383 'items': [
384 {'type': 'integer'},
385 {'type': 'string'},
386 ],
387 }
389 Valid arrays are those with integers or strings, nothing else.
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
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']))
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')
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')
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')
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 self.l('{variable}__missing_keys = set({required}) - {variable}.keys()')
464 with self.l('if {variable}__missing_keys:'):
465 dynamic = 'str(sorted({variable}__missing_keys)) + " properties"'
466 self.exc('{name} must contain ', self.e(self._definition['required']), rule='required', append_to_msg=dynamic)
468 def generate_properties(self):
469 """
470 Means object with defined keys.
472 .. code-block:: python
474 {
475 'properties': {
476 'key': {'type': 'number'},
477 },
478 }
480 Valid object is containing key called 'key' and value any number.
481 """
482 self.create_variable_is_dict()
483 with self.l('if {variable}_is_dict:'):
484 self.create_variable_keys()
485 for key, prop_definition in self._definition['properties'].items():
486 key_name = re.sub(r'($[^a-zA-Z]|[^a-zA-Z0-9])', '', key)
487 if not isinstance(prop_definition, (dict, bool)):
488 raise JsonSchemaDefinitionException('{}[{}] must be object'.format(self._variable, key_name))
489 with self.l('if "{}" in {variable}_keys:', self.e(key)):
490 self.l('{variable}_keys.remove("{}")', self.e(key))
491 self.l('{variable}__{0} = {variable}["{1}"]', key_name, self.e(key))
492 self.generate_func_code_block(
493 prop_definition,
494 '{}__{}'.format(self._variable, key_name),
495 '{}.{}'.format(self._variable_name, self.e(key)),
496 clear_variables=True,
497 )
498 if self._use_default and isinstance(prop_definition, dict) and 'default' in prop_definition:
499 self.l('else: {variable}["{}"] = {}', self.e(key), repr(prop_definition['default']))
501 def generate_pattern_properties(self):
502 """
503 Means object with defined keys as patterns.
505 .. code-block:: python
507 {
508 'patternProperties': {
509 '^x': {'type': 'number'},
510 },
511 }
513 Valid object is containing key starting with a 'x' and value any number.
514 """
515 self.create_variable_is_dict()
516 with self.l('if {variable}_is_dict:'):
517 self.create_variable_keys()
518 for pattern, definition in self._definition['patternProperties'].items():
519 self._compile_regexps[pattern] = re.compile(pattern)
520 with self.l('for {variable}_key, {variable}_val in {variable}.items():'):
521 for pattern, definition in self._definition['patternProperties'].items():
522 with self.l('if REGEX_PATTERNS[{}].search({variable}_key):', repr(pattern)):
523 with self.l('if {variable}_key in {variable}_keys:'):
524 self.l('{variable}_keys.remove({variable}_key)')
525 self.generate_func_code_block(
526 definition,
527 '{}_val'.format(self._variable),
528 '{}.{{{}_key}}'.format(self._variable_name, self._variable),
529 clear_variables=True,
530 )
532 def generate_additional_properties(self):
533 """
534 Means object with keys with values defined by definition.
536 .. code-block:: python
538 {
539 'properties': {
540 'key': {'type': 'number'},
541 }
542 'additionalProperties': {'type': 'string'},
543 }
545 Valid object is containing key called 'key' and it's value any number and
546 any other key with any string.
547 """
548 self.create_variable_is_dict()
549 with self.l('if {variable}_is_dict:'):
550 self.create_variable_keys()
551 add_prop_definition = self._definition["additionalProperties"]
552 if add_prop_definition is True or add_prop_definition == {}:
553 return
554 if add_prop_definition:
555 properties_keys = list(self._definition.get("properties", {}).keys())
556 with self.l('for {variable}_key in {variable}_keys:'):
557 with self.l('if {variable}_key not in {}:', properties_keys):
558 self.l('{variable}_value = {variable}.get({variable}_key)')
559 self.generate_func_code_block(
560 add_prop_definition,
561 '{}_value'.format(self._variable),
562 '{}.{{{}_key}}'.format(self._variable_name, self._variable),
563 )
564 else:
565 with self.l('if {variable}_keys:'):
566 self.exc('{name} must not contain "+str({variable}_keys)+" properties', rule='additionalProperties')
568 def generate_dependencies(self):
569 """
570 Means when object has property, it needs to have also other property.
572 .. code-block:: python
574 {
575 'dependencies': {
576 'bar': ['foo'],
577 },
578 }
580 Valid object is containing only foo, both bar and foo or none of them, but not
581 object with only bar.
583 Since draft 06 definition can be boolean or empty array. True and empty array
584 means nothing, False means that key cannot be there at all.
585 """
586 self.create_variable_is_dict()
587 with self.l('if {variable}_is_dict:'):
588 is_empty = True
589 for key, values in self._definition["dependencies"].items():
590 if values == [] or values is True:
591 continue
592 is_empty = False
593 with self.l('if "{}" in {variable}:', self.e(key)):
594 if values is False:
595 self.exc('{} in {name} must not be there', key, rule='dependencies')
596 elif isinstance(values, list):
597 for value in values:
598 with self.l('if "{}" not in {variable}:', self.e(value)):
599 self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule='dependencies')
600 else:
601 self.generate_func_code_block(values, self._variable, self._variable_name, clear_variables=True)
602 if is_empty:
603 self.l('pass')