diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/pyramid/request.py | 143 |
1 files changed, 85 insertions, 58 deletions
diff --git a/src/pyramid/request.py b/src/pyramid/request.py index d79aad9bf..0d3cf481b 100644 --- a/src/pyramid/request.py +++ b/src/pyramid/request.py @@ -339,41 +339,41 @@ class RequestLocalCache: """ A store that caches values during for the lifecycle of a request. - Instantiate a cache object and use it to decorate methods or functions - that accept a request parameter. Any other arguments are not used as - cache keys so make sure they are constant across calls per-request. + Wrapping Functions - Wrapping methods: + Instantiate and use it to decorate functions that accept a request + parameter. The result is cached and returned in subsequent invocations + of the function. .. code-block:: python - class SecurityPolicy: - identity_cache = RequestLocalCache() + @RequestLocalCache() + def get_user(request): + result = ... # do some expensive computations + return result - @identity_cache - def authenticated_identity(self, request): - result = ... # do some expensive computations - return result + Wrapping Methods - Wrapping functions: + A method can be wrapped but it needs to be bound to an instance such that + it only accepts one argument - the request. .. code-block:: python - user_cache = RequestLocalCache() + class SecurityPolicy: + def __init__(self): + self.identity_cache = RequestLocalCache(self.load_identity) - @user_cache - def get_user(request): - result = ... # do some expensive computations - return result + def load_identity(self, request): + result = ... # do some expensive computations + return result - It's also possible to inspect and influence the cache during runtime using - :meth:`.get`, :meth:`.set` and :meth:`.clear`. Using these methods, the - cache can be used directly as well, without using it as a decorator. + def authenticated_identity(self, request): + return self.identity_cache.get_or_create(request) - The cache will release resources aggressively by utilizing - :meth:`pyramid.request.Request.add_finished_callback`, but it will also - maintain a weakref to the request and cleanup when it is garbage collected - if the callbacks are not invoked for some reason. + The cache maintains a weakref to each request and will release the cached + values when the request is garbage-collected. However, in most scenarios, + it will release resources earlier via + :meth:`pyramid.request.Request.add_finished_callback`. .. versionadded:: 2.0 @@ -381,56 +381,62 @@ class RequestLocalCache: NO_VALUE = Sentinel('NO_VALUE') - def __init__(self): - self.store = weakref.WeakKeyDictionary() + def __init__(self, creator=None): + self._store = weakref.WeakKeyDictionary() + self._creator = creator - def auto_adapt(decorator): - class FunctionOrMethodAdapter: - def __init__(self, inst, func): - self.inst = inst - self.__wrapped__ = func + def __call__(self, fn): + """ + Decorate and return a new function that utilizes the cache. - def __call__(self, *args, **kwargs): - return decorator(self.inst, self.__wrapped__)(*args, **kwargs) + The cache is attached as an attribute to the decorated function + such that it may be manipulated directly. For example: - def __get__(self, instance, owner): - return decorator( - self.inst, self.__wrapped__.__get__(instance, owner) - ) + .. code-block:: python - def adapt(inst, fn): - return FunctionOrMethodAdapter(inst, fn) + @RequestLocalCache() + def do_something_expensive(request): + return ... - return adapt + value = do_something_expensive(request) + do_something_expensive.cache.clear(request) - @auto_adapt - def __call__(self, fn): - """ - Decorate a method or function. + The ``fn`` is also bound as the creator on the cache such that + invocations of :meth:`.get_or_create` will use it. """ @functools.wraps(fn) - def wrapper(request, *args, **kwargs): - result = self.get(request) - if result is self.NO_VALUE: - result = fn(request, *args, **kwargs) - self.set(request, result) - request.add_finished_callback(self.clear) - return result + def wrapper(request): + return wrapper.cache.get_or_create(request, fn) + wrapper.cache = self + self._creator = fn return wrapper - del auto_adapt - - def clear(self, request): + def get_or_create(self, request, creator=None): """ - Delete the value from the cache. + Return the cached value. - The cached value is returned or :attr:`.NO_VALUE`. + If no value is cached then execute the creator, cache the result, + and return it. + + The creator may be passed in as an argument or bound to the cache + using the ``__call__`` or constructor arguments. """ - return self.store.pop(request, self.NO_VALUE) + result = self._store.get(request, self.NO_VALUE) + if result is self.NO_VALUE: + if creator is None: + creator = self._creator + if creator is None: + raise ValueError( + 'no creator function has been registered with the ' + 'cache or supplied to "get_or_create"' + ) + result = creator(request) + self.set(request, result) + return result def get(self, request, default=NO_VALUE): """ @@ -439,11 +445,32 @@ class RequestLocalCache: The cached value is returned or ``default``. """ - return self.store.get(request, default) + return self._store.get(request, default) def set(self, request, value): """ Update the cache with a new value. """ - self.store[request] = value + already_set = request in self._store + self._store[request] = value + + # avoid registering the callback more than once + if not already_set: + request.add_finished_callback(self._store.pop) + + def clear(self, request): + """ + Delete the value from the cache. + + The cached value is returned or :attr:`.NO_VALUE`. + + """ + old_value = self.NO_VALUE + if request in self._store: + old_value = self._store[request] + + # keep a value in the store so that we don't register another + # finished callback when set is invoked + self._store[request] = self.NO_VALUE + return old_value |
