Skip to content

Commit 0306213

Browse files
committed
issue #158: TRY to speed-up scope & $ref url-handling by keeping
fragments separated from URL (and avoid redunant frag/defrag).
1 parent f0422be commit 0306213

File tree

3 files changed

+60
-24
lines changed

3 files changed

+60
-24
lines changed

jsonschema/compat.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import unicode_literals
2-
import sys
2+
33
import operator
4+
import sys
5+
46

57
try:
68
from collections import MutableMapping, Sequence # noqa
@@ -13,7 +15,8 @@
1315
zip = zip
1416
from io import StringIO
1517
from urllib.parse import (
16-
unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit
18+
unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit,
19+
DefragResult
1720
)
1821
from urllib.request import urlopen
1922
str_types = str,
@@ -23,7 +26,8 @@
2326
from itertools import izip as zip # noqa
2427
from StringIO import StringIO
2528
from urlparse import (
26-
urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit # noqa
29+
urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit, # noqa
30+
DefragResult
2731
)
2832
from urllib import unquote # noqa
2933
from urllib2 import urlopen # noqa
@@ -47,7 +51,7 @@ def urldefrag(url):
4751
else:
4852
defrag = url
4953
frag = ''
50-
return defrag, frag
54+
return DefragResult(defrag, frag)
5155

5256

5357
# flake8: noqa

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+
splitted_fragments: Time @ Xeon 3.2GHz (x 1.05 faster)::
50+
51+
290 runs in 2.00 sec
52+
ms/run: mean(6.88), std(0.66), MIN(6.00), MAX(12.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+
splitted_fragments: Time @ Xeon 3.2GHz (x 1.16 faster)::
75+
76+
191 runs in 2.01 sec
77+
ms/run: mean(10.47), std(0.81), MIN(9.50), MAX(14.00)
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+
splitted_fragments: Time @ Xeon 3.2GHz (x 1.11 faster)::
100+
101+
115 runs in 2.00 sec
102+
ms/run: mean(17.24), std(1.24), MIN(16.00), MAX(24.51)
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+
splitted_fragments: Time @ Xeon 3.2GHz (x 1.12 faster)::
127+
128+
19 runs in 2.10 sec
129+
ms/run: mean(105.07), std(2.45), MIN(102.02), MAX(109.02)
110130
"""
111131

112132
stats = []

jsonschema/validators.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
from jsonschema import _utils, _validators
1313
from jsonschema.compat import (
14-
Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen,
14+
Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen, DefragResult,
15+
1516
str_types, int_types, iteritems,
1617
)
1718
from jsonschema.exceptions import ErrorTree # Backwards compatibility # noqa
@@ -222,7 +223,7 @@ class RefResolver(object):
222223
223224
:argument str base_uri: URI of the referring document
224225
:argument referrer: the actual referring document
225-
:argument dict store: a mapping from URIs to documents to cache
226+
:argument dict store: a mapping from URIs (without fragments!) to documents to cache
226227
:argument bool cache_remote: whether remote refs should be cached after
227228
first resolution
228229
:argument dict handlers: a mapping from URI schemes to functions that
@@ -233,6 +234,7 @@ class RefResolver(object):
233234
def __init__(
234235
self, base_uri, referrer, store=(), cache_remote=True, handlers=(),
235236
):
237+
base_uri = urldefrag(base_uri)
236238
self.base_uri = base_uri
237239
self.resolution_scope = base_uri
238240
# This attribute is not used, it is for backwards compatibility
@@ -241,11 +243,11 @@ def __init__(
241243
self.handlers = dict(handlers)
242244

243245
self.store = _utils.URIDict(
244-
(id, validator.META_SCHEMA)
246+
(id, validator.META_SCHEMA) ## IDs assumed pure urls (no fragments).
245247
for id, validator in iteritems(meta_schemas)
246248
)
247249
self.store.update(store)
248-
self.store[base_uri] = referrer
250+
self.store[base_uri.url] = referrer
249251

250252
@classmethod
251253
def from_schema(cls, schema, *args, **kwargs):
@@ -260,13 +262,22 @@ def from_schema(cls, schema, *args, **kwargs):
260262
return cls(schema.get(u"id", u""), schema, *args, **kwargs)
261263

262264
@contextlib.contextmanager
263-
def in_scope(self, scope):
264-
old_scope = self.resolution_scope
265-
self.resolution_scope = urljoin(old_scope, scope)
266-
try:
265+
def in_scope(self, scope, is_defragged=False):
266+
if not scope:
267267
yield
268-
finally:
269-
self.resolution_scope = old_scope
268+
else:
269+
old_scope = self.resolution_scope
270+
if not is_defragged:
271+
scope = urldefrag(scope)
272+
self.resolution_scope = DefragResult(
273+
urljoin(old_scope.url, scope.url, allow_fragments=False)
274+
if scope.url else old_scope.url,
275+
scope.fragment
276+
)
277+
try:
278+
yield
279+
finally:
280+
self.resolution_scope = old_scope
270281

271282
@contextlib.contextmanager
272283
def resolving(self, ref):
@@ -278,23 +289,24 @@ def resolving(self, ref):
278289
279290
"""
280291

281-
full_uri = urljoin(self.resolution_scope, ref)
282-
uri, fragment = urldefrag(full_uri)
283-
if not uri:
284-
uri = self.base_uri
292+
ref = urldefrag(ref)
285293

286-
if uri in self.store:
287-
document = self.store[uri]
288-
else:
294+
url = urljoin(self.resolution_scope.url, ref.url, allow_fragments=False) \
295+
if ref.url else self.resolution_scope.url
296+
297+
try:
298+
document = self.store[url]
299+
except KeyError:
289300
try:
290-
document = self.resolve_remote(uri)
301+
document = self.resolve_remote(url)
291302
except Exception as exc:
292303
raise RefResolutionError(exc)
293304

305+
uri = DefragResult(url, ref.fragment)
294306
old_base_uri, self.base_uri = self.base_uri, uri
295307
try:
296-
with self.in_scope(uri):
297-
yield self.resolve_fragment(document, fragment)
308+
with self.in_scope(uri, is_defragged=True):
309+
yield self.resolve_fragment(document, ref.fragment)
298310
finally:
299311
self.base_uri = old_base_uri
300312

0 commit comments

Comments
 (0)