Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fastjsonschema/draft04.py: 12%
290 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +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):
38 super().__init__(definition, resolver)
39 self._custom_formats = formats
40 self._use_default = use_default
41 self._json_keywords_to_function.update((
42 ('type', self.generate_type),
43 ('enum', self.generate_enum),
44 ('allOf', self.generate_all_of),
45 ('anyOf', self.generate_any_of),
46 ('oneOf', self.generate_one_of),
47 ('not', self.generate_not),
48 ('minLength', self.generate_min_length),
49 ('maxLength', self.generate_max_length),
50 ('pattern', self.generate_pattern),
51 ('format', self.generate_format),
52 ('minimum', self.generate_minimum),
53 ('maximum', self.generate_maximum),
54 ('multipleOf', self.generate_multiple_of),
55 ('minItems', self.generate_min_items),
56 ('maxItems', self.generate_max_items),
57 ('uniqueItems', self.generate_unique_items),
58 ('items', self.generate_items),
59 ('minProperties', self.generate_min_properties),
60 ('maxProperties', self.generate_max_properties),
61 ('required', self.generate_required),
62 # Check dependencies before properties generates default values.
63 ('dependencies', self.generate_dependencies),
64 ('properties', self.generate_properties),
65 ('patternProperties', self.generate_pattern_properties),
66 ('additionalProperties', self.generate_additional_properties),
67 ))
68 self._any_or_one_of_count = 0
70 @property
71 def global_state(self):
72 res = super().global_state
73 res['custom_formats'] = self._custom_formats
74 return res
76 def generate_type(self):
77 """
78 Validation of type. Can be one type or list of types.
80 .. code-block:: python
82 {'type': 'string'}
83 {'type': ['string', 'number']}
84 """
85 types = enforce_list(self._definition['type'])
86 try:
87 python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types)
88 except KeyError as exc:
89 raise JsonSchemaDefinitionException('Unknown type: {}'.format(exc))
91 extra = ''
92 if ('number' in types or 'integer' in types) and 'boolean' not in types:
93 extra = ' or isinstance({variable}, bool)'.format(variable=self._variable)
95 with self.l('if not isinstance({variable}, ({})){}:', python_types, extra):
96 self.exc('{name} must be {}', ' or '.join(types), rule='type')
98 def generate_enum(self):
99 """
100 Means that only value specified in the enum is valid.
102 .. code-block:: python
104 {
105 'enum': ['a', 'b'],
106 }
107 """
108 enum = self._definition['enum']
109 if not isinstance(enum, (list, tuple)):
110 raise JsonSchemaDefinitionException('enum must be an array')
111 with self.l('if {variable} not in {enum}:'):
112 self.exc('{name} must be one of {}', self.e(enum), rule='enum')
114 def generate_all_of(self):
115 """
116 Means that value have to be valid by all of those definitions. It's like put it in
117 one big definition.
119 .. code-block:: python
121 {
122 'allOf': [
123 {'type': 'number'},
124 {'minimum': 5},
125 ],
126 }
128 Valid values for this definition are 5, 6, 7, ... but not 4 or 'abc' for example.
129 """
130 for definition_item in self._definition['allOf']:
131 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True)
133 def generate_any_of(self):
134 """
135 Means that value have to be valid by any of those definitions. It can also be valid
136 by all of them.
138 .. code-block:: python
140 {
141 'anyOf': [
142 {'type': 'number', 'minimum': 10},
143 {'type': 'number', 'maximum': 5},
144 ],
145 }
147 Valid values for this definition are 3, 4, 5, 10, 11, ... but not 8 for example.
148 """
149 self._any_or_one_of_count += 1
150 count = self._any_or_one_of_count
151 self.l('{variable}_any_of_count{count} = 0', count=count)
152 for definition_item in self._definition['anyOf']:
153 # When we know it's passing (at least once), we do not need to do another expensive try-except.
154 with self.l('if not {variable}_any_of_count{count}:', count=count, optimize=False):
155 with self.l('try:', optimize=False):
156 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True)
157 self.l('{variable}_any_of_count{count} += 1', count=count)
158 self.l('except JsonSchemaValueException: pass')
160 with self.l('if not {variable}_any_of_count{count}:', count=count, optimize=False):
161 self.exc('{name} cannot be validated by any definition', rule='anyOf')
163 def generate_one_of(self):
164 """
165 Means that value have to be valid by only one of those definitions. It can't be valid
166 by two or more of them.
168 .. code-block:: python
170 {
171 'oneOf': [
172 {'type': 'number', 'multipleOf': 3},
173 {'type': 'number', 'multipleOf': 5},
174 ],
175 }
177 Valid values for this definition are 3, 5, 6, ... but not 15 for example.
178 """
179 self._any_or_one_of_count += 1
180 count = self._any_or_one_of_count
181 self.l('{variable}_one_of_count{count} = 0', count=count)
182 for definition_item in self._definition['oneOf']:
183 # When we know it's failing (one of means exactly once), we do not need to do another expensive try-except.
184 with self.l('if {variable}_one_of_count{count} < 2:', count=count, optimize=False):
185 with self.l('try:', optimize=False):
186 self.generate_func_code_block(definition_item, self._variable, self._variable_name, clear_variables=True)
187 self.l('{variable}_one_of_count{count} += 1', count=count)
188 self.l('except JsonSchemaValueException: pass')
190 with self.l('if {variable}_one_of_count{count} != 1:', count=count):
191 dynamic = '" (" + str({variable}_one_of_count{}) + " matches found)"'
192 self.exc('{name} must be valid exactly by one definition', count, append_to_msg=dynamic, rule='oneOf')
194 def generate_not(self):
195 """
196 Means that value have not to be valid by this definition.
198 .. code-block:: python
200 {'not': {'type': 'null'}}
202 Valid values for this definition are 'hello', 42, {} ... but not None.
204 Since draft 06 definition can be boolean. False means nothing, True
205 means everything is invalid.
206 """
207 not_definition = self._definition['not']
208 if not_definition is True:
209 self.exc('{name} must not be there', rule='not')
210 elif not_definition is False:
211 return
212 elif not not_definition:
213 with self.l('if {}:', self._variable):
214 self.exc('{name} must NOT match a disallowed definition', rule='not')
215 else:
216 with self.l('try:', optimize=False):
217 self.generate_func_code_block(not_definition, self._variable, self._variable_name)
218 self.l('except JsonSchemaValueException: pass')
219 with self.l('else:'):
220 self.exc('{name} must NOT match a disallowed definition', rule='not')
222 def generate_min_length(self):
223 with self.l('if isinstance({variable}, str):'):
224 self.create_variable_with_length()
225 if not isinstance(self._definition['minLength'], int):
226 raise JsonSchemaDefinitionException('minLength must be a number')
227 with self.l('if {variable}_len < {minLength}:'):
228 self.exc('{name} must be longer than or equal to {minLength} characters', rule='minLength')
230 def generate_max_length(self):
231 with self.l('if isinstance({variable}, str):'):
232 self.create_variable_with_length()
233 if not isinstance(self._definition['maxLength'], int):
234 raise JsonSchemaDefinitionException('maxLength must be a number')
235 with self.l('if {variable}_len > {maxLength}:'):
236 self.exc('{name} must be shorter than or equal to {maxLength} characters', rule='maxLength')
238 def generate_pattern(self):
239 with self.l('if isinstance({variable}, str):'):
240 pattern = self._definition['pattern']
241 safe_pattern = pattern.replace('\\', '\\\\').replace('"', '\\"')
242 end_of_string_fixed_pattern = DOLLAR_FINDER.sub(r'\\Z', pattern)
243 self._compile_regexps[pattern] = re.compile(end_of_string_fixed_pattern)
244 with self.l('if not REGEX_PATTERNS[{}].search({variable}):', repr(pattern)):
245 self.exc('{name} must match pattern {}', safe_pattern, rule='pattern')
247 def generate_format(self):
248 """
249 Means that value have to be in specified format. For example date, email or other.
251 .. code-block:: python
253 {'format': 'email'}
255 Valid value for this definition is user@example.com but not @username
256 """
257 with self.l('if isinstance({variable}, str):'):
258 format_ = self._definition['format']
259 # Checking custom formats - user is allowed to override default formats.
260 if format_ in self._custom_formats:
261 custom_format = self._custom_formats[format_]
262 if isinstance(custom_format, str):
263 self._generate_format(format_, format_ + '_re_pattern', custom_format)
264 else:
265 with self.l('if not custom_formats["{}"]({variable}):', format_):
266 self.exc('{name} must be {}', format_, rule='format')
267 elif format_ in self.FORMAT_REGEXS:
268 format_regex = self.FORMAT_REGEXS[format_]
269 self._generate_format(format_, format_ + '_re_pattern', format_regex)
270 # Format regex is used only in meta schemas.
271 elif format_ == 'regex':
272 with self.l('try:', optimize=False):
273 self.l('re.compile({variable})')
274 with self.l('except Exception:'):
275 self.exc('{name} must be a valid regex', rule='format')
276 else:
277 raise JsonSchemaDefinitionException('Unknown format: {}'.format(format_))
280 def _generate_format(self, format_name, regexp_name, regexp):
281 if self._definition['format'] == format_name:
282 if not regexp_name in self._compile_regexps:
283 self._compile_regexps[regexp_name] = re.compile(regexp)
284 with self.l('if not REGEX_PATTERNS["{}"].match({variable}):', regexp_name):
285 self.exc('{name} must be {}', format_name, rule='format')
287 def generate_minimum(self):
288 with self.l('if isinstance({variable}, (int, float, Decimal)):'):
289 if not isinstance(self._definition['minimum'], (int, float, decimal.Decimal)):
290 raise JsonSchemaDefinitionException('minimum must be a number')
291 if self._definition.get('exclusiveMinimum', False):
292 with self.l('if {variable} <= {minimum}:'):
293 self.exc('{name} must be bigger than {minimum}', rule='minimum')
294 else:
295 with self.l('if {variable} < {minimum}:'):
296 self.exc('{name} must be bigger than or equal to {minimum}', rule='minimum')
298 def generate_maximum(self):
299 with self.l('if isinstance({variable}, (int, float, Decimal)):'):
300 if not isinstance(self._definition['maximum'], (int, float, decimal.Decimal)):
301 raise JsonSchemaDefinitionException('maximum must be a number')
302 if self._definition.get('exclusiveMaximum', False):
303 with self.l('if {variable} >= {maximum}:'):
304 self.exc('{name} must be smaller than {maximum}', rule='maximum')
305 else:
306 with self.l('if {variable} > {maximum}:'):
307 self.exc('{name} must be smaller than or equal to {maximum}', rule='maximum')
309 def generate_multiple_of(self):
310 with self.l('if isinstance({variable}, (int, float, Decimal)):'):
311 if not isinstance(self._definition['multipleOf'], (int, float, decimal.Decimal)):
312 raise JsonSchemaDefinitionException('multipleOf must be a number')
313 # For proper multiplication check of floats we need to use decimals,
314 # because for example 19.01 / 0.01 = 1901.0000000000002.
315 if isinstance(self._definition['multipleOf'], float):
316 self.l('quotient = Decimal(repr({variable})) / Decimal(repr({multipleOf}))')
317 else:
318 self.l('quotient = {variable} / {multipleOf}')
319 with self.l('if int(quotient) != quotient:'):
320 self.exc('{name} must be multiple of {multipleOf}', rule='multipleOf')
322 def generate_min_items(self):
323 self.create_variable_is_list()
324 with self.l('if {variable}_is_list:'):
325 if not isinstance(self._definition['minItems'], int):
326 raise JsonSchemaDefinitionException('minItems must be a number')
327 self.create_variable_with_length()
328 with self.l('if {variable}_len < {minItems}:'):
329 self.exc('{name} must contain at least {minItems} items', rule='minItems')
331 def generate_max_items(self):
332 self.create_variable_is_list()
333 with self.l('if {variable}_is_list:'):
334 if not isinstance(self._definition['maxItems'], int):
335 raise JsonSchemaDefinitionException('maxItems must be a number')
336 self.create_variable_with_length()
337 with self.l('if {variable}_len > {maxItems}:'):
338 self.exc('{name} must contain less than or equal to {maxItems} items', rule='maxItems')
340 def generate_unique_items(self):
341 """
342 With Python 3.4 module ``timeit`` recommended this solutions:
344 .. code-block:: python
346 >>> timeit.timeit("len(x) > len(set(x))", "x=range(100)+range(100)", number=100000)
347 0.5839540958404541
348 >>> timeit.timeit("len({}.fromkeys(x)) == len(x)", "x=range(100)+range(100)", number=100000)
349 0.7094449996948242
350 >>> timeit.timeit("seen = set(); any(i in seen or seen.add(i) for i in x)", "x=range(100)+range(100)", number=100000)
351 2.0819358825683594
352 >>> timeit.timeit("np.unique(x).size == len(x)", "x=range(100)+range(100); import numpy as np", number=100000)
353 2.1439831256866455
354 """
355 unique_definition = self._definition['uniqueItems']
356 if not unique_definition:
357 return
359 self.create_variable_is_list()
360 with self.l('if {variable}_is_list:'):
361 self.l(
362 'def fn(var): '
363 'return frozenset(dict((k, fn(v)) '
364 'for k, v in var.items()).items()) '
365 'if hasattr(var, "items") else tuple(fn(v) '
366 'for v in var) '
367 'if isinstance(var, (dict, list)) else str(var) '
368 'if isinstance(var, bool) else var')
369 self.create_variable_with_length()
370 with self.l('if {variable}_len > len(set(fn({variable}_x) for {variable}_x in {variable})):'):
371 self.exc('{name} must contain unique items', rule='uniqueItems')
373 def generate_items(self):
374 """
375 Means array is valid only when all items are valid by this definition.
377 .. code-block:: python
379 {
380 'items': [
381 {'type': 'integer'},
382 {'type': 'string'},
383 ],
384 }
386 Valid arrays are those with integers or strings, nothing else.
388 Since draft 06 definition can be also boolean. True means nothing, False
389 means everything is invalid.
390 """
391 items_definition = self._definition['items']
392 if items_definition is True:
393 return
395 self.create_variable_is_list()
396 with self.l('if {variable}_is_list:'):
397 self.create_variable_with_length()
398 if items_definition is False:
399 with self.l('if {variable}:'):
400 self.exc('{name} must not be there', rule='items')
401 elif isinstance(items_definition, list):
402 for idx, item_definition in enumerate(items_definition):
403 with self.l('if {variable}_len > {}:', idx):
404 self.l('{variable}__{0} = {variable}[{0}]', idx)
405 self.generate_func_code_block(
406 item_definition,
407 '{}__{}'.format(self._variable, idx),
408 '{}[{}]'.format(self._variable_name, idx),
409 )
410 if self._use_default and isinstance(item_definition, dict) and 'default' in item_definition:
411 self.l('else: {variable}.append({})', repr(item_definition['default']))
413 if 'additionalItems' in self._definition:
414 if self._definition['additionalItems'] is False:
415 with self.l('if {variable}_len > {}:', len(items_definition)):
416 self.exc('{name} must contain only specified items', rule='items')
417 else:
418 with self.l('for {variable}_x, {variable}_item in enumerate({variable}[{0}:], {0}):', len(items_definition)):
419 count = self.generate_func_code_block(
420 self._definition['additionalItems'],
421 '{}_item'.format(self._variable),
422 '{}[{{{}_x}}]'.format(self._variable_name, self._variable),
423 )
424 if count == 0:
425 self.l('pass')
426 else:
427 if items_definition:
428 with self.l('for {variable}_x, {variable}_item in enumerate({variable}):'):
429 count = self.generate_func_code_block(
430 items_definition,
431 '{}_item'.format(self._variable),
432 '{}[{{{}_x}}]'.format(self._variable_name, self._variable),
433 )
434 if count == 0:
435 self.l('pass')
437 def generate_min_properties(self):
438 self.create_variable_is_dict()
439 with self.l('if {variable}_is_dict:'):
440 if not isinstance(self._definition['minProperties'], int):
441 raise JsonSchemaDefinitionException('minProperties must be a number')
442 self.create_variable_with_length()
443 with self.l('if {variable}_len < {minProperties}:'):
444 self.exc('{name} must contain at least {minProperties} properties', rule='minProperties')
446 def generate_max_properties(self):
447 self.create_variable_is_dict()
448 with self.l('if {variable}_is_dict:'):
449 if not isinstance(self._definition['maxProperties'], int):
450 raise JsonSchemaDefinitionException('maxProperties must be a number')
451 self.create_variable_with_length()
452 with self.l('if {variable}_len > {maxProperties}:'):
453 self.exc('{name} must contain less than or equal to {maxProperties} properties', rule='maxProperties')
455 def generate_required(self):
456 self.create_variable_is_dict()
457 with self.l('if {variable}_is_dict:'):
458 if not isinstance(self._definition['required'], (list, tuple)):
459 raise JsonSchemaDefinitionException('required must be an array')
460 self.create_variable_with_length()
461 with self.l('if not all(prop in {variable} for prop in {required}):'):
462 self.exc('{name} must contain {} properties', self.e(self._definition['required']), rule='required')
464 def generate_properties(self):
465 """
466 Means object with defined keys.
468 .. code-block:: python
470 {
471 'properties': {
472 'key': {'type': 'number'},
473 },
474 }
476 Valid object is containing key called 'key' and value any number.
477 """
478 self.create_variable_is_dict()
479 with self.l('if {variable}_is_dict:'):
480 self.create_variable_keys()
481 for key, prop_definition in self._definition['properties'].items():
482 key_name = re.sub(r'($[^a-zA-Z]|[^a-zA-Z0-9])', '', key)
483 if not isinstance(prop_definition, (dict, bool)):
484 raise JsonSchemaDefinitionException('{}[{}] must be object'.format(self._variable, key_name))
485 with self.l('if "{}" in {variable}_keys:', self.e(key)):
486 self.l('{variable}_keys.remove("{}")', self.e(key))
487 self.l('{variable}__{0} = {variable}["{1}"]', key_name, self.e(key))
488 self.generate_func_code_block(
489 prop_definition,
490 '{}__{}'.format(self._variable, key_name),
491 '{}.{}'.format(self._variable_name, self.e(key)),
492 clear_variables=True,
493 )
494 if self._use_default and isinstance(prop_definition, dict) and 'default' in prop_definition:
495 self.l('else: {variable}["{}"] = {}', self.e(key), repr(prop_definition['default']))
497 def generate_pattern_properties(self):
498 """
499 Means object with defined keys as patterns.
501 .. code-block:: python
503 {
504 'patternProperties': {
505 '^x': {'type': 'number'},
506 },
507 }
509 Valid object is containing key starting with a 'x' and value any number.
510 """
511 self.create_variable_is_dict()
512 with self.l('if {variable}_is_dict:'):
513 self.create_variable_keys()
514 for pattern, definition in self._definition['patternProperties'].items():
515 self._compile_regexps[pattern] = re.compile(pattern)
516 with self.l('for {variable}_key, {variable}_val in {variable}.items():'):
517 for pattern, definition in self._definition['patternProperties'].items():
518 with self.l('if REGEX_PATTERNS[{}].search({variable}_key):', repr(pattern)):
519 with self.l('if {variable}_key in {variable}_keys:'):
520 self.l('{variable}_keys.remove({variable}_key)')
521 self.generate_func_code_block(
522 definition,
523 '{}_val'.format(self._variable),
524 '{}.{{{}_key}}'.format(self._variable_name, self._variable),
525 clear_variables=True,
526 )
528 def generate_additional_properties(self):
529 """
530 Means object with keys with values defined by definition.
532 .. code-block:: python
534 {
535 'properties': {
536 'key': {'type': 'number'},
537 }
538 'additionalProperties': {'type': 'string'},
539 }
541 Valid object is containing key called 'key' and it's value any number and
542 any other key with any string.
543 """
544 self.create_variable_is_dict()
545 with self.l('if {variable}_is_dict:'):
546 self.create_variable_keys()
547 add_prop_definition = self._definition["additionalProperties"]
548 if add_prop_definition is True or add_prop_definition == {}:
549 return
550 if add_prop_definition:
551 properties_keys = list(self._definition.get("properties", {}).keys())
552 with self.l('for {variable}_key in {variable}_keys:'):
553 with self.l('if {variable}_key not in {}:', properties_keys):
554 self.l('{variable}_value = {variable}.get({variable}_key)')
555 self.generate_func_code_block(
556 add_prop_definition,
557 '{}_value'.format(self._variable),
558 '{}.{{{}_key}}'.format(self._variable_name, self._variable),
559 )
560 else:
561 with self.l('if {variable}_keys:'):
562 self.exc('{name} must not contain "+str({variable}_keys)+" properties', rule='additionalProperties')
564 def generate_dependencies(self):
565 """
566 Means when object has property, it needs to have also other property.
568 .. code-block:: python
570 {
571 'dependencies': {
572 'bar': ['foo'],
573 },
574 }
576 Valid object is containing only foo, both bar and foo or none of them, but not
577 object with only bar.
579 Since draft 06 definition can be boolean or empty array. True and empty array
580 means nothing, False means that key cannot be there at all.
581 """
582 self.create_variable_is_dict()
583 with self.l('if {variable}_is_dict:'):
584 is_empty = True
585 for key, values in self._definition["dependencies"].items():
586 if values == [] or values is True:
587 continue
588 is_empty = False
589 with self.l('if "{}" in {variable}:', self.e(key)):
590 if values is False:
591 self.exc('{} in {name} must not be there', key, rule='dependencies')
592 elif isinstance(values, list):
593 for value in values:
594 with self.l('if "{}" not in {variable}:', self.e(value)):
595 self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule='dependencies')
596 else:
597 self.generate_func_code_block(values, self._variable, self._variable_name, clear_variables=True)
598 if is_empty:
599 self.l('pass')