draft06.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import decimal
  2. from .draft04 import CodeGeneratorDraft04, JSON_TYPE_TO_PYTHON_TYPE
  3. from .exceptions import JsonSchemaDefinitionException
  4. from .generator import enforce_list
  5. class CodeGeneratorDraft06(CodeGeneratorDraft04):
  6. FORMAT_REGEXS = dict(CodeGeneratorDraft04.FORMAT_REGEXS, **{
  7. 'json-pointer': r'^(/(([^/~])|(~[01]))*)*\Z',
  8. 'uri-reference': r'^(\w+:(\/?\/?))?[^#\\\s]*(#[^\\\s]*)?\Z',
  9. 'uri-template': (
  10. r'^(?:(?:[^\x00-\x20\"\'<>%\\^`{|}]|%[0-9a-f]{2})|'
  11. r'\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+'
  12. r'(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+'
  13. r'(?::[1-9][0-9]{0,3}|\*)?)*\})*\Z'
  14. ),
  15. })
  16. def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
  17. super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions)
  18. self._json_keywords_to_function.update((
  19. ('exclusiveMinimum', self.generate_exclusive_minimum),
  20. ('exclusiveMaximum', self.generate_exclusive_maximum),
  21. ('propertyNames', self.generate_property_names),
  22. ('contains', self.generate_contains),
  23. ('const', self.generate_const),
  24. ))
  25. def _generate_func_code_block(self, definition):
  26. if isinstance(definition, bool):
  27. self.generate_boolean_schema()
  28. elif '$ref' in definition:
  29. # needed because ref overrides any sibling keywords
  30. self.generate_ref()
  31. else:
  32. self.run_generate_functions(definition)
  33. def generate_boolean_schema(self):
  34. """
  35. Means that schema can be specified by boolean.
  36. True means everything is valid, False everything is invalid.
  37. """
  38. if self._definition is True:
  39. self.l('pass')
  40. if self._definition is False:
  41. self.exc('{name} must not be there')
  42. def generate_type(self):
  43. """
  44. Validation of type. Can be one type or list of types.
  45. Since draft 06 a float without fractional part is an integer.
  46. .. code-block:: python
  47. {'type': 'string'}
  48. {'type': ['string', 'number']}
  49. """
  50. types = enforce_list(self._definition['type'])
  51. try:
  52. python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types)
  53. except KeyError as exc:
  54. raise JsonSchemaDefinitionException('Unknown type: {}'.format(exc))
  55. extra = ''
  56. if 'integer' in types:
  57. extra += ' and not (isinstance({variable}, float) and {variable}.is_integer())'.format(
  58. variable=self._variable,
  59. )
  60. if ('number' in types or 'integer' in types) and 'boolean' not in types:
  61. extra += ' or isinstance({variable}, bool)'.format(variable=self._variable)
  62. with self.l('if not isinstance({variable}, ({})){}:', python_types, extra):
  63. self.exc('{name} must be {}', ' or '.join(types), rule='type')
  64. def generate_exclusive_minimum(self):
  65. with self.l('if isinstance({variable}, (int, float, Decimal)):'):
  66. if not isinstance(self._definition['exclusiveMinimum'], (int, float, decimal.Decimal)):
  67. raise JsonSchemaDefinitionException('exclusiveMinimum must be an integer, a float or a decimal')
  68. with self.l('if {variable} <= {exclusiveMinimum}:'):
  69. self.exc('{name} must be bigger than {exclusiveMinimum}', rule='exclusiveMinimum')
  70. def generate_exclusive_maximum(self):
  71. with self.l('if isinstance({variable}, (int, float, Decimal)):'):
  72. if not isinstance(self._definition['exclusiveMaximum'], (int, float, decimal.Decimal)):
  73. raise JsonSchemaDefinitionException('exclusiveMaximum must be an integer, a float or a decimal')
  74. with self.l('if {variable} >= {exclusiveMaximum}:'):
  75. self.exc('{name} must be smaller than {exclusiveMaximum}', rule='exclusiveMaximum')
  76. def generate_property_names(self):
  77. """
  78. Means that keys of object must to follow this definition.
  79. .. code-block:: python
  80. {
  81. 'propertyNames': {
  82. 'maxLength': 3,
  83. },
  84. }
  85. Valid keys of object for this definition are foo, bar, ... but not foobar for example.
  86. """
  87. property_names_definition = self._definition.get('propertyNames', {})
  88. if property_names_definition is True:
  89. pass
  90. elif property_names_definition is False:
  91. self.create_variable_keys()
  92. with self.l('if {variable}_keys:'):
  93. self.exc('{name} must not be there', rule='propertyNames')
  94. else:
  95. self.create_variable_is_dict()
  96. with self.l('if {variable}_is_dict:'):
  97. self.create_variable_with_length()
  98. with self.l('if {variable}_len != 0:'):
  99. self.l('{variable}_property_names = True')
  100. with self.l('for {variable}_key in {variable}:'):
  101. with self.l('try:'):
  102. self.generate_func_code_block(
  103. property_names_definition,
  104. '{}_key'.format(self._variable),
  105. self._variable_name,
  106. clear_variables=True,
  107. )
  108. with self.l('except JsonSchemaValueException:'):
  109. self.l('{variable}_property_names = False')
  110. with self.l('if not {variable}_property_names:'):
  111. self.exc('{name} must be named by propertyName definition', rule='propertyNames')
  112. def generate_contains(self):
  113. """
  114. Means that array must contain at least one defined item.
  115. .. code-block:: python
  116. {
  117. 'contains': {
  118. 'type': 'number',
  119. },
  120. }
  121. Valid array is any with at least one number.
  122. """
  123. self.create_variable_is_list()
  124. with self.l('if {variable}_is_list:'):
  125. contains_definition = self._definition['contains']
  126. if contains_definition is False:
  127. self.exc('{name} is always invalid', rule='contains')
  128. elif contains_definition is True:
  129. with self.l('if not {variable}:'):
  130. self.exc('{name} must not be empty', rule='contains')
  131. else:
  132. self.l('{variable}_contains = False')
  133. with self.l('for {variable}_key in {variable}:'):
  134. with self.l('try:'):
  135. self.generate_func_code_block(
  136. contains_definition,
  137. '{}_key'.format(self._variable),
  138. self._variable_name,
  139. clear_variables=True,
  140. )
  141. self.l('{variable}_contains = True')
  142. self.l('break')
  143. self.l('except JsonSchemaValueException: pass')
  144. with self.l('if not {variable}_contains:'):
  145. self.exc('{name} must contain one of contains definition', rule='contains')
  146. def generate_const(self):
  147. """
  148. Means that value is valid when is equeal to const definition.
  149. .. code-block:: python
  150. {
  151. 'const': 42,
  152. }
  153. Only valid value is 42 in this example.
  154. """
  155. const = self._definition['const']
  156. if isinstance(const, str):
  157. const = '"{}"'.format(self.e(const))
  158. with self.l('if {variable} != {}:', const):
  159. self.exc('{name} must be same as const definition: {definition_rule}', rule='const')