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