[Checkins] SVN: grok/trunk/ Refactored Directive base class to prepare for directives that can be called multiple times and/or with multiple values

Wolfgang Schnerring wosc at wosc.de
Sat Jan 6 11:15:19 EST 2007


Log message for revision 71742:
  Refactored Directive base class to prepare for directives that can be called multiple times and/or with multiple values

Changed:
  U   grok/trunk/doc/design/utility.py
  U   grok/trunk/src/grok/directive.py
  A   grok/trunk/src/grok/tests/directive/
  A   grok/trunk/src/grok/tests/directive/__init__.py
  A   grok/trunk/src/grok/tests/directive/argumenterror.py
  A   grok/trunk/src/grok/tests/directive/argumenterror_fixture.py
  U   grok/trunk/src/grok/tests/test_grok.py

-=-
Modified: grok/trunk/doc/design/utility.py
===================================================================
--- grok/trunk/doc/design/utility.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/doc/design/utility.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -3,9 +3,9 @@
 class Calculator(grok.GlobalUtility):
     grok.implements(ICalculator)  # if this is not specified, it breaks
     grok.name('')  # this is actually the default
-    grok.utility_provides(ICalculator) # this is actually the default
+    grok.provides(ICalculator) # this is actually the default
 
-grok.global_utility(factory, provides=IFace, name=u'', setup=None)
+grok.global_utility(factory, provides=IFace, name=u'')
 
 class Calculator(grok.LocalUtility):
     grok.utility_provides(ICalculator)

Modified: grok/trunk/src/grok/directive.py
===================================================================
--- grok/trunk/src/grok/directive.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/src/grok/directive.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -15,6 +15,7 @@
 """
 
 import sys
+import inspect
 from zope import interface
 from zope.interface.interfaces import IInterface
 
@@ -66,66 +67,106 @@
     """
     Directive sets a value into the context's locals as __<name>__
     ('.' in the name are replaced with '_').
-    A directive can be called only once.
     """
 
-    def __init__(self, name, directive_context):
+    def __init__(self, name, directive_context, value_factory):
         self.name = name
+        self.local_name = '__%s__' % name.replace('.', '_')
         self.directive_context = directive_context
+        self.value_factory = value_factory
 
-    def __call__(self, value):
-        self.check(value)
+    def __call__(self, *args, **kw):
+        self.check_argument_signature(*args, **kw)
+        self.check_arguments(*args, **kw)
 
         frame = sys._getframe(1)
+        self.check_directive_context(frame)
+
+        value = self.value_factory(*args, **kw)
+        self.store(frame, value)
+
+    def check_arguments(self, *args, **kw):
+        raise NotImplementedError
+
+    # to get a correct error message, we construct a function that has the same
+    # signature as check_arguments(), but without "self".
+    def check_argument_signature(self, *arguments, **kw):
+        args, varargs, varkw, defaults = inspect.getargspec(self.check_arguments)
+        argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
+        exec("def signature_checker" + argspec + ": pass")
+        try:
+            signature_checker(*arguments, **kw)
+        except TypeError, e:
+            message = e.args[0]
+            message = message.replace("signature_checker()", self.name)
+            raise TypeError(message)
+
+    def check_directive_context(self, frame):
         if not self.directive_context.matches(frame):
             raise GrokImportError("%s can only be used on %s level."
                                   % (self.name,
                                      self.directive_context.description))
 
-        local_name = '__%s__' % self.name.replace('.', '_')
-        if local_name in frame.f_locals:
+    def store(self, frame, value):
+        raise NotImplementedError
+
+class OnceDirective(Directive):
+    def store(self, frame, value):
+        if self.local_name in frame.f_locals:
             raise GrokImportError("%s can only be called once per %s."
                                   % (self.name,
                                      self.directive_context.description))
-        frame.f_locals[local_name] = value
+        frame.f_locals[self.local_name] = value
 
-    def check(self, value):
-        pass
-
-class TextDirective(Directive):
+class TextDirective(OnceDirective):
     """
     Directive that only accepts unicode/ASCII values.
     """
 
-    def check(self, value):
+    def check_arguments(self, value):
         if util.not_unicode_or_ascii(value):
             raise GrokImportError("You can only pass unicode or ASCII to "
                                   "%s." % self.name)
 
-class InterfaceOrClassDirective(Directive):
+class InterfaceOrClassDirective(OnceDirective):
     """
     Directive that only accepts classes or interface values.
     """
 
-    def check(self, value):
+    def check_arguments(self, value):
         if not (IInterface.providedBy(value) or util.isclass(value)):
             raise GrokImportError("You can only pass classes or interfaces to "
                                   "%s." % self.name)
 
-class InterfaceDirective(Directive):
+class InterfaceDirective(OnceDirective):
     """
     Directive that only accepts interface values.
     """
 
-    def check(self, value):
+    def check_arguments(self, value):
         if not (IInterface.providedBy(value)):
             raise GrokImportError("You can only pass interfaces to "
                                   "%s." % self.name)
+        
+# Even though the value_factory is called with (*args, **kw), we're safe since
+# check_arguments would have bailed out with a TypeError if the number arguments
+# we were called with was not what we expect here.
+def single_value_factory(value):
+    return value
 
 # Define grok directives
-name = TextDirective('grok.name', ClassDirectiveContext())
-template = TextDirective('grok.template', ClassDirectiveContext())
+name = TextDirective('grok.name',
+                     ClassDirectiveContext(),
+                     single_value_factory)
+template = TextDirective('grok.template',
+                         ClassDirectiveContext(),
+                         single_value_factory)
 context = InterfaceOrClassDirective('grok.context',
-                                    ClassOrModuleDirectiveContext())
-templatedir = TextDirective('grok.templatedir', ModuleDirectiveContext())
-provides = InterfaceDirective('grok.provides', ClassDirectiveContext())
+                                    ClassOrModuleDirectiveContext(),
+                                    single_value_factory)
+templatedir = TextDirective('grok.templatedir',
+                            ModuleDirectiveContext(),
+                            single_value_factory)
+provides = InterfaceDirective('grok.provides',
+                              ClassDirectiveContext(),
+                              single_value_factory)

Added: grok/trunk/src/grok/tests/directive/__init__.py
===================================================================
--- grok/trunk/src/grok/tests/directive/__init__.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/src/grok/tests/directive/__init__.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -0,0 +1 @@
+# this is a package

Added: grok/trunk/src/grok/tests/directive/argumenterror.py
===================================================================
--- grok/trunk/src/grok/tests/directive/argumenterror.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/src/grok/tests/directive/argumenterror.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -0,0 +1,6 @@
+"""
+   >>> import grok.tests.directive.argumenterror_fixture
+   Traceback (most recent call last):
+     ...
+   TypeError: grok.templatedir takes exactly 1 argument (3 given)
+"""

Added: grok/trunk/src/grok/tests/directive/argumenterror_fixture.py
===================================================================
--- grok/trunk/src/grok/tests/directive/argumenterror_fixture.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/src/grok/tests/directive/argumenterror_fixture.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -0,0 +1,3 @@
+import grok
+
+grok.templatedir('too', 'many', 'arguments')

Modified: grok/trunk/src/grok/tests/test_grok.py
===================================================================
--- grok/trunk/src/grok/tests/test_grok.py	2007-01-06 16:07:59 UTC (rev 71741)
+++ grok/trunk/src/grok/tests/test_grok.py	2007-01-06 16:15:18 UTC (rev 71742)
@@ -34,7 +34,7 @@
     suite = unittest.TestSuite()
     for name in ['adapter', 'error', 'view', 'scan', 'event',
                  'zcml', 'static', 'utility', 'xmlrpc', 'container',
-                 'traversal', 'form', 'site', 'grokker']:
+                 'traversal', 'form', 'site', 'grokker', 'directive']:
         suite.addTest(suiteFromPackage(name))
     return suite
 



More information about the Checkins mailing list