[Checkins] SVN: z3c.caching/trunk/ Add interfaces which can be used for cache response mutation and intercept plus documentation on how they might be used.
Martin Aspeli
optilude at gmx.net
Thu Dec 31 01:39:37 EST 2009
Log message for revision 107426:
Add interfaces which can be used for cache response mutation and intercept plus documentation on how they might be used.
Add (back?) ILastModified concept, plus a default adapter for views which delegates to the view's context.
Move registration from an import side-effect to ZCML. Yes, this means you have to register it yourself if you want to use it without ZCML, but you can just as easily just create a global registry object yourself if your'e not bought into Zope's mechanisms of configuration.
Changed:
U z3c.caching/trunk/README.txt
U z3c.caching/trunk/docs/HISTORY.txt
U z3c.caching/trunk/setup.py
A z3c.caching/trunk/src/z3c/caching/configure.zcml
U z3c.caching/trunk/src/z3c/caching/interfaces.py
A z3c.caching/trunk/src/z3c/caching/lastmodified.py
U z3c.caching/trunk/src/z3c/caching/registry.py
A z3c.caching/trunk/src/z3c/caching/tests/test_lastmodified.py
U z3c.caching/trunk/src/z3c/caching/tests/test_registry.py
U z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py
-=-
Modified: z3c.caching/trunk/README.txt
===================================================================
--- z3c.caching/trunk/README.txt 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/README.txt 2009-12-31 06:39:37 UTC (rev 107426)
@@ -15,11 +15,17 @@
behaviour for each ruleset.
Depending on your environment there are different options for turning
-the ruleset into HTTP caching headers. If you are using Plone_
-you can use `five.caching`_ to integrate with CacheSetup. In a WSGI
-environment you could set the ruleset in `environ` or a response header
-and add a piece of middleware which acts on those hints.
+the ruleset into HTTP caching headers.
+* If you are using Plone_ 3 and CacheFu_ you can use `five.caching`_ to
+ integrate with CacheSetup.
+* If you are using Zope 2.12 or later, you can use `plone.caching`_ to
+ integrate with the publisher events.
+* If you are using Plone 4, you can also use `plone.app.caching`_, which
+ provides UI and default operations.
+* In a WSGI environment you could set the ruleset in `environ` or a response
+ header and add a piece of middleware which acts on those hints.
+
Usage
=====
@@ -45,10 +51,13 @@
</configure>
-
This example sets up a browser view called ``frontpage_view`` and
associates it with the ``plone-content-types`` ruleset.
+You can specify either a class or an interface in the ``for`` attribute. As
+with an adapter registration, a more specific registration can be used to
+override a more generic one.
+
If you prefer to use python directly you can do so::
from z3c.caching.registry import register
@@ -56,15 +65,103 @@
register(FrontpageView, "plone-content-types")
-You can register a ruleset for objects, their interfaces or a base class.
-
To find the ruleset for an object use the ``lookup`` method::
- from z3c.caching.registry import lookup
+ from z3c.caching.registry import getGlobalRulesetRegistry
+ registry = getGlobalRulesetRegistry()
- lookup(FrontpageView)
+ cacheRule = registry.lookup(FrontpageView)
+Caching operations
+------------------
+This package does not directly include support for performing caching
+operations such as setting response headers. However, it defines interfaces
+which can be used by a framework such as `five.caching`_ or `plone.caching`_
+for this purpose.
+
+The basic principle is that a *cache rule* is looked up for the view or other
+resource published, as shown above. This is a string, which can then be mapped
+to a caching operation. Operations can be implemented using one of the
+following two interfaces::
+
+ class IResponseMutator(Interface):
+ """Represents a caching operation, typically setting of response headers.
+
+ Should be registered as a named multi-adapter from a cacheable object
+ (e.g. a view, or just Interface for a general operation) and the request.
+
+ A higher level framework is assumed to do something like this::
+
+ registry = getGlobalRulesetRegistry()
+ published = getPublishiedView(request)
+
+ cacheRule = registry.lookup(published)
+ operation = getOperationFor(cacheRule)
+
+ mutator = queryMultiAdapter((published, request,),
+ IResponseMutator, name=operation)
+ mutator(response)
+
+ Here, a cache rule is looked up for the published view. An operation is
+ then looked up for the cache rule (presuming there exists some mapping
+ from cache rules to operations) and invoked.
+ """
+
+ def __call__(response):
+ """Mutate the response
+ """
+
+ class ICacheInterceptor(Interface):
+ """Represents a caching intercept, typically for the purposes of sending
+ a 304 response.
+
+ Should be registered as a named multi-adapter from a cacheable object
+ (e.g. a view, or just Interface for a general operation) and the request.
+
+ A higher level framework is assumed to do something like this::
+
+ registry = getGlobalRulesetRegistry()
+ published = getPublishiedView(request)
+
+ cacheRule = registry.lookup(published)
+ operation = getInterceptorFor(cacheRule)
+
+ intercept = queryMultiAdapter((published, request,),
+ ICacheInterceptor, name=operation)
+ if intercept(response):
+ return # abort rendering and return what we have
+
+ # continue as normal
+ """
+
+ def __call__(response):
+ """Mutate the response if required. Return True if the response should
+ be intercepted. In this case, normal rendering may be aborted and the
+ response returned as-is.
+ """
+
+In addition, a helper adapter interface is defined which can be used to
+determine the last modified date of a given content item::
+
+ class ILastModified(Interface):
+ """An abstraction to help obtain a last-modified date for a published
+ resource.
+
+ Should be registered as an unnamed multi-adapter from a published object
+ (e.g. a view) and the request.
+ """
+
+ def __call__():
+ """Return the last-modified date, as a Python datetime object.
+ """
+
+One implementation exists for this interface: When looked up for a Zope
+browser view, it will delegate to an ``ILastModified`` adapter on the view's
+context.
+
.. _Plone: http://plone.org/
+.. _CacheFu: http://plone.org/products/cachefu
.. _five.caching: http://pypi.python.org/pypi/five.caching
-
+.. _plone.caching: http://pypi.python.org/pypi/plone.caching
+.. _plone.app.caching: http://pypi.python.org/pypi/plone.app.caching
Modified: z3c.caching/trunk/docs/HISTORY.txt
===================================================================
--- z3c.caching/trunk/docs/HISTORY.txt 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/docs/HISTORY.txt 2009-12-31 06:39:37 UTC (rev 107426)
@@ -4,9 +4,21 @@
2.0a1 - Unreleased
------------------
+* Added ``ILastModified`` implementation for a view which delegates to the
+ view's context.
+ [optilude]
+* Added interfaces for response mutation (e.g. to set interfaces) and
+ interception (304 type responses)
+ [optilude]
+* Added ``enumerate()`` method to the registry, used to list all currently
+ used cache rule ids.
+ [optilude]
+* Made the registry use the ZCA more directly.
+ [matthewwilkes]
+
1.0b1 - October 15, 2008
------------------------
Modified: z3c.caching/trunk/setup.py
===================================================================
--- z3c.caching/trunk/setup.py 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/setup.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -29,6 +29,7 @@
"setuptools",
"zope.interface",
"zope.component",
+ "zope.browser",
],
extras_require = {
"zcml": ("zope.configuration", )
Added: z3c.caching/trunk/src/z3c/caching/configure.zcml
===================================================================
--- z3c.caching/trunk/src/z3c/caching/configure.zcml (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/configure.zcml 2009-12-31 06:39:37 UTC (rev 107426)
@@ -0,0 +1,6 @@
+<configure xmlns="http://namespaces.zope.org/zope" i18n_domain="z3c.caching">
+
+ <adapter factory=".registry.RulesetRegistry" />
+ <adapter factory=".lastmodified.viewDelegateLastModified" />
+
+</configure>
Modified: z3c.caching/trunk/src/z3c/caching/interfaces.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/interfaces.py 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/src/z3c/caching/interfaces.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -1,10 +1,10 @@
from zope.interface import Interface, Attribute
class ICacheRule(Interface):
- """Represents the cache rule applied to an object."""
+ """Represents the cache rule applied to an object.
+ """
id = Attribute("The identifier of this cache rule")
-
class IRulesetRegistry(Interface):
@@ -31,3 +31,69 @@
def enumerate():
"""Return a sequence of all unique registered rule set ids (strings)
"""
+
+class IResponseMutator(Interface):
+ """Represents a caching operation, typically setting of response headers.
+
+ Should be registered as a named multi-adapter from a cacheable object
+ (e.g. a view, or just Interface for a general operation) and the request.
+
+ A higher level framework is assumed to do something like this::
+
+ registry = getGlobalRulesetRegistry()
+ published = getPublishiedView(request)
+
+ cacheRule = registry.lookup(published)
+ operation = getOperationFor(cacheRule)
+
+ mutator = queryMultiAdapter((published, request,), IResponseMutator, name=operation)
+ mutator(response)
+
+ Here, a cache rule is looked up for the published view. An operation is
+ then looked up for the cache rule (presuming there exists some mapping
+ from cache rules to operations) and invoked.
+ """
+
+ def __call__(response):
+ """Mutate the response
+ """
+
+class ICacheInterceptor(Interface):
+ """Represents a caching intercept, typically for the purposes of sending
+ a 304 response.
+
+ Should be registered as a named multi-adapter from a cacheable object
+ (e.g. a view, or just Interface for a general operation) and the request.
+
+ A higher level framework is assumed to do something like this::
+
+ registry = getGlobalRulesetRegistry()
+ published = getPublishiedView(request)
+
+ cacheRule = registry.lookup(published)
+ operation = getInterceptorFor(cacheRule)
+
+ intercept = queryMultiAdapter((published, request,), ICacheInterceptor, name=operation)
+ if intercept(response):
+ return # abort rendering and return what we have
+
+ # continue as normal
+ """
+
+ def __call__(response):
+ """Mutate the response if required. Return True if the response should
+ be intercepted. In this case, normal rendering may be aborted and the
+ response returned as-is.
+ """
+
+class ILastModified(Interface):
+ """An abstraction to help obtain a last-modified date for a published
+ resource.
+
+ Should be registered as an unnamed multi-adapter from a published object
+ (e.g. a view) and the request.
+ """
+
+ def __call__():
+ """Return the last-modified date, as a Python datetime object.
+ """
Added: z3c.caching/trunk/src/z3c/caching/lastmodified.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/lastmodified.py (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/lastmodified.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -0,0 +1,13 @@
+from zope.interface import implementer, Interface
+from zope.component import adapter, queryMultiAdapter
+
+from zope.browser.interfaces import IView
+from z3c.caching.interfaces import ILastModified
+
+ at implementer(ILastModified)
+ at adapter(IView, Interface)
+def viewDelegateLastModified(view, request):
+ """When looking up an ILastModified for a view, look up an ILastModified
+ for its context. May return None, in which case adaptation will fail.
+ """
+ return queryMultiAdapter((view.context, request), ILastModified)
Modified: z3c.caching/trunk/src/z3c/caching/registry.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/registry.py 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/src/z3c/caching/registry.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -75,11 +75,9 @@
def enumerate(self):
seen = set()
for reg in self.registry.registeredAdapters():
- if reg.provided != ICacheRule:
- continue
- if reg.factory.id not in seen:
+ if reg.provided == ICacheRule and reg.factory.id not in seen:
yield reg.factory.id
- seen.add(reg.factory.id)
+ seen.add(reg.factory.id)
def directLookup(self, obj):
"""Find a rule _directly_ assigned to `obj`"""
@@ -92,8 +90,5 @@
__getitem__ = lookup
-# Set up RulesetRegistry as an adapter for component roots
-getGlobalSiteManager().registerAdapter(RulesetRegistry)
-
def getGlobalRulesetRegistry():
return IRulesetRegistry(getGlobalSiteManager())
Added: z3c.caching/trunk/src/z3c/caching/tests/test_lastmodified.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test_lastmodified.py (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/tests/test_lastmodified.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -0,0 +1,76 @@
+from unittest import TestCase
+import zope.component.testing
+
+from zope.interface import implements
+from zope.component import adapts, provideAdapter, queryMultiAdapter
+from zope.browser.interfaces import IView
+
+from z3c.caching.interfaces import ILastModified
+from z3c.caching.lastmodified import viewDelegateLastModified
+
+class TestLastModified(TestCase):
+
+ def setUp(self):
+ provideAdapter(viewDelegateLastModified)
+
+ def tearDown(self):
+ zope.component.testing.tearDown()
+
+ def test_no_adapter(self):
+
+ class DummyView(object):
+ implements(IView)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ class DummyContext(object):
+ pass
+
+ class DummyRequest(dict):
+ pass
+
+ context = DummyContext()
+ request = DummyRequest()
+
+ lastModified = queryMultiAdapter((context, request,), ILastModified)
+ self.assertEquals(None, lastModified)
+
+ def test_with_adapter(self):
+
+ class DummyView(object):
+ implements(IView)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ class DummyContext(object):
+ pass
+
+ class DummyRequest(dict):
+ pass
+
+ class DummyLastModified(object):
+ implements(ILastModified)
+ adapts(DummyContext, DummyRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ provideAdapter(DummyLastModified)
+
+ context = DummyContext()
+ request = DummyRequest()
+
+ lastModified = queryMultiAdapter((context, request,), ILastModified)
+ self.failUnless(isinstance(lastModified, DummyLastModified,))
+
+ self.assertEquals(context, lastModified.context)
+ self.assertEquals(request, lastModified.request)
+
+def test_suite():
+ import unittest
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
Modified: z3c.caching/trunk/src/z3c/caching/tests/test_registry.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test_registry.py 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/src/z3c/caching/tests/test_registry.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -3,8 +3,13 @@
from zope.interface import Interface
from zope.interface import implements
+from zope.component import provideAdapter
+
+from z3c.caching.registry import RulesetRegistry
from z3c.caching.registry import getGlobalRulesetRegistry
+import zope.component.testing
+
class ITestView(Interface):
pass
@@ -21,10 +26,12 @@
class TestRulesetRegistry(TestCase):
def setUp(self):
+ provideAdapter(RulesetRegistry)
self.registry = getGlobalRulesetRegistry()
def tearDown(self):
self.registry.clear()
+ zope.component.testing.tearDown()
def test_no_ruleset_returned_if_unregistered(self):
self.failUnless(self.registry[None] is None)
Modified: z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py 2009-12-31 05:23:40 UTC (rev 107425)
+++ z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py 2009-12-31 06:39:37 UTC (rev 107426)
@@ -1,19 +1,25 @@
from unittest import TestCase
+from zope.component import provideAdapter
from zope.configuration import xmlconfig
from z3c.caching.registry import getGlobalRulesetRegistry
-import z3c.caching.tests
+from z3c.caching.registry import RulesetRegistry
+
from z3c.caching.tests.test_registry import TestView
+import zope.component.testing
+import z3c.caching.tests
+
class TestZCMLDeclarations(TestCase):
def setUp(self):
+ provideAdapter(RulesetRegistry)
self.registry = getGlobalRulesetRegistry()
def tearDown(self):
- xmlconfig._clearContext()
self.registry.clear()
+ zope.component.testing.tearDown()
def test_simple_registration(self):
i = TestView()
More information about the checkins
mailing list