Perf improvements by using a cache.
This commit is contained in:
parent
812392bbd4
commit
613cf3e1a0
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue