Skip to content

Commit 468b016

Browse files
committed
issue python-jsonschema#158: Unroll scope-resolution optionally appening into a list whe
not null (instead of using a context-manager each time) Roughly x 1.5 faster
1 parent ba8c0d0 commit 468b016

File tree

2 files changed

+63
-33
lines changed

2 files changed

+63
-33
lines changed

jsonschema/tests/test_benchmarks.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def test_V3_meta_schema(self):
4545
4646
278 runs in 2.00 sec
4747
ms/run: mean(7.17), std(0.56), MIN(6.50), MAX(10.51)
48+
49+
unroll_scopes: Time @ Xeon 3.2GHz (x 1.4 faster)::
50+
51+
348 runs in 2.00 sec
52+
ms/run: mean(5.73), std(0.38), MIN(5.50), MAX(8.00)
4853
"""
4954

5055
stats = []
@@ -65,6 +70,11 @@ def test_V4_meta_schema(self):
6570
6671
164 runs in 2.00 sec
6772
ms/run: mean(12.15), std(1.36), MIN(11.01), MAX(21.02)
73+
74+
unroll_scopes: Time @ Xeon 3.2GHz (x 1.22 faster)::
75+
76+
201 runs in 2.01 sec
77+
ms/run: mean(9.95), std(0.57), MIN(9.50), MAX(12.50)
6878
"""
6979

7080
stats = []
@@ -85,6 +95,11 @@ def test_both_meta_schemas(self):
8595
8696
104 runs in 2.01 sec
8797
ms/run: mean(19.13), std(1.12), MIN(18.01), MAX(23.02)
98+
99+
unroll_scopes: Time @ Xeon 3.2GHz (x 1.19 faster)::
100+
101+
125 runs in 2.02 sec
102+
ms/run: mean(16.00), std(0.88), MIN(15.00), MAX(19.00)
88103
"""
89104

90105
v_classes = [Draft3Validator, Draft4Validator]
@@ -107,6 +122,11 @@ def test_ref_model(self):
107122
108123
16 runs in 2.00 sec
109124
ms/run: mean(117.80), std(4.32), MIN(113.09), MAX(127.60)
125+
126+
unroll_scopes: Time @ Xeon 3.2GHz (x 1.8 faster)::
127+
128+
30 runs in 2.02 sec
129+
ms/run: mean(65.05), std(2.11), MIN(62.01), MAX(71.51)
110130
"""
111131

112132
stats = []

jsonschema/validators.py

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -79,30 +79,38 @@ def iter_errors(self, instance, _schema=None):
7979
if _schema is None:
8080
_schema = self.schema
8181

82-
with self.resolver.in_scope(_schema.get(u"id", u"")):
83-
ref = _schema.get(u"$ref")
84-
if ref is not None:
85-
validators = [(u"$ref", ref)]
86-
else:
87-
validators = iteritems(_schema)
88-
89-
for k, v in validators:
90-
validator = self.VALIDATORS.get(k)
91-
if validator is None:
92-
continue
93-
94-
errors = validator(self, v, instance, _schema) or ()
95-
for error in errors:
96-
# set details if not already set by the called fn
97-
error._set(
98-
validator=k,
99-
validator_value=v,
100-
instance=instance,
101-
schema=_schema,
102-
)
103-
if k != u"$ref":
104-
error.schema_path.appendleft(k)
105-
yield error
82+
scope = _schema.get(u"id")
83+
has_scope = scope
84+
if has_scope:
85+
self.resolver.push_scope(scope)
86+
87+
88+
ref = _schema.get(u"$ref")
89+
if ref is not None:
90+
validators = [(u"$ref", ref)]
91+
else:
92+
validators = iteritems(_schema)
93+
94+
for k, v in validators:
95+
validator = self.VALIDATORS.get(k)
96+
if validator is None:
97+
continue
98+
99+
errors = validator(self, v, instance, _schema) or ()
100+
for error in errors:
101+
# set details if not already set by the called fn
102+
error._set(
103+
validator=k,
104+
validator_value=v,
105+
instance=instance,
106+
schema=_schema,
107+
)
108+
if k != u"$ref":
109+
error.schema_path.appendleft(k)
110+
yield error
111+
112+
if has_scope:
113+
self.resolver.pop_scope()
106114

107115
def descend(self, instance, schema, path=None, schema_path=None):
108116
for error in self.iter_errors(instance, schema):
@@ -240,6 +248,8 @@ def __init__(
240248
self.cache_remote = cache_remote
241249
self.handlers = dict(handlers)
242250

251+
252+
self.old_scopes = []
243253
self.store = _utils.URIDict(
244254
(id, validator.META_SCHEMA)
245255
for id, validator in iteritems(meta_schemas)
@@ -259,14 +269,13 @@ def from_schema(cls, schema, *args, **kwargs):
259269

260270
return cls(schema.get(u"id", u""), schema, *args, **kwargs)
261271

262-
@contextlib.contextmanager
263-
def in_scope(self, scope):
272+
def push_scope(self, resoultion_scope):
264273
old_scope = self.resolution_scope
265-
self.resolution_scope = urljoin(old_scope, scope)
266-
try:
267-
yield
268-
finally:
269-
self.resolution_scope = old_scope
274+
self.old_scopes.append(old_scope)
275+
self.resolution_scope = urljoin(old_scope, resoultion_scope)
276+
277+
def pop_scope(self):
278+
self.resolution_scope = self.old_scopes.pop()
270279

271280
@contextlib.contextmanager
272281
def resolving(self, ref):
@@ -293,8 +302,9 @@ def resolving(self, ref):
293302

294303
old_base_uri, self.base_uri = self.base_uri, uri
295304
try:
296-
with self.in_scope(uri):
297-
yield self.resolve_fragment(document, fragment)
305+
self.push_scope(uri)
306+
yield self.resolve_fragment(document, fragment)
307+
self.pop_scope()
298308
finally:
299309
self.base_uri = old_base_uri
300310

0 commit comments

Comments
 (0)