Skip to content

Commit ee1a256

Browse files
committed
Use lru_cache
1 parent ca59f3f commit ee1a256

File tree

7 files changed

+45
-33
lines changed

7 files changed

+45
-33
lines changed

jsonschema/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
Draft3Validator, Draft4Validator, RefResolver, validate
2020
)
2121

22-
23-
__version__ = "2.5.0-dev"
24-
22+
from jsonschema.version import __version__
2523

2624
# flake8: noqa

jsonschema/_utils.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,6 @@ def __repr__(self):
3838
return repr(self.store)
3939

4040

41-
class Cache(object):
42-
"""Cache the result of a function, using the arguments to the function as
43-
the key.
44-
"""
45-
46-
def __init__(self, func):
47-
self.func = func
48-
self._cache = {}
49-
50-
def __call__(self, *args):
51-
if args in self._cache:
52-
return self._cache[args]
53-
self._cache[args] = value = self.func(*args)
54-
return value
55-
56-
5741
class Unset(object):
5842
"""
5943
An as-of-yet unset attribute or unprovided default parameter.

jsonschema/compat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
if PY3:
1515
zip = zip
16+
from functools import lru_cache
1617
from io import StringIO
1718
from urllib.parse import (
1819
unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit
@@ -23,6 +24,7 @@
2324
iteritems = operator.methodcaller("items")
2425
else:
2526
from itertools import izip as zip # noqa
27+
from repoze.lru import lru_cache
2628
from StringIO import StringIO
2729
from urlparse import (
2830
urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit # noqa

jsonschema/tests/test_validators.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from collections import deque
2-
from contextlib import contextmanager
32
import json
43

54
from jsonschema import FormatChecker, ValidationError
@@ -870,6 +869,13 @@ def test_if_you_give_it_junk_you_get_a_resolution_error(self):
870869
pass
871870
self.assertEqual(str(err.exception), "Oh no! What's this?")
872871

872+
def test_helpful_error_message_on_failed_pop_scope(self):
873+
resolver = RefResolver("", {})
874+
resolver.pop_scope()
875+
with self.assertRaises(RefResolutionError) as exc:
876+
resolver.pop_scope()
877+
self.assertIn("Failed to pop the scope", str(exc.exception))
878+
873879

874880
def sorted_errors(errors):
875881
def key(error):

jsonschema/validators.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from jsonschema import _utils, _validators
1313
from jsonschema.compat import (
1414
Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen,
15-
str_types, int_types, iteritems,
15+
str_types, int_types, iteritems, lru_cache,
1616
)
1717
from jsonschema.exceptions import ErrorTree # Backwards compatibility # noqa
1818
from jsonschema.exceptions import RefResolutionError, SchemaError, UnknownType
@@ -233,27 +233,31 @@ class RefResolver(object):
233233
first resolution
234234
:argument dict handlers: a mapping from URI schemes to functions that
235235
should be used to retrieve them
236-
236+
:arguments callable cache_func: a function decorator used to cache
237+
expensive calls. Should support the `functools.lru_cache` interface.
238+
:argument int cache_maxsize: number of items to store in the cache. Set
239+
this to 0 to disable caching. Defaults to 1000.
237240
"""
238241

239242
def __init__(
240243
self, base_uri, referrer, store=(), cache_remote=True, handlers=(),
244+
cache_func=lru_cache, cache_maxsize=1000,
241245
):
242246
# This attribute is not used, it is for backwards compatibility
243247
self.referrer = referrer
244248
self.cache_remote = cache_remote
245249
self.handlers = dict(handlers)
246250

247-
self.scopes_stack = [base_uri]
251+
self._scopes_stack = [base_uri]
248252
self.store = _utils.URIDict(
249253
(id, validator.META_SCHEMA)
250254
for id, validator in iteritems(meta_schemas)
251255
)
252256
self.store.update(store)
253257
self.store[base_uri] = referrer
254258

255-
self.urljoin_cache = _utils.Cache(urljoin)
256-
self.resolve_cache = _utils.Cache(self.resolve_from_url)
259+
self._urljoin_cache = cache_func(cache_maxsize)(urljoin)
260+
self._resolve_cache = cache_func(cache_maxsize)(self.resolve_from_url)
257261

258262
@classmethod
259263
def from_schema(cls, schema, *args, **kwargs):
@@ -268,15 +272,21 @@ def from_schema(cls, schema, *args, **kwargs):
268272
return cls(schema.get(u"id", u""), schema, *args, **kwargs)
269273

270274
def push_scope(self, scope):
271-
self.scopes_stack.append(
272-
self.urljoin_cache(self.resolution_scope, scope))
275+
self._scopes_stack.append(
276+
self._urljoin_cache(self.resolution_scope, scope))
273277

274278
def pop_scope(self):
275-
self.scopes_stack.pop()
279+
try:
280+
self._scopes_stack.pop()
281+
except IndexError:
282+
raise RefResolutionError(
283+
"Failed to pop the scope from an empty stack. "
284+
"`pop_scope()` should only be called once for every "
285+
"`push_scope()`")
276286

277287
@property
278288
def resolution_scope(self):
279-
return self.scopes_stack[-1]
289+
return self._scopes_stack[-1]
280290

281291

282292
# Deprecated, this function is no longer used, but is preserved for
@@ -308,8 +318,8 @@ def resolve(self, ref):
308318
:argument str ref: reference to resolve
309319
310320
"""
311-
url = self.urljoin_cache(self.resolution_scope, ref)
312-
return url, self.resolve_cache(url)
321+
url = self._urljoin_cache(self.resolution_scope, ref)
322+
return url, self._resolve_cache(url)
313323

314324
def resolve_from_url(self, url):
315325
url, fragment = urldefrag(url)

jsonschema/version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "2.5.0-dev"

setup.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import os.path
12
from setuptools import setup
3+
import sys
24

3-
from jsonschema import __version__
4-
5+
# Load __version__ info globals without importing anything
6+
with open(
7+
os.path.join(os.path.dirname(__file__), 'jsonschema', 'version.py')
8+
) as fh:
9+
exec(fh.read())
510

611
with open("README.rst") as readme:
712
long_description = readme.read()
@@ -21,6 +26,11 @@
2126
"Programming Language :: Python :: Implementation :: PyPy",
2227
]
2328

29+
install_requires = []
30+
31+
if sys.version_info < (3, 2):
32+
install_requires.append('repoze.lru >= 0.6')
33+
2434
setup(
2535
name="jsonschema",
2636
version=__version__,
@@ -34,4 +44,5 @@
3444
long_description=long_description,
3545
url="http://github.com/Julian/jsonschema",
3646
entry_points={"console_scripts": ["jsonschema = jsonschema.cli:main"]},
47+
install_requires=install_requires,
3748
)

0 commit comments

Comments
 (0)