[Checkins] SVN: martian/branches/philikon-decl-dir-rules/ * Moved the extended ClassGrokker implementation that's aware of directive declarations

Philipp von Weitershausen philikon at philikon.de
Fri May 9 20:02:27 EDT 2008


Log message for revision 86592:
  * Moved the extended ClassGrokker implementation that's aware of directive declarations
  from the grokcore.component branch to martian.  You can still implement your own grok()
  method (in which case you'll simply override the method provided here).  If you want 
  to make use of declarative directives, just implement the execute() method
  (this was renamed from register() because register() has a different connotation in
  other grokkers).
  
  * Introduce explicit defaults to the scope's get() methods. That way the
  checks for default values are much more explicit
  
  * Cleanup
  

Changed:
  U   martian/branches/philikon-decl-dir-rules/CHANGES.txt
  U   martian/branches/philikon-decl-dir-rules/src/martian/README.txt
  U   martian/branches/philikon-decl-dir-rules/src/martian/components.py
  U   martian/branches/philikon-decl-dir-rules/src/martian/directive.py
  U   martian/branches/philikon-decl-dir-rules/src/martian/directive.txt

-=-
Modified: martian/branches/philikon-decl-dir-rules/CHANGES.txt
===================================================================
--- martian/branches/philikon-decl-dir-rules/CHANGES.txt	2008-05-09 23:03:41 UTC (rev 86591)
+++ martian/branches/philikon-decl-dir-rules/CHANGES.txt	2008-05-10 00:02:26 UTC (rev 86592)
@@ -4,8 +4,22 @@
 0.9.6 (unreleased)
 ==================
 
-* ...
+Feature changes
+---------------
 
+* Refactored the ``martian.Directive`` base class yet again to allow
+  more declarative (rather than imperative) usage in grokkers.
+  Directives themselves no longer have a ``get()`` method nor a
+  default value factory (``get_default()``).  Instead you will have to
+  "bind" the directive first which is typically done in a grokker.
+
+* Extended the ``ClassGrokker`` baseclass with a standard ``grok()``
+  method that allows you to simply declare a set of directives that
+  are used on the grokked classes.  Then you just have to implement an
+  ``execute()`` method that will receive the data from those
+  directives as keyword arguments.  This simplifies the implementation
+  of class grokkers a lot.
+
 0.9.5 (2008-05-04)
 ==================
 

Modified: martian/branches/philikon-decl-dir-rules/src/martian/README.txt
===================================================================
--- martian/branches/philikon-decl-dir-rules/src/martian/README.txt	2008-05-09 23:03:41 UTC (rev 86591)
+++ martian/branches/philikon-decl-dir-rules/src/martian/README.txt	2008-05-10 00:02:26 UTC (rev 86592)
@@ -424,34 +424,49 @@
 ClassGrokker
 ------------
 
-Besides instances we can also grok classes. Let's define an application
-where we register classes representing animals::
+Besides instances we can also grok classes. Let's define an
+application where we register classes representing animals.  Animals
+can be given names using the ``name`` directive::
 
+  >>> from martian.directive import Directive, CLASS, ONCE
+
   >>> class animal(FakeModule):
+  ...   class name(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...
   ...   class Animal(object):
-  ...     name = None
   ...     def __repr__(self):
-  ...       return '<Animal %s>' % self.name
+  ...       return '<Animal %s>' % animal.name.bind().get(self)
+  ...
   ...   all_animals = {}
   ...   def create_animal(name):
   ...     return all_animals[name]()
   >>> animal = fake_import(animal)
 
-Let's define a grokker that can grok an ``Animal``::
+Let's define a grokker that can grok an ``Animal``.  We could either
+implement the ``grok`` method as with ``InstanceGrokkers``, or we can
+rely on the implementation that the baseclass already provides.  In
+the latter case, we just have to declare what directives the grokker
+may want to use on the class and the implement the ``execute`` method::
 
   >>> from martian import ClassGrokker
   >>> class AnimalGrokker(ClassGrokker):
   ...   component_class = animal.Animal
-  ...   def grok(self, name, obj, **kw):
-  ...     animal.all_animals[obj.name] = obj
+  ...   directives = [
+  ...       animal.name.bind()
+  ...       ]
+  ...   def execute(self, class_, name, **kw):
+  ...     animal.all_animals[name] = class_
   ...     return True
 
 Let's test our grokker::
 
+  >>> class Snake(animal.Animal):
+  ...   animal.name('snake')
+  ...
   >>> animal_grokker = AnimalGrokker()
-  >>> class Snake(animal.Animal):
-  ...   name = 'snake'
-  >>> animal_grokker.grok('snake', Snake)
+  >>> animal_grokker.grok('Snake', Snake)
   True
   >>> animal.all_animals.keys()
   ['snake']
@@ -461,6 +476,80 @@
   >>> animal.create_animal('snake')
   <Animal snake>
 
+Note that we can supply a different default value for the directive
+default when binding the directive to the grokker:
+
+  >>> class AnimalGrokker(AnimalGrokker):
+  ...   directives = [
+  ...       animal.name.bind(default='generic animal')
+  ...       ]
+  ...
+  >>> class Generic(animal.Animal):
+  ...   pass
+  ...
+  >>> animal_grokker = AnimalGrokker()
+  >>> animal_grokker.grok('Generic', Generic)
+  True
+  >>> sorted(animal.all_animals.keys())
+  ['generic animal', 'snake']
+
+Moreover, we can also supply a default factory that may want to
+determine a dynamic default value based on the class that's being
+grokked.  For instance, let's say the default name of an animal should
+the class name converted to lowercase letters::
+
+  >>> def default_animal_name(class_, module, **data):
+  ...   return class_.__name__.lower()
+  ...
+  >>> class AnimalGrokker(AnimalGrokker):
+  ...   directives = [
+  ...       animal.name.bind(get_default=default_animal_name)
+  ...       ]
+  ...
+  >>> class Mouse(animal.Animal):
+  ...   pass
+  ...
+  >>> animal_grokker = AnimalGrokker()
+  >>> animal_grokker.grok('Mouse', Mouse)
+  True
+  >>> sorted(animal.all_animals.keys())
+  ['generic animal', 'mouse', 'snake']
+
+Note that these default value factories will also get the data from
+all directives that are in front of them in the grokker's directive
+list.  For instance, consider the following directive:
+
+  >>> class zoologicalname(animal.name):
+  ...   pass
+  ...
+
+with the following default rule that takes the regular name as the
+default zoological name::
+
+  >>> def default_zoological_name(class_, module, name, **data):
+  ...   return name
+  ...
+  >>> class ZooAnimalGrokker(ClassGrokker):
+  ...   component_class = animal.Animal
+  ...   directives = [
+  ...       animal.name.bind(get_default=default_animal_name),
+  ...       zoologicalname.bind(get_default=default_zoological_name)
+  ...       ]
+  ...   def execute(self, class_, name, zoologicalname, **kw):
+  ...     print zoologicalname
+  ...     return True
+  ...
+  >>> class Hippopotamus(animal.Animal):
+  ...   pass
+  ...   # No need to use animal.name(), we'll take the class name as default.
+  ...   # The zoological name is the same as well.
+  ...
+
+  >>> zoo_animal_grokker = ZooAnimalGrokker()
+  >>> zoo_animal_grokker.grok('Hippopotamus', Hippopotamus)
+  hippopotamus
+  True
+
 MultiClassGrokker
 -----------------
 
@@ -470,13 +559,13 @@
 
   >>> class animals(FakeModule):
   ...   class Elephant(animal.Animal):
-  ...     name = 'elephant'
+  ...     animal.name('elephant')
   ...   class Tiger(animal.Animal):
-  ...     name = 'tiger'
+  ...     animal.name('tiger')
   ...   class Lion(animal.Animal):
-  ...     name = 'lion'
+  ...     animal.name('lion')
   ...   class Chair(object):
-  ...     name = 'chair'
+  ...     animal.name('chair')
   >>> animals = fake_import(animals)
 
 First we need to wrap our ``AnimalGrokker`` into a ``MultiClassGrokker``::
@@ -494,7 +583,7 @@
 The animals (but not anything else) should have become available::
 
   >>> sorted(animal.all_animals.keys())
-  ['elephant', 'lion', 'snake', 'tiger']
+  ['elephant', 'generic animal', 'lion', 'mouse', 'snake', 'tiger']
 
 We can create animals using their name now::
 
@@ -524,7 +613,7 @@
 Let's try it with some individual objects::
 
   >>> class Whale(animal.Animal):
-  ...    name = 'whale'
+  ...    animal.name('whale')
   >>> multi.grok('Whale', Whale)
   True
   >>> 'whale' in animal.all_animals
@@ -599,7 +688,7 @@
   >>> class mix(FakeModule):
   ...   # grokked by AnimalGrokker
   ...   class Whale(animal.Animal):
-  ...      name = 'whale'
+  ...      animal.name('whale')
   ...   # not grokked
   ...   my_whale = Whale()
   ...   # grokked by ColorGrokker
@@ -613,7 +702,7 @@
   ...   rocknroll = RockMusic('rock n roll')
   ...   # grokked by AnimalGrokker
   ...   class Dragon(animal.Animal):
-  ...     name = 'dragon'
+  ...     animal.name('dragon')
   ...   # not grokked
   ...   class Chair(object):
   ...     pass
@@ -625,7 +714,7 @@
   ...     pass
   ...   # grokked by AnimalGrokker
   ...   class SpermWhale(Whale):
-  ...     name = 'sperm whale'
+  ...     animal.name('sperm whale')
   ...   # not grokked
   ...   another = object()
   >>> mix = fake_import(mix)

Modified: martian/branches/philikon-decl-dir-rules/src/martian/components.py
===================================================================
--- martian/branches/philikon-decl-dir-rules/src/martian/components.py	2008-05-09 23:03:41 UTC (rev 86591)
+++ martian/branches/philikon-decl-dir-rules/src/martian/components.py	2008-05-10 00:02:26 UTC (rev 86592)
@@ -48,8 +48,22 @@
 class ClassGrokker(ComponentGrokkerBase):
     """Grokker that groks classes in a module.
     """
-    pass
+    # Use a tuple instead of a list here to make it immutable, just to be safe
+    directives = ()
 
+    def grok(self, name, class_, module_info=None, **kw):
+        module = None
+        if module_info is not None:
+            module = module_info.getModule()
+
+        # Populate the data dict with information from the directives:
+        for directive in self.directives:
+            kw[directive.name] = directive.get(class_, module, **kw)
+        return self.execute(class_, **kw)
+
+    def execute(self, class_, **data):
+        raise NotImplementedError
+
 class InstanceGrokker(ComponentGrokkerBase):
     """Grokker that groks instances in a module.
     """

Modified: martian/branches/philikon-decl-dir-rules/src/martian/directive.py
===================================================================
--- martian/branches/philikon-decl-dir-rules/src/martian/directive.py	2008-05-09 23:03:41 UTC (rev 86591)
+++ martian/branches/philikon-decl-dir-rules/src/martian/directive.py	2008-05-10 00:02:26 UTC (rev 86592)
@@ -82,7 +82,6 @@
 
 DICT = StoreDict()
 
-_SENTINEL = object()
 _USE_DEFAULT = object()
 
 class ClassScope(object):
@@ -91,16 +90,8 @@
     def check(self, frame):
         return util.frame_is_class(frame) and not is_fake_module(frame)
 
-    def get(self, directive, component, module):
-        assert component is not None, (
-            "The directive '%s' has scope CLASS "
-            "but no class was passed into 'get'" %
-                               directive.__name__)
-        assert module is None, (
-            "The directive '%s' has scope CLASS "
-            "but a module was also passed into 'get'" %
-            directive.__name__)
-        return directive.store.get(directive, component, default=_USE_DEFAULT)
+    def get(self, directive, component, module, default):
+        return directive.store.get(directive, component, default)
     
 CLASS = ClassScope()
 
@@ -110,19 +101,10 @@
     def check(self, frame):
         return util.frame_is_class(frame) or util.frame_is_module(frame)
 
-    def get(self, directive, component, module):
-        assert component is not None, (
-            "The directive '%s' has scope CLASS_OR_MODULE "
-            "but no class was passed into 'get'" %
-                               directive.__name__)
-        assert module is not None, (
-            "The directive '%s' has scope CLASS_OR_MODULE "
-            "but no module was passed into 'get'" %
-            directive.__name__)
-
-        value = directive.store.get(directive, component, default=_USE_DEFAULT)
-        if value is _USE_DEFAULT:
-            value = directive.store.get(directive, module, default=_USE_DEFAULT)
+    def get(self, directive, component, module, default):
+        value = directive.store.get(directive, component, default)
+        if value is default:
+            value = directive.store.get(directive, module, default)
         return value
     
 CLASS_OR_MODULE = ClassOrModuleScope()
@@ -133,17 +115,8 @@
     def check(self, frame):
         return util.frame_is_module(frame) or is_fake_module(frame)
 
-    def get(self, directive, component, module):
-        assert component is None, (
-            "The directive '%s' has scope MODULE "
-            "but a class was also passed into 'get'" %
-            directive.__name__)
-        assert module is not None, (
-            "The directive '%s' has scope MODULE "
-            "but no module was passed into 'get'" %
-            directive.__name__)
-
-        return directive.store.get(directive, module, default=_USE_DEFAULT)
+    def get(self, directive, component, module, default):
+        return directive.store.get(directive, module, default)
     
 MODULE = ModuleScope()
 
@@ -211,14 +184,15 @@
         if get_default is not None:
             self.get_default = get_default
 
-    def get_default(self, component, module, **data):
+    def get_default(self, component, module=None, **data):
         if self.default is not None:
             return self.default
         return self.directive.default
 
     def get(self, component=None, module=None, **data):
         directive = self.directive
-        value = directive.scope.get(directive, component, module)
+        value = directive.scope.get(directive, component, module,
+                                    default=_USE_DEFAULT)
         if value is _USE_DEFAULT:
             value = self.get_default(component, module, **data)
         return value

Modified: martian/branches/philikon-decl-dir-rules/src/martian/directive.txt
===================================================================
--- martian/branches/philikon-decl-dir-rules/src/martian/directive.txt	2008-05-09 23:03:41 UTC (rev 86591)
+++ martian/branches/philikon-decl-dir-rules/src/martian/directive.txt	2008-05-10 00:02:26 UTC (rev 86592)
@@ -179,19 +179,6 @@
   ...     scope = CLASS
   ...     store = ONCE
 
-We are not allowed to pass in a module argument to ``get`` now::
-
-  >>> class testmodule2(FakeModule):
-  ...    layer2('Test2')
-  ...    class Foo(object):
-  ...       pass
-  >>> testmodule2 = fake_import(testmodule2)
-
-  >>> layer2.bind().get(testmodule2.Foo, testmodule2)
-  Traceback (most recent call last):
-    ...
-  AssertionError: The directive 'layer2' has scope CLASS but a module was also passed into 'get'
-
 Using a directive multiple times
 --------------------------------
 



More information about the Checkins mailing list