Skip to content

Commit 7f30b0a

Browse files
committed
Add request-level caching for registry lookups
Cache value lookups, __contains__, and forInterface proxies per request. Add get_registry() convenience function caching getUtility(IRegistry).
1 parent f09c737 commit 7f30b0a

File tree

4 files changed

+491
-7
lines changed

4 files changed

+491
-7
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
python_requires=">=3.10",
4141
install_requires=[
4242
"Zope",
43+
"zope.globalrequest",
4344
],
4445
extras_require={"test": ["plone.schema"]},
4546
entry_points="""

src/plone/registry/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from plone.registry.fieldref import FieldRef
22
from plone.registry.record import Record
3+
from plone.registry.registry import get_registry
34
from plone.registry.registry import Registry

src/plone/registry/registry.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,47 @@
1111
from plone.registry.record import Record
1212
from plone.registry.recordsproxy import RecordsProxy
1313
from plone.registry.recordsproxy import RecordsProxyCollection
14+
from zope.component import getUtility
1415
from zope.component import queryAdapter
1516
from zope.event import notify
17+
from zope.globalrequest import getRequest
1618
from zope.interface import implementer
1719
from zope.schema import getFieldNames
1820
from zope.schema import getFieldsInOrder
1921

2022
import re
2123
import warnings
2224

25+
_CACHE_MARKER = object()
26+
27+
28+
def _get_request_cache():
29+
"""Return the per-request value cache dict, or None if no request."""
30+
request = getRequest()
31+
if request is None:
32+
return None
33+
cache = getattr(request, "_plone_registry_cache", None)
34+
if cache is None:
35+
cache = {}
36+
request._plone_registry_cache = cache
37+
return cache
38+
39+
40+
def get_registry():
41+
"""Get the registry utility, cached per-request.
42+
43+
Falls back to getUtility(IRegistry) when no request is active.
44+
"""
45+
request = getRequest()
46+
if request is not None:
47+
registry = getattr(request, "_plone_registry", None)
48+
if registry is not None:
49+
return registry
50+
registry = getUtility(IRegistry)
51+
if request is not None:
52+
request._plone_registry = registry
53+
return registry
54+
2355

2456
@implementer(IRegistry)
2557
class Registry(Persistent):
@@ -31,20 +63,40 @@ def __init__(self):
3163
# Basic value access API
3264

3365
def __getitem__(self, name):
34-
# Fetch straight from records._values to avoid loading the field
35-
# as a separate persistent object
36-
return self.records._values[name]
66+
cache = _get_request_cache()
67+
if cache is not None:
68+
value = cache.get(name, _CACHE_MARKER)
69+
if value is not _CACHE_MARKER:
70+
return value
71+
value = self.records._values[name]
72+
if cache is not None:
73+
cache[name] = value
74+
return value
3775

3876
def get(self, name, default=None):
39-
# Fetch straight from records._values to avoid loading the field
40-
# as a separate persistent object
41-
return self.records._values.get(name, default)
77+
cache = _get_request_cache()
78+
if cache is not None:
79+
value = cache.get(name, _CACHE_MARKER)
80+
if value is not _CACHE_MARKER:
81+
return value
82+
value = self.records._values.get(name, _CACHE_MARKER)
83+
if value is _CACHE_MARKER:
84+
return default
85+
if cache is not None:
86+
cache[name] = value
87+
return value
4288

4389
def __setitem__(self, name, value):
4490
# make sure we get the Record class' validation
4591
self.records[name].value = value
92+
cache = _get_request_cache()
93+
if cache is not None:
94+
cache[name] = value
4695

4796
def __contains__(self, name):
97+
cache = _get_request_cache()
98+
if cache is not None and name in cache:
99+
return True
48100
return name in self.records._values
49101

50102
# Records - make this a property so that it's readonly
@@ -65,6 +117,14 @@ def forInterface(self, interface, check=True, omit=(), prefix=None, factory=None
65117
if not prefix.endswith("."):
66118
prefix += "."
67119

120+
# Try per-request proxy cache
121+
cache = _get_request_cache()
122+
cache_key = ("__proxy__", interface.__identifier__, prefix, omit)
123+
if cache is not None:
124+
proxy = cache.get(cache_key)
125+
if proxy is not None:
126+
return proxy
127+
68128
if check:
69129
for name in getFieldNames(interface):
70130
if name not in omit and prefix + name not in self:
@@ -76,7 +136,12 @@ def forInterface(self, interface, check=True, omit=(), prefix=None, factory=None
76136
if factory is None:
77137
factory = RecordsProxy
78138

79-
return factory(self, interface, omitted=omit, prefix=prefix)
139+
proxy = factory(self, interface, omitted=omit, prefix=prefix)
140+
141+
if cache is not None:
142+
cache[cache_key] = proxy
143+
144+
return proxy
80145

81146
def registerInterface(self, interface, omit=(), prefix=None):
82147
if prefix is None:

0 commit comments

Comments
 (0)