[Checkins] SVN: z3c.caching/trunk/ Add the ability to explicitly declare types. Mainly for UI support, but if you set explicit=True, it can also be used to throw an error if undeclared types are used.

Martin Aspeli optilude at gmx.net
Sat Jan 2 04:59:02 EST 2010


Log message for revision 107534:
  Add the ability to explicitly declare types. Mainly for UI support, but if you set explicit=True, it can also be used to throw an error if undeclared types are used.

Changed:
  U   z3c.caching/trunk/README.txt
  U   z3c.caching/trunk/docs/HISTORY.txt
  U   z3c.caching/trunk/src/z3c/caching/interfaces.py
  U   z3c.caching/trunk/src/z3c/caching/meta.zcml
  U   z3c.caching/trunk/src/z3c/caching/registry.py
  A   z3c.caching/trunk/src/z3c/caching/tests/test3.zcml
  A   z3c.caching/trunk/src/z3c/caching/tests/test4.zcml
  A   z3c.caching/trunk/src/z3c/caching/tests/test5.zcml
  U   z3c.caching/trunk/src/z3c/caching/tests/test_registry.py
  U   z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py
  U   z3c.caching/trunk/src/z3c/caching/zcml.py

-=-
Modified: z3c.caching/trunk/README.txt
===================================================================
--- z3c.caching/trunk/README.txt	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/README.txt	2010-01-02 09:59:02 UTC (rev 107534)
@@ -36,7 +36,13 @@
       xmlns="http://namespaces.zope.org/zope"
       xmlns:browser="http://namespaces.zope.org/browser"
       xmlns:cache="http://namespaces.zope.org/cache"/>
-
+    
+    <cache:rulesetType
+        name="plone-content-types"
+        title="Plone content types"
+        description="Non-folderish content types"
+        />
+    
     <cache:ruleset
         for=".frontpage.FrontpageView"
         ruleset="plone-content-types"
@@ -58,6 +64,10 @@
 with an adapter registration, a more specific registration can be used to
 override a more generic one.
 
+Above, we also add some metadata about the type of ruleset using the
+``<cache:rulesetType />`` directive. This is principally useful for UI support
+and can be often be skipped.
+
 If you prefer to use python directly you can do so::
 
    from z3c.caching.registry import register
@@ -65,11 +75,39 @@
 
    register(FrontpageView, "plone-content-types")
 
-To find the ruleset for an object use the ``lookup`` method::
+To find the ruleset for an object use the ``lookup()`` method::
 
    from z3c.caching.registry import lookup
    cacheRule = lookup(FrontpageView)
 
+To declare the ruleset type metadata, use the ``declareType`` method::
+
+   from z3c.caching.registry import declareType
+   declareType = declareType(name="plone-content-types", \
+                             title=u"Plone content types", \
+                             description=u"Non-folderish content types")
+
+If you want to get a list of all declared types, use the ``enumerateTypes()``
+method::
+
+    from z3c.caching.registry import enumerate
+    for type_ in enumerateTypes():
+        ...
+
+The ``type_`` object provides ``IRulesetType`` and has attributes for
+``name``, ``title`` and ``description``.
+
+Strict mode
+-----------
+
+By default, you are not required to declare the type of a ruleset before using
+it. This is convenient, but increases the risk of typos or a proliferation of
+rulesets that are semantically equivalent. If you want to guard against this
+case, you can put the ruleset into explicit mode, like this::
+
+    from z3c.caching.registry import setExplicitMode
+    setExplicitMode(True)
+
 Last modified date/time
 -----------------------
 

Modified: z3c.caching/trunk/docs/HISTORY.txt
===================================================================
--- z3c.caching/trunk/docs/HISTORY.txt	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/docs/HISTORY.txt	2010-01-02 09:59:02 UTC (rev 107534)
@@ -4,11 +4,15 @@
 2.0a1 - Unreleased
 ------------------
 
+* Added concept of an explicitly declare ruleset type. Optional by default,
+  but can be made required by setting `explicit` to `True`.
+  [optilude]
+
 * Added ``ILastModified`` implementation for a view which delegates to the
   view's context.
   [optilude]
 
-* Added ``enumerate()`` method to the registry, used to list all currently
+* Added ``enumerateTypes()`` method to the registry, used to list all currently
   used cache rule ids.
   [optilude]
 

Modified: z3c.caching/trunk/src/z3c/caching/interfaces.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/interfaces.py	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/interfaces.py	2010-01-02 09:59:02 UTC (rev 107534)
@@ -1,10 +1,11 @@
 from zope.interface import Interface
+from zope import schema
 
 class IRulesetRegistry(Interface):
     
     def register(obj, rule):
         """Mark objects that are implementers of `obj` to use the caching 
-        rule `rule`.  The value for `rule` MUST be a valid python identifier.
+        rule `rule`.
         """
     
     def unregister(obj):
@@ -22,10 +23,36 @@
         been registered `None` is returned.
         """
     
-    def enumerate():
-        """Return a sequence of all unique registered rule set ids (strings)
+    def __getitem__(obj):
+        """Convenience spelling for `lookup(obj)`.
         """
+    
+    def declareType(name, type, description):
+        """Declare a new ruleset type. This will put a new `IRulesetType`
+        into the list of objects returned by `enumerate`.
+        """
+    
+    def enumerateTypes():
+        """Return a sequence of all unique registered rule set types, as
+        ``IRuleSetType`` objects.
+        """
+    
+    explicit = schema.Bool(
+            title=u"Explicit mode",
+            description=u"If true, ruleset types must be declared before being used.",
+            required=True,
+            default=False
+        )
 
+class IRulesetType(Interface):
+    """A ruleset type. The name can be used in a <cache:ruleset /> directive.
+    The title and description are used for UI support.
+    """
+    
+    name        = schema.ASCIILine(title=u"Ruleset name")
+    title       = schema.TextLine(title=u"Title")
+    description = schema.TextLine(title=u"Description", required=False)
+
 class ILastModified(Interface):
     """An abstraction to help obtain a last-modified date for a published
     resource.

Modified: z3c.caching/trunk/src/z3c/caching/meta.zcml
===================================================================
--- z3c.caching/trunk/src/z3c/caching/meta.zcml	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/meta.zcml	2010-01-02 09:59:02 UTC (rev 107534)
@@ -5,6 +5,12 @@
   <meta:directives namespace="http://namespaces.zope.org/cache">
     
     <meta:directive
+        name="rulesetType"
+        schema=".interfaces.IRulesetType"
+        handler=".zcml.rulesetType"
+        />
+    
+    <meta:directive
         name="ruleset"
         schema=".zcml.IRuleset"
         handler=".zcml.ruleset"

Modified: z3c.caching/trunk/src/z3c/caching/registry.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/registry.py	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/registry.py	2010-01-02 09:59:02 UTC (rev 107534)
@@ -12,10 +12,11 @@
 
 from zope.interface import implements, Interface, Attribute
 
-from zope.component import adapts, getGlobalSiteManager
+from zope.component import adapts, queryUtility, getUtilitiesFor, getGlobalSiteManager
 from zope.component.interfaces import IComponents
 
 from z3c.caching.interfaces import IRulesetRegistry
+from z3c.caching.interfaces import IRulesetType
 
 class ICacheRule(Interface):
     """Represents the cache rule applied to an object.    
@@ -31,6 +32,15 @@
     def __init__(self, identifier):
         self.id = identifier
 
+class RulesetType(object):
+    __slots__ = ('name', 'title', 'description',)
+    implements(IRulesetType)
+    
+    def __init__(self, name, title, description):
+        self.name = name
+        self.title = title
+        self.description = description
+
 def get_context_to_cacherule_adapter_factory(rule):
     """Given a cache rule return an adapter factory which expects an object 
     but only returns the pre-specified cache rule."""
@@ -46,10 +56,13 @@
 
     def __init__(self, registry):
         self.registry = registry
-
+        
     def register(self, obj, rule):
         rule = str(rule) # We only want ascii, tyvm
         
+        if self.explicit and queryUtility(IRulesetType, rule) is None:
+            raise LookupError("Explicit mode set and ruleset %s not found" % rule)
+        
         factory = get_context_to_cacherule_adapter_factory(rule)
         existing = self.directLookup(obj)
         if existing is None:
@@ -64,7 +77,6 @@
         self.registry.unregisterAdapter(provided=ICacheRule, required=(obj,))
         return None
 
-
     def clear(self):
         # We force the iterator to be evaluated to start with as the backing
         # storage will be changing size
@@ -75,6 +87,16 @@
                 self.registry.unregisterAdapter(factory=rule.factory,
                                                 provided=rule.provided, 
                                                 required=rule.required)
+        
+        for type_ in list(self.registry.registeredUtilities()):
+            if type_.provided != IRulesetType:
+                continue # Not our responsibility
+            else:
+                self.registry.unregisterUtility(component=type_.component,
+                                                provided=IRulesetType, 
+                                                name=type_.name)
+        
+        self.explicit = False
         return None
 
     def lookup(self, obj):
@@ -83,13 +105,24 @@
             return rule.id
         return None
     
-    def enumerate(self):
-        seen = set()
-        for reg in self.registry.registeredAdapters():
-            if reg.provided == ICacheRule and reg.factory.id not in seen:
-                yield reg.factory.id
-                seen.add(reg.factory.id)
+    __getitem__ = lookup
     
+    def declareType(self, name, title, description):
+        type_ = RulesetType(name, title, description)
+        self.registry.registerUtility(type_, IRulesetType, name=name)
+    
+    def enumerateTypes(self):
+        for name, type_ in getUtilitiesFor(IRulesetType):
+            yield type_
+    
+    def _get_explicit(self):
+        return getattr(self.registry, '_z3c_caching_explicit', False)
+    def _set_explicit(self, value):
+        setattr(self.registry, '_z3c_caching_explicit', value)
+    explicit = property(_get_explicit, _set_explicit)
+    
+    # Helper methods
+    
     def directLookup(self, obj):
         """Find a rule _directly_ assigned to `obj`"""
         for rule in self.registry.registeredAdapters():
@@ -99,8 +132,6 @@
                 return rule.factory(None).id
         return None
 
-    __getitem__ = lookup
-
 def getGlobalRulesetRegistry():
     return IRulesetRegistry(getGlobalSiteManager(), None)
 
@@ -109,13 +140,13 @@
 def register(obj, rule):
     registry = getGlobalRulesetRegistry()
     if registry is None:
-        raise LookupError("Global not found initialised")
+        raise LookupError("Global registry initialised")
     return registry.register(obj, rule)
 
 def unregister(obj):
     registry = getGlobalRulesetRegistry()
     if registry is None:
-        raise LookupError("Global not found initialised")
+        raise LookupError("Global registry initialised")
     return registry.unregister(obj)
 
 def lookup(obj):
@@ -124,4 +155,23 @@
         return None
     return registry.lookup(obj)
 
-__all__ = ['getGlobalRulesetRegistry', 'register', 'unregister', 'lookup']
\ No newline at end of file
+def enumerateTypes():
+    registry = getGlobalRulesetRegistry()
+    if registry is None:
+        raise LookupError("Global registry initialised")
+    return registry.enumerateTypes()
+
+def declareType(name, title, description):
+    registry = getGlobalRulesetRegistry()
+    if registry is None:
+        raise LookupError("Global registry initialised")
+    registry.declareType(name, title, description)
+
+def setExplicitMode(mode=True):
+    registry = getGlobalRulesetRegistry()
+    if registry is None:
+        raise LookupError("Global registry initialised")
+    registry.explicit = mode
+
+__all__ = ['getGlobalRulesetRegistry', 'register', 'unregister', 'lookup',
+           'enumerate', 'declareType', 'setExplicitMode']

Added: z3c.caching/trunk/src/z3c/caching/tests/test3.zcml
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test3.zcml	                        (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/tests/test3.zcml	2010-01-02 09:59:02 UTC (rev 107534)
@@ -0,0 +1,12 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:cache="http://namespaces.zope.org/cache">
+
+    <include package="z3c.caching" file="meta.zcml" />
+
+    <cache:rulesetType
+        name="rule1"
+        title="Rule 1"
+        description="Rule one"
+        />
+
+</configure>

Added: z3c.caching/trunk/src/z3c/caching/tests/test4.zcml
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test4.zcml	                        (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/tests/test4.zcml	2010-01-02 09:59:02 UTC (rev 107534)
@@ -0,0 +1,18 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:cache="http://namespaces.zope.org/cache">
+
+    <include package="z3c.caching" file="meta.zcml" />
+
+    <cache:rulesetType
+        name="rule1"
+        title="Rule 1"
+        description="Rule one"
+        />
+
+    <cache:rulesetType
+        name="rule1"
+        title="Rule 1"
+        description="Rule two"
+        />
+
+</configure>

Added: z3c.caching/trunk/src/z3c/caching/tests/test5.zcml
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test5.zcml	                        (rev 0)
+++ z3c.caching/trunk/src/z3c/caching/tests/test5.zcml	2010-01-02 09:59:02 UTC (rev 107534)
@@ -0,0 +1,17 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:cache="http://namespaces.zope.org/cache">
+
+    <include package="z3c.caching" file="meta.zcml" />
+
+    <cache:ruleset
+        for="z3c.caching.tests.test_registry.ITestView"
+        ruleset="rule1"
+        />
+
+    <cache:rulesetType
+        name="rule1"
+        title="Rule 1"
+        description="Rule one"
+        />
+
+</configure>

Modified: z3c.caching/trunk/src/z3c/caching/tests/test_registry.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test_registry.py	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/tests/test_registry.py	2010-01-02 09:59:02 UTC (rev 107534)
@@ -99,18 +99,79 @@
 
     def test_clearing_registry_removes_rulesets(self):
         self.registry.register(ITestView, "frop")
+        
+        self.registry.clear()
+        
         i = TestView()
+        self.failUnless(self.registry[i] is None)
+    
+    def test_clearing_registry_removes_types(self):
+        self.registry.declareType("rule1", u"Rule 1", u"First rule")
+        self.registry.declareType("rule2", u"Rule 2", u"Second rule")
+        
+        self.registry.register(ITestView, "frop")
+        
         self.registry.clear()
+        
+        i = TestView()
         self.failUnless(self.registry[i] is None)
+        self.assertEquals(0, len(list(self.registry.enumerateTypes())))
     
-    def test_enumerate(self):
-        self.registry.register(ITestView, "rule1")
-        self.registry.register(IMoreSpecificTestView, "rule2")
-        self.registry.register(OtherTestView, "rule2")
-        self.assertEqual(set(['rule1', 'rule2']), set(self.registry.enumerate()))
+    def test_declareType_overrides(self):
+        self.registry.declareType("rule1", u"Rule 1", u"First rule")
+        self.registry.declareType("rule2", u"Rule 2", u"Second rule")
+        self.registry.declareType("rule1", u"Rule One", u"Rule uno")
+        
+        rules = list(self.registry.enumerateTypes())
+        rules.sort(lambda x,y: cmp(x.name, y.name))
+        
+        self.assertEquals(2, len(rules))
+        self.assertEquals("rule1", rules[0].name)
+        self.assertEquals(u"Rule One", rules[0].title)
+        self.assertEquals(u"Rule uno", rules[0].description)
+        self.assertEquals("rule2", rules[1].name)
+        self.assertEquals(u"Rule 2", rules[1].title)
+        self.assertEquals(u"Second rule", rules[1].description)
     
+    def test_enumerateTypes(self):
+        self.registry.declareType("rule1", u"Rule 1", u"First rule")
+        self.registry.declareType("rule2", u"Rule 2", u"Second rule")
+        
+        rules = list(self.registry.enumerateTypes())
+        rules.sort(lambda x,y: cmp(x.title, y.title))
+        
+        self.assertEquals(2, len(rules))
+        self.assertEquals("rule1", rules[0].name)
+        self.assertEquals(u"Rule 1", rules[0].title)
+        self.assertEquals(u"First rule", rules[0].description)
+        self.assertEquals("rule2", rules[1].name)
+        self.assertEquals(u"Rule 2", rules[1].title)
+        self.assertEquals(u"Second rule", rules[1].description)
+    
     def test_enumerate_empty(self):
-        self.assertEqual(set([]), set(self.registry.enumerate()))
+        self.assertEqual(set([]), set(self.registry.enumerateTypes()))
+    
+    def test_set_explicit_mode(self):
+        self.registry.explicit = True
+        
+        self.assertRaises(LookupError, self.registry.register, TestView, "rule1")
+        self.assertEquals(None, self.registry.lookup(TestView()))
+        
+        self.registry.declareType("rule1", u"Rule 1", u"First rule")
+        self.registry.register(TestView, "rule1")
+        
+        self.assertEquals("rule1", self.registry.lookup(TestView()))
+        
+    def test_disable_explicit_mode(self):
+        self.registry.explicit = True
+        
+        self.assertRaises(LookupError, self.registry.register, TestView, "rule1")
+        self.assertEquals(None, self.registry.lookup(TestView()))
+        
+        self.registry.explicit = False
+        
+        self.registry.register(TestView, "rule1")
+        self.assertEquals("rule1", self.registry.lookup(TestView()))
 
 class TestConvenienceAPI(TestCase):
 
@@ -156,7 +217,30 @@
         self.failUnless(lookup(TestView) is None)
         unregister(TestView)
         self.failUnless(lookup(TestView) is None)
-
+    
+    def test_declareType_enumerateTypes(self):
+        from z3c.caching.registry import declareType, enumerateTypes
+        declareType("rule1", u"Rule 1", u"Rule one")
+        
+        rules = list(enumerateTypes())
+        rules.sort(lambda x,y: cmp(x.name, y.name))
+        
+        self.assertEquals(1, len(rules))
+        self.assertEquals("rule1", rules[0].name)
+        self.assertEquals(u"Rule 1", rules[0].title)
+        self.assertEquals(u"Rule one", rules[0].description)
+    
+    def test_set_explicit_mode(self):
+        from z3c.caching.registry import setExplicitMode
+        
+        self.assertEquals(False, self.registry.explicit)
+        setExplicitMode()
+        self.assertEquals(True, self.registry.explicit)
+        setExplicitMode(False)
+        self.assertEquals(False, self.registry.explicit)
+        setExplicitMode(True)
+        self.assertEquals(True, self.registry.explicit)
+    
 def test_suite():
     import unittest
     return unittest.defaultTestLoader.loadTestsFromName(__name__)

Modified: z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/tests/test_zcml.py	2010-01-02 09:59:02 UTC (rev 107534)
@@ -34,6 +34,42 @@
     def test_conflicting_registrations(self):
         zcml = xmlconfig.XMLConfig("test2.zcml", z3c.caching.tests)
         self.assertRaises(Exception, zcml) # ZCML conflict error
+    
+    def test_declareType(self):
+        zcml = xmlconfig.XMLConfig("test3.zcml", z3c.caching.tests)
+        zcml()
+        
+        rules = list(self.registry.enumerateTypes())
+        rules.sort(lambda x,y: cmp(x.name, y.name))
+        
+        self.assertEquals(1, len(rules))
+        self.assertEquals("rule1", rules[0].name)
+        self.assertEquals(u"Rule 1", rules[0].title)
+        self.assertEquals(u"Rule one", rules[0].description)
+    
+    def test_declareType_multiple(self):
+        zcml = xmlconfig.XMLConfig("test4.zcml", z3c.caching.tests)
+        self.assertRaises(Exception, zcml) # ZCML conflict error
+    
+    def test_declareType_explicit_after(self):
+        i = TestView()
+        self.failUnless(self.registry[i] is None)
+        
+        self.registry.explicit = True
+        
+        zcml = xmlconfig.XMLConfig("test5.zcml", z3c.caching.tests)
+        zcml()
+        
+        rules = list(self.registry.enumerateTypes())
+        rules.sort(lambda x,y: cmp(x.name, y.name))
+        
+        self.assertEquals(1, len(rules))
+        self.assertEquals("rule1", rules[0].name)
+        self.assertEquals(u"Rule 1", rules[0].title)
+        self.assertEquals(u"Rule one", rules[0].description)
+        
+        i = TestView()
+        self.assertEqual(self.registry[i], "rule1")
 
 def test_suite():
     import unittest

Modified: z3c.caching/trunk/src/z3c/caching/zcml.py
===================================================================
--- z3c.caching/trunk/src/z3c/caching/zcml.py	2010-01-02 09:37:54 UTC (rev 107533)
+++ z3c.caching/trunk/src/z3c/caching/zcml.py	2010-01-02 09:59:02 UTC (rev 107534)
@@ -16,12 +16,19 @@
             default=None,
             required=True)
 
+def rulesetType(_context, name, title, description=u""):
+    declareType = getGlobalRulesetRegistry().declareType
+    _context.action(
+            discriminator=("declareCacheRuleSetType", name),
+            callable=declareType,
+            args=(name, title, description,),
+            order=-10)
 
+
 def ruleset(_context, for_, ruleset):
     register = getGlobalRulesetRegistry().register
     _context.action(
             discriminator=("registerCacheRule", for_),
-            callable = register,
-            args = (for_, ruleset))
-
-
+            callable=register,
+            args=(for_, ruleset,),
+            order=10)



More information about the checkins mailing list