summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2020-01-09 12:20:22 -0600
committerGitHub <noreply@github.com>2020-01-09 12:20:22 -0600
commit912bccb8b715b0249c2c23736c467eaee14a4e3b (patch)
tree8c8b05a388418b54d43589c042777f6bd25f7a5a /src
parent5702c3c3a4357a6071c9ba624a89655209548336 (diff)
parentf04c06c1de47373e51f6fb1b5dc1330b3df58299 (diff)
downloadpyramid-912bccb8b715b0249c2c23736c467eaee14a4e3b.tar.gz
pyramid-912bccb8b715b0249c2c23736c467eaee14a4e3b.tar.bz2
pyramid-912bccb8b715b0249c2c23736c467eaee14a4e3b.zip
Merge pull request #3561 from mmerickel/request-local-cache
Request local cache
Diffstat (limited to 'src')
-rw-r--r--src/pyramid/paster.py6
-rw-r--r--src/pyramid/request.py134
-rw-r--r--src/pyramid/scripting.py8
3 files changed, 148 insertions, 0 deletions
diff --git a/src/pyramid/paster.py b/src/pyramid/paster.py
index 22c09e41a..00c1a8915 100644
--- a/src/pyramid/paster.py
+++ b/src/pyramid/paster.py
@@ -107,6 +107,12 @@ def bootstrap(config_uri, request=None, options=None):
Added the ability to use the return value as a context manager.
+ .. versionchanged:: 2.0
+
+ Request finished callbacks added via
+ :meth:`pyramid.request.Request.add_finished_callback` will be invoked
+ by the ``closer``.
+
"""
app = get_app(config_uri, options=options)
env = prepare(request)
diff --git a/src/pyramid/request.py b/src/pyramid/request.py
index d65be2a2f..62bd22589 100644
--- a/src/pyramid/request.py
+++ b/src/pyramid/request.py
@@ -1,4 +1,6 @@
from collections import deque
+import functools
+import weakref
from webob import BaseRequest
from zope.interface import implementer
from zope.interface.interface import InterfaceClass
@@ -17,6 +19,7 @@ from pyramid.url import URLMethodsMixin
from pyramid.util import (
InstancePropertyHelper,
InstancePropertyMixin,
+ Sentinel,
bytes_,
text_,
)
@@ -330,3 +333,134 @@ def apply_request_extensions(request, extensions=None):
InstancePropertyHelper.apply_properties(
request, extensions.descriptors
)
+
+
+class RequestLocalCache:
+ """
+ A store that caches values during for the lifecycle of a request.
+
+ Wrapping Functions
+
+ 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
+
+ @RequestLocalCache()
+ def get_user(request):
+ result = ... # do some expensive computations
+ return result
+
+ value = get_user(request)
+
+ # manipulate the cache directly
+ get_user.cache.clear(request)
+
+ The cache instance is attached to the resulting function as the ``cache``
+ attribute such that the function may be used to manipulate the cache.
+
+ Wrapping Methods
+
+ A method can be used as the creator function but it needs to be bound to
+ an instance such that it only accepts one argument - the request. An easy
+ way to do this is to bind the creator in the constructor and then use
+ :meth:`.get_or_create`:
+
+ .. code-block:: python
+
+ class SecurityPolicy:
+ def __init__(self):
+ self.identity_cache = RequestLocalCache(self.load_identity)
+
+ def load_identity(self, request):
+ result = ... # do some expensive computations
+ return result
+
+ def authenticated_identity(self, request):
+ return self.identity_cache.get_or_create(request)
+
+ 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
+
+ """
+
+ NO_VALUE = Sentinel('NO_VALUE')
+
+ def __init__(self, creator=None):
+ self._store = weakref.WeakKeyDictionary()
+ self._creator = creator
+
+ def __call__(self, fn):
+ @functools.wraps(fn)
+ def wrapper(request):
+ return wrapper.cache.get_or_create(request, fn)
+
+ wrapper.cache = self
+ self._creator = fn
+ return wrapper
+
+ def get_or_create(self, request, creator=None):
+ """
+ Return the value from the cache. Compute if necessary.
+
+ 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
+ by decorating a function or supplied as a constructor argument.
+
+ """
+ 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):
+ """
+ Return the value from the cache.
+
+ The cached value is returned or ``default``.
+
+ """
+ return self._store.get(request, default)
+
+ def set(self, request, value):
+ """
+ Update the cache with a new 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
diff --git a/src/pyramid/scripting.py b/src/pyramid/scripting.py
index abcdd1030..bf23f1008 100644
--- a/src/pyramid/scripting.py
+++ b/src/pyramid/scripting.py
@@ -74,6 +74,12 @@ def prepare(request=None, registry=None):
Added the ability to use the return value as a context manager.
+ .. versionchanged:: 2.0
+
+ Request finished callbacks added via
+ :meth:`pyramid.request.Request.add_finished_callback` will be invoked
+ by the ``closer``.
+
"""
if registry is None:
registry = getattr(request, 'registry', global_registries.last)
@@ -94,6 +100,8 @@ def prepare(request=None, registry=None):
apply_request_extensions(request)
def closer():
+ if request.finished_callbacks:
+ request._process_finished_callbacks()
ctx.end()
root_factory = registry.queryUtility(