[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