Perf improvements by using a cache.

This commit is contained in:
Daniel Nephin 2015-02-27 21:15:08 -05:00
parent 812392bbd4
commit 613cf3e1a0
3 changed files with 54 additions and 47 deletions

View File

@ -38,6 +38,22 @@ class URIDict(MutableMapping):
return repr(self.store)
class Cache(object):
"""Cache the result of a function, using the arguments to the function as
the key.
"""
def __init__(self, func):
self.func = func
self._cache = {}
def __call__(self, *args):
if args in self._cache:
return self._cache[args]
self._cache[args] = value = self.func(*args)
return value
class Unset(object):
"""
An as-of-yet unset attribute or unprovided default parameter.

View File

@ -775,11 +775,11 @@ class TestRefResolver(unittest.TestCase):
self.assertEqual(resolved, self.referrer["properties"]["foo"])
def test_it_resolves_local_refs_with_id(self):
schema = {"id": "foo://bar/schema#", "a": {"foo": "bar"}}
schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
resolver = RefResolver.from_schema(schema)
with resolver.resolving("#/a") as resolved:
self.assertEqual(resolved, schema["a"])
with resolver.resolving("foo://bar/schema#/a") as resolved:
with resolver.resolving("http://bar/schema#/a") as resolved:
self.assertEqual(resolved, schema["a"])
def test_it_retrieves_stored_refs(self):
@ -815,7 +815,7 @@ class TestRefResolver(unittest.TestCase):
def test_it_can_construct_a_base_uri_from_a_schema(self):
schema = {"id" : "foo"}
resolver = RefResolver.from_schema(schema)
self.assertEqual(resolver.base_uri.url, "foo")
self.assertEqual(resolver.resolution_scope, "foo")
with resolver.resolving("") as resolved:
self.assertEqual(resolved, schema)
with resolver.resolving("#") as resolved:
@ -828,7 +828,7 @@ class TestRefResolver(unittest.TestCase):
def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
schema = {}
resolver = RefResolver.from_schema(schema)
self.assertEqual(resolver.base_uri.url, "")
self.assertEqual(resolver.resolution_scope, "")
with resolver.resolving("") as resolved:
self.assertEqual(resolved, schema)
with resolver.resolving("#") as resolved:
@ -863,9 +863,7 @@ class TestRefResolver(unittest.TestCase):
)
with resolver.resolving(ref):
pass
with resolver.resolving(ref):
pass
self.assertEqual(foo_handler.call_count, 2)
self.assertEqual(foo_handler.call_count, 1)
def test_if_you_give_it_junk_you_get_a_resolution_error(self):
ref = "foo://bar"

View File

@ -11,8 +11,7 @@ except ImportError:
from jsonschema import _utils, _validators
from jsonschema.compat import (
Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen, DefragResult,
Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen,
str_types, int_types, iteritems,
)
from jsonschema.exceptions import ErrorTree # Backwards compatibility # noqa
@ -109,7 +108,7 @@ def create(meta_schema, validators=(), version=None, default_types=None): # noq
yield error
finally:
if scope:
self.resolver.pop_scope()
self.resolver.scopes_stack.pop()
def descend(self, instance, schema, path=None, schema_path=None):
for error in self.iter_errors(instance, schema):
@ -240,21 +239,21 @@ class RefResolver(object):
def __init__(
self, base_uri, referrer, store=(), cache_remote=True, handlers=(),
):
base_uri = urldefrag(base_uri)
self.base_uri = base_uri
self.resolution_scope = base_uri
# This attribute is not used, it is for backwards compatibility
self.referrer = referrer
self.cache_remote = cache_remote
self.handlers = dict(handlers)
self.scopes_stack = []
self.scopes_stack = [base_uri]
self.store = _utils.URIDict(
(id, validator.META_SCHEMA)
for id, validator in iteritems(meta_schemas)
)
self.store.update(store)
self.store[base_uri.url] = referrer
self.store[base_uri] = referrer
self.urljoin_cache = _utils.Cache(urljoin)
self.resolve_cache = _utils.Cache(self.resolve_from_url)
@classmethod
def from_schema(cls, schema, *args, **kwargs):
@ -268,19 +267,21 @@ class RefResolver(object):
return cls(schema.get(u"id", u""), schema, *args, **kwargs)
def push_scope(self, scope, is_defragged=False):
old_scope = self.resolution_scope
self.scopes_stack.append(old_scope)
if not is_defragged:
scope = urldefrag(scope)
self.resolution_scope = DefragResult(
urljoin(old_scope.url, scope.url, allow_fragments=False)
if scope.url else old_scope.url,
scope.fragment
)
def push_scope(self, scope):
self.scopes_stack.append(
self.urljoin_cache(self.resolution_scope, scope))
def pop_scope(self):
self.resolution_scope = self.scopes_stack.pop()
@property
def resolution_scope(self):
return self.scopes_stack[-1]
@contextlib.contextmanager
def in_scope(self, scope):
self.push_scope(scope)
try:
yield
finally:
self.scopes_stack.pop()
@contextlib.contextmanager
def resolving(self, ref):
@ -291,33 +292,25 @@ class RefResolver(object):
:argument str ref: reference to resolve
"""
url = self.urljoin_cache(self.resolution_scope, ref)
ref = urldefrag(ref)
if ref.url:
url = urljoin(
self.resolution_scope.url,
ref.url,
allow_fragments=False)
else:
url = self.resolution_scope.url
self.push_scope(url)
try:
document = self.store[url]
yield self.resolve_cache(url)
finally:
self.scopes_stack.pop()
def resolve_from_url(self, url):
ref = urldefrag(url)
try:
document = self.store[ref.url]
except KeyError:
try:
document = self.resolve_remote(url)
document = self.resolve_remote(ref.url)
except Exception as exc:
raise RefResolutionError(exc)
uri = DefragResult(url, ref.fragment)
old_base_uri, self.base_uri = self.base_uri, uri
self.push_scope(uri, is_defragged=True)
try:
yield self.resolve_fragment(document, ref.fragment)
finally:
self.pop_scope()
self.base_uri = old_base_uri
return self.resolve_fragment(document, ref.fragment)
def resolve_fragment(self, document, fragment):
"""