Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/fastjsonschema/draft06.py: 16%

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

105 statements  

1import decimal 

2from .draft04 import CodeGeneratorDraft04, JSON_TYPE_TO_PYTHON_TYPE 

3from .exceptions import JsonSchemaDefinitionException 

4from .generator import VALIDATION_EXCEPTIONS, enforce_list 

5 

6 

7class CodeGeneratorDraft06(CodeGeneratorDraft04): 

8 FORMAT_REGEXS = dict(CodeGeneratorDraft04.FORMAT_REGEXS, **{ 

9 'json-pointer': r'^(/(([^/~])|(~[01]))*)*\Z', 

10 'uri-reference': r'^(\w+:(\/?\/?))?[^#\\\s]*(#[^\\\s]*)?\Z', 

11 'uri-template': ( 

12 r'^(?:(?:[^\x00-\x20\"\'<>%\\^`{|}]|%[0-9a-f]{2})|' 

13 r'\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+' 

14 r'(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+' 

15 r'(?::[1-9][0-9]{0,3}|\*)?)*\})*\Z' 

16 ), 

17 }) 

18 

19 def __init__( 

20 self, 

21 definition, 

22 resolver=None, 

23 formats={}, 

24 use_default=True, 

25 use_formats=True, 

26 detailed_exceptions=True, 

27 fast_fail=True, 

28 ): 

29 super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions, fast_fail) 

30 self._json_keywords_to_function.update(( 

31 ('exclusiveMinimum', self.generate_exclusive_minimum), 

32 ('exclusiveMaximum', self.generate_exclusive_maximum), 

33 ('propertyNames', self.generate_property_names), 

34 ('contains', self.generate_contains), 

35 ('const', self.generate_const), 

36 )) 

37 

38 def _generate_func_code_block(self, definition): 

39 if isinstance(definition, bool): 

40 return self.generate_boolean_schema() 

41 elif '$ref' in definition: 

42 # needed because ref overrides any sibling keywords 

43 return self.generate_ref() 

44 return self.run_generate_functions(definition) 

45 

46 def generate_boolean_schema(self): 

47 """ 

48 Means that schema can be specified by boolean. 

49 True means everything is valid, False everything is invalid. 

50 """ 

51 if self._definition is True: 

52 self.l('pass') 

53 if self._definition is False: 

54 self.exc('{name} must not be there') 

55 

56 def generate_type(self): 

57 """ 

58 Validation of type. Can be one type or list of types. 

59 

60 Since draft 06 a float without fractional part is an integer. 

61 

62 .. code-block:: python 

63 

64 {'type': 'string'} 

65 {'type': ['string', 'number']} 

66 """ 

67 types = enforce_list(self._definition['type']) 

68 try: 

69 python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types) 

70 except KeyError as exc: 

71 raise JsonSchemaDefinitionException('Unknown type') from exc 

72 

73 extra = '' 

74 

75 if 'integer' in types: 

76 extra += ' and not (isinstance({variable}, float) and {variable}.is_integer())'.format( 

77 variable=self._variable, 

78 ) 

79 

80 if ('number' in types or 'integer' in types) and 'boolean' not in types: 

81 extra += ' or isinstance({variable}, bool)'.format(variable=self._variable) 

82 

83 with self.l('if not isinstance({variable}, ({})){}:', python_types, extra): 

84 self.exc('{name} must be {}', ' or '.join(types), rule='type') 

85 

86 def generate_minimum(self): 

87 """ 

88 Since draft 06 exclusiveMinimum is a number validated on its own, 

89 so minimum is always inclusive. 

90 """ 

91 with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): 

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

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

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

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

96 

97 def generate_maximum(self): 

98 """ 

99 Since draft 06 exclusiveMaximum is a number validated on its own, 

100 so maximum is always inclusive. 

101 """ 

102 with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): 

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

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

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

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

107 

108 def generate_exclusive_minimum(self): 

109 with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): 

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

111 raise JsonSchemaDefinitionException('exclusiveMinimum must be an integer, a float or a decimal') 

112 with self.l('if {variable} <= {exclusiveMinimum}:'): 

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

114 

115 def generate_exclusive_maximum(self): 

116 with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): 

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

118 raise JsonSchemaDefinitionException('exclusiveMaximum must be an integer, a float or a decimal') 

119 with self.l('if {variable} >= {exclusiveMaximum}:'): 

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

121 

122 def generate_property_names(self): 

123 """ 

124 Means that keys of object must to follow this definition. 

125 

126 .. code-block:: python 

127 

128 { 

129 'propertyNames': { 

130 'maxLength': 3, 

131 }, 

132 } 

133 

134 Valid keys of object for this definition are foo, bar, ... but not foobar for example. 

135 """ 

136 property_names_definition = self._definition.get('propertyNames', {}) 

137 if property_names_definition is True: 

138 pass 

139 elif property_names_definition is False: 

140 self.create_variable_is_dict() 

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

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

143 else: 

144 self.create_variable_is_dict() 

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

146 self.create_variable_with_length() 

147 with self.l('if {variable}_len != 0:'): 

148 self.l('{variable}_property_names = True') 

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

150 with self.l('try:'): 

151 code_len = len(self._code) 

152 with self.trial_validation(): 

153 self.generate_func_code_block( 

154 property_names_definition, 

155 '{}_key'.format(self._variable), 

156 self._variable_name, 

157 clear_variables=True, 

158 ) 

159 if len(self._code) == code_len: 

160 self.l('pass') 

161 with self.l('except {}:', VALIDATION_EXCEPTIONS): 

162 self.l('{variable}_property_names = False') 

163 with self.l('if not {variable}_property_names:'): 

164 self.exc('{name} must be named by propertyName definition', rule='propertyNames') 

165 

166 def generate_contains(self): 

167 """ 

168 Means that array must contain at least one defined item. 

169 

170 .. code-block:: python 

171 

172 { 

173 'contains': { 

174 'type': 'number', 

175 }, 

176 } 

177 

178 Valid array is any with at least one number. 

179 """ 

180 self.create_variable_is_list() 

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

182 contains_definition = self._definition['contains'] 

183 

184 if contains_definition is False: 

185 self.exc('{name} is always invalid', rule='contains') 

186 elif contains_definition is True: 

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

188 self.exc('{name} must not be empty', rule='contains') 

189 else: 

190 self.l('{variable}_contains = False') 

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

192 with self.l('try:'): 

193 with self.trial_validation(): 

194 self.generate_func_code_block( 

195 contains_definition, 

196 '{}_key'.format(self._variable), 

197 self._variable_name, 

198 clear_variables=True, 

199 ) 

200 self.l('{variable}_contains = True') 

201 self.l('break') 

202 self.l('except {}: pass', VALIDATION_EXCEPTIONS) 

203 

204 with self.l('if not {variable}_contains:'): 

205 self.exc('{name} must contain one of contains definition', rule='contains') 

206 

207 def generate_const(self): 

208 """ 

209 Means that value is valid when is equeal to const definition. 

210 

211 .. code-block:: python 

212 

213 { 

214 'const': 42, 

215 } 

216 

217 Only valid value is 42 in this example. 

218 """ 

219 const = self._definition['const'] 

220 match = self._enum_value_matches(self._variable, const) 

221 with self.l('if not ({}):', match): 

222 self.exc('{name} must be same as const definition: {definition_rule}', rule='const')