Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fastjsonschema/__init__.py: 41%

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

34 statements  

1# ___ 

2# \./ DANGER: This project implements some code generation 

3# .--.O.--. techniques involving string concatenation. 

4# \/ \/ If you look at it, you might die. 

5# 

6 

7r""" 

8Installation 

9************ 

10 

11.. code-block:: bash 

12 

13 pip install fastjsonschema 

14 

15Support only for Python 3.3 and higher. 

16 

17About 

18***** 

19 

20``fastjsonschema`` implements validation of JSON documents by JSON schema. 

21The library implements JSON schema drafts 04, 06, and 07. The main purpose is 

22to have a really fast implementation. See some numbers: 

23 

24 * Probably the most popular, ``jsonschema``, can take up to 5 seconds for valid 

25 inputs and 1.2 seconds for invalid inputs. 

26 * Second most popular, ``json-spec``, is even worse with up to 7.2 and 1.7 seconds. 

27 * Last ``validictory``, now deprecated, is much better with 370 or 23 milliseconds, 

28 but it does not follow all standards, and it can be still slow for some purposes. 

29 

30With this library you can gain big improvements as ``fastjsonschema`` takes 

31only about 25 milliseconds for valid inputs and 2 milliseconds for invalid ones. 

32Pretty amazing, right? :-) 

33 

34Technically it works by generating the most stupid code on the fly, which is fast but 

35is hard to write by hand. The best efficiency is achieved when a validator is compiled 

36once and used many times, of course. It works similarly like regular expressions. But 

37you can also generate the code to a file, which is even slightly faster. 

38 

39You can run the performance benchmarks on your computer or server with the included 

40script: 

41 

42.. code-block:: bash 

43 

44 $ make performance 

45 fast_compiled valid ==> 0.0464646 

46 fast_compiled invalid ==> 0.0030227 

47 fast_file valid ==> 0.0461219 

48 fast_file invalid ==> 0.0030608 

49 fast_not_compiled valid ==> 11.4627202 

50 fast_not_compiled invalid ==> 2.5726230 

51 jsonschema valid ==> 7.5844927 

52 jsonschema invalid ==> 1.9204665 

53 jsonschema_compiled valid ==> 0.6938364 

54 jsonschema_compiled invalid ==> 0.0359244 

55 jsonspec valid ==> 9.0715843 

56 jsonspec invalid ==> 2.1650488 

57 validictory valid ==> 0.4874793 

58 validictory invalid ==> 0.0232244 

59 

60This library follows and implements `JSON schema draft-04, draft-06, and draft-07 

61<http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also 

62check out this `understanding JSON schema <https://spacetelescope.github.io/understanding-json-schema>`_. 

63 

64Note that there are some differences compared to JSON schema standard: 

65 

66 * Regular expressions are full Python ones, not only what JSON schema allows. It's easier 

67 to allow everything, and also it's faster to compile without limits. So keep in mind that when 

68 you will use a more advanced regular expression, it may not work with other libraries or in 

69 other languages. 

70 * Because Python matches new line for a dollar in regular expressions (``a$`` matches ``a`` and ``a\\n``), 

71 instead of ``$`` is used ``\Z`` and all dollars in your regular expression are changed to ``\\Z`` 

72 as well. When you want to use dollar as regular character, you have to escape it (``\$``). 

73 * JSON schema says you can use keyword ``default`` for providing default values. This implementation 

74 uses that and always returns transformed input data. 

75 

76Usage 

77***** 

78 

79.. code-block:: python 

80 

81 import fastjsonschema 

82 

83 point_schema = { 

84 "type": "object", 

85 "properties": { 

86 "x": { 

87 "type": "number", 

88 }, 

89 "y": { 

90 "type": "number", 

91 }, 

92 }, 

93 "required": ["x", "y"], 

94 "additionalProperties": False, 

95 } 

96 

97 point_validator = fastjsonschema.compile(point_schema) 

98 try: 

99 point_validator({"x": 1.0, "y": 2.0}) 

100 except fastjsonschema.JsonSchemaException as e: 

101 print(f"Data failed validation: {e}") 

102 

103API 

104*** 

105""" 

106from functools import partial, update_wrapper 

107 

108from .draft04 import CodeGeneratorDraft04 

109from .draft06 import CodeGeneratorDraft06 

110from .draft07 import CodeGeneratorDraft07 

111from .exceptions import JsonSchemaException, JsonSchemaValueException, JsonSchemaDefinitionException 

112from .ref_resolver import RefResolver 

113from .version import VERSION 

114 

115__all__ = ( 

116 'VERSION', 

117 'JsonSchemaException', 

118 'JsonSchemaValueException', 

119 'JsonSchemaDefinitionException', 

120 'validate', 

121 'compile', 

122 'compile_to_code', 

123) 

124 

125 

126def validate(definition, data, handlers={}, formats={}, use_default=True, use_formats=True): 

127 """ 

128 Validation function for lazy programmers or for use cases when you need 

129 to call validation only once, so you do not have to compile it first. 

130 Use it only when you do not care about performance (even though it will 

131 be still faster than alternative implementations). 

132 

133 .. code-block:: python 

134 

135 import fastjsonschema 

136 

137 fastjsonschema.validate({'type': 'string'}, 'hello') 

138 # same as: compile({'type': 'string'})('hello') 

139 

140 Preferred is to use :any:`compile` function. 

141 """ 

142 return compile(definition, handlers, formats, use_default, use_formats)(data) 

143 

144 

145#TODO: Change use_default to False when upgrading to version 3. 

146# pylint: disable=redefined-builtin,dangerous-default-value,exec-used 

147def compile(definition, handlers={}, formats={}, use_default=True, use_formats=True): 

148 """ 

149 Generates validation function for validating JSON schema passed in ``definition``. 

150 Example: 

151 

152 .. code-block:: python 

153 

154 import fastjsonschema 

155 

156 validate = fastjsonschema.compile({'type': 'string'}) 

157 validate('hello') 

158 

159 This implementation supports keyword ``default`` (can be turned off 

160 by passing `use_default=False`): 

161 

162 .. code-block:: python 

163 

164 validate = fastjsonschema.compile({ 

165 'type': 'object', 

166 'properties': { 

167 'a': {'type': 'number', 'default': 42}, 

168 }, 

169 }) 

170 

171 data = validate({}) 

172 assert data == {'a': 42} 

173 

174 Supported implementations are draft-04, draft-06 and draft-07. Which version 

175 should be used is determined by `$draft` in your ``definition``. When not 

176 specified, the latest implementation is used (draft-07). 

177 

178 .. code-block:: python 

179 

180 validate = fastjsonschema.compile({ 

181 '$schema': 'http://json-schema.org/draft-04/schema', 

182 'type': 'number', 

183 }) 

184 

185 You can pass mapping from URI to function that should be used to retrieve 

186 remote schemes used in your ``definition`` in parameter ``handlers``. 

187 

188 Also, you can pass mapping for custom formats. Key is the name of your 

189 formatter and value can be regular expression, which will be compiled or 

190 callback returning `bool` (or you can raise your own exception). 

191 

192 .. code-block:: python 

193 

194 validate = fastjsonschema.compile(definition, formats={ 

195 'foo': r'foo|bar', 

196 'bar': lambda value: value in ('foo', 'bar'), 

197 }) 

198 

199 Note that formats are automatically used as assertions. It can be turned 

200 off by passing `use_formats=False`. When disabled, custom formats are 

201 disabled as well. (Added in 2.19.0.) 

202 

203 Exception :any:`JsonSchemaDefinitionException` is raised when generating the 

204 code fails (bad definition). 

205 

206 Exception :any:`JsonSchemaValueException` is raised from generated function when 

207 validation fails (data do not follow the definition). 

208 """ 

209 resolver, code_generator = _factory(definition, handlers, formats, use_default, use_formats) 

210 global_state = code_generator.global_state 

211 # Do not pass local state so it can recursively call itself. 

212 exec(code_generator.func_code, global_state) 

213 func = global_state[resolver.get_scope_name()] 

214 if formats: 

215 return update_wrapper(partial(func, custom_formats=formats), func) 

216 return func 

217 

218 

219# pylint: disable=dangerous-default-value 

220def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_formats=True): 

221 """ 

222 Generates validation code for validating JSON schema passed in ``definition``. 

223 Example: 

224 

225 .. code-block:: python 

226 

227 import fastjsonschema 

228 

229 code = fastjsonschema.compile_to_code({'type': 'string'}) 

230 with open('your_file.py', 'w') as f: 

231 f.write(code) 

232 

233 You can also use it as a script: 

234 

235 .. code-block:: bash 

236 

237 echo "{'type': 'string'}" | python3 -m fastjsonschema > your_file.py 

238 python3 -m fastjsonschema "{'type': 'string'}" > your_file.py 

239 

240 Exception :any:`JsonSchemaDefinitionException` is raised when generating the 

241 code fails (bad definition). 

242 """ 

243 _, code_generator = _factory(definition, handlers, formats, use_default, use_formats) 

244 return ( 

245 'VERSION = "' + VERSION + '"\n' + 

246 code_generator.global_state_code + '\n' + 

247 code_generator.func_code 

248 ) 

249 

250 

251def _factory(definition, handlers, formats={}, use_default=True, use_formats=True): 

252 resolver = RefResolver.from_schema(definition, handlers=handlers, store={}) 

253 code_generator = _get_code_generator_class(definition)( 

254 definition, 

255 resolver=resolver, 

256 formats=formats, 

257 use_default=use_default, 

258 use_formats=use_formats, 

259 ) 

260 return resolver, code_generator 

261 

262 

263def _get_code_generator_class(schema): 

264 # Schema in from draft-06 can be just the boolean value. 

265 if isinstance(schema, dict): 

266 schema_version = schema.get('$schema', '') 

267 if 'draft-04' in schema_version: 

268 return CodeGeneratorDraft04 

269 if 'draft-06' in schema_version: 

270 return CodeGeneratorDraft06 

271 return CodeGeneratorDraft07