Skip to content

Commit b0e2bcc

Browse files
committed
Add detailed_exception parameter
1 parent 356bfa2 commit b0e2bcc

File tree

7 files changed

+50
-29
lines changed

7 files changed

+50
-29
lines changed

CHANGELOG.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Improved regexp for date format to comfort test suite
99
* Improved regexp for ipv4 format to comfort test suite
1010
* Added partial support of idn-hostname format
11+
* Added `detailed_exceptions` flag (default to True to avoid breaking change) - when used, results are twice as fast
1112

1213
=== 2.20.0 (2024-06-15)
1314

fastjsonschema/__init__.py

+27-21
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,22 @@
4242
.. code-block:: bash
4343
4444
$ 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
45+
fast_compiled valid ==> 0.0993900
46+
fast_compiled invalid ==> 0.0041089
47+
fast_compiled_without_exc valid ==> 0.0465258
48+
fast_compiled_without_exc invalid ==> 0.0023688
49+
fast_file valid ==> 0.0989483
50+
fast_file invalid ==> 0.0041104
51+
fast_not_compiled valid ==> 11.9572681
52+
fast_not_compiled invalid ==> 2.9512092
53+
jsonschema valid ==> 5.2233240
54+
jsonschema invalid ==> 1.3227916
55+
jsonschema_compiled valid ==> 0.4447982
56+
jsonschema_compiled invalid ==> 0.0231333
57+
jsonspec valid ==> 4.1450569
58+
jsonspec invalid ==> 1.0485777
59+
validictory valid ==> 0.2730411
60+
validictory invalid ==> 0.0183669
5961
6062
This library follows and implements `JSON schema draft-04, draft-06, and draft-07
6163
<http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also
@@ -123,7 +125,7 @@
123125
)
124126

125127

126-
def validate(definition, data, handlers={}, formats={}, use_default=True, use_formats=True):
128+
def validate(definition, data, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
127129
"""
128130
Validation function for lazy programmers or for use cases when you need
129131
to call validation only once, so you do not have to compile it first.
@@ -139,12 +141,12 @@ def validate(definition, data, handlers={}, formats={}, use_default=True, use_fo
139141
140142
Preferred is to use :any:`compile` function.
141143
"""
142-
return compile(definition, handlers, formats, use_default, use_formats)(data)
144+
return compile(definition, handlers, formats, use_default, use_formats, detailed_exceptions)(data)
143145

144146

145147
#TODO: Change use_default to False when upgrading to version 3.
146148
# pylint: disable=redefined-builtin,dangerous-default-value,exec-used
147-
def compile(definition, handlers={}, formats={}, use_default=True, use_formats=True):
149+
def compile(definition, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
148150
"""
149151
Generates validation function for validating JSON schema passed in ``definition``.
150152
Example:
@@ -200,13 +202,16 @@ def compile(definition, handlers={}, formats={}, use_default=True, use_formats=T
200202
off by passing `use_formats=False`. When disabled, custom formats are
201203
disabled as well. (Added in 2.19.0.)
202204
205+
If you don't need detailed exceptions, you can turn the details off and gain
206+
additional performance by passing `detailed_exceptions=False`.
207+
203208
Exception :any:`JsonSchemaDefinitionException` is raised when generating the
204209
code fails (bad definition).
205210
206211
Exception :any:`JsonSchemaValueException` is raised from generated function when
207212
validation fails (data do not follow the definition).
208213
"""
209-
resolver, code_generator = _factory(definition, handlers, formats, use_default, use_formats)
214+
resolver, code_generator = _factory(definition, handlers, formats, use_default, use_formats, detailed_exceptions)
210215
global_state = code_generator.global_state
211216
# Do not pass local state so it can recursively call itself.
212217
exec(code_generator.func_code, global_state)
@@ -217,7 +222,7 @@ def compile(definition, handlers={}, formats={}, use_default=True, use_formats=T
217222

218223

219224
# pylint: disable=dangerous-default-value
220-
def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_formats=True):
225+
def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
221226
"""
222227
Generates validation code for validating JSON schema passed in ``definition``.
223228
Example:
@@ -240,22 +245,23 @@ def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_f
240245
Exception :any:`JsonSchemaDefinitionException` is raised when generating the
241246
code fails (bad definition).
242247
"""
243-
_, code_generator = _factory(definition, handlers, formats, use_default, use_formats)
248+
_, code_generator = _factory(definition, handlers, formats, use_default, use_formats, detailed_exceptions)
244249
return (
245250
'VERSION = "' + VERSION + '"\n' +
246251
code_generator.global_state_code + '\n' +
247252
code_generator.func_code
248253
)
249254

250255

251-
def _factory(definition, handlers, formats={}, use_default=True, use_formats=True):
256+
def _factory(definition, handlers, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
252257
resolver = RefResolver.from_schema(definition, handlers=handlers, store={})
253258
code_generator = _get_code_generator_class(definition)(
254259
definition,
255260
resolver=resolver,
256261
formats=formats,
257262
use_default=use_default,
258263
use_formats=use_formats,
264+
detailed_exceptions=detailed_exceptions,
259265
)
260266
return resolver, code_generator
261267

fastjsonschema/draft04.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class CodeGeneratorDraft04(CodeGenerator):
3434
'uri': r'^\w+:(\/?\/?)[^\s]+\Z',
3535
}
3636

37-
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True):
38-
super().__init__(definition, resolver)
37+
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
38+
super().__init__(definition, resolver, detailed_exceptions)
3939
self._custom_formats = formats
4040
self._use_formats = use_formats
4141
self._use_default = use_default

fastjsonschema/draft06.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class CodeGeneratorDraft06(CodeGeneratorDraft04):
1616
),
1717
})
1818

19-
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True):
20-
super().__init__(definition, resolver, formats, use_default, use_formats)
19+
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
20+
super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions)
2121
self._json_keywords_to_function.update((
2222
('exclusiveMinimum', self.generate_exclusive_minimum),
2323
('exclusiveMaximum', self.generate_exclusive_maximum),

fastjsonschema/draft07.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class CodeGeneratorDraft07(CodeGeneratorDraft06):
1717
),
1818
})
1919

20-
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True):
21-
super().__init__(definition, resolver, formats, use_default, use_formats)
20+
def __init__(self, definition, resolver=None, formats={}, use_default=True, use_formats=True, detailed_exceptions=True):
21+
super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions)
2222
# pylint: disable=duplicate-code
2323
self._json_keywords_to_function.update((
2424
('if', self.generate_if_then_else),

fastjsonschema/generator.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ class CodeGenerator:
2929

3030
INDENT = 4 # spaces
3131

32-
def __init__(self, definition, resolver=None):
32+
def __init__(self, definition, resolver=None, detailed_exceptions=True):
3333
self._code = []
3434
self._compile_regexps = {}
3535
self._custom_formats = {}
36+
self._detailed_exceptions = detailed_exceptions
3637

3738
# Any extra library should be here to be imported only once.
3839
# Lines are imports to be printed in the file and objects
@@ -266,6 +267,10 @@ def exc(self, msg, *args, append_to_msg=None, rule=None):
266267
"""
267268
Short-cut for creating raising exception in the code.
268269
"""
270+
if not self._detailed_exceptions:
271+
self.l('raise JsonSchemaValueException("'+msg+'")', *args)
272+
return
273+
269274
arg = '"'+msg+'"'
270275
if append_to_msg:
271276
arg += ' + (' + append_to_msg + ')'

performance.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575

7676
fastjsonschema_validate = fastjsonschema.compile(JSON_SCHEMA)
77+
fastjsonschema_validate_without_exc = fastjsonschema.compile(JSON_SCHEMA, detailed_exceptions=False)
7778

7879

7980
def fast_compiled(value, _):
@@ -84,6 +85,10 @@ def fast_not_compiled(value, json_schema):
8485
fastjsonschema.compile(json_schema)(value)
8586

8687

88+
def fast_compiled_without_exc(value, _):
89+
fastjsonschema_validate_without_exc(value)
90+
91+
8792
validator_class = jsonschema.validators.validator_for(JSON_SCHEMA)
8893
validator = validator_class(JSON_SCHEMA)
8994

@@ -118,6 +123,7 @@ def t(func, valid_values=True):
118123
jsonschema,
119124
jsonspec,
120125
fast_compiled,
126+
fast_compiled_without_exc,
121127
fast_file,
122128
fast_not_compiled,
123129
jsonschema_compiled,
@@ -139,14 +145,17 @@ def t(func, valid_values=True):
139145
""".format(func))
140146

141147
res = timeit.timeit(code, setup, number=NUMBER)
142-
print('{:<20} {:<10} ==> {:10.7f}'.format(module, 'valid' if valid_values else 'invalid', res))
148+
print('{:<30} {:<10} ==> {:10.7f}'.format(module, 'valid' if valid_values else 'invalid', res))
143149

144150

145151
print('Number: {}'.format(NUMBER))
146152

147153
t('fast_compiled')
148154
t('fast_compiled', valid_values=False)
149155

156+
t('fast_compiled_without_exc')
157+
t('fast_compiled_without_exc', valid_values=False)
158+
150159
t('fast_file')
151160
t('fast_file', valid_values=False)
152161

0 commit comments

Comments
 (0)