[Checkins] SVN: martian/trunk/ Merged the 'philikon-decl-dir-rules' branch:

Philipp von Weitershausen philikon at philikon.de
Wed May 14 11:57:20 EDT 2008


Log message for revision 86732:
  Merged the 'philikon-decl-dir-rules' branch:
  
  * 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.
  

Changed:
  U   martian/trunk/CHANGES.txt
  U   martian/trunk/src/martian/README.txt
  U   martian/trunk/src/martian/components.py
  U   martian/trunk/src/martian/directive.py
  U   martian/trunk/src/martian/directive.txt
  U   martian/trunk/src/martian/tests/test_all.py
  U   martian/trunk/src/martian/util.py

-=-
Modified: martian/trunk/CHANGES.txt
===================================================================
--- martian/trunk/CHANGES.txt	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/CHANGES.txt	2008-05-14 15:57:19 UTC (rev 86732)
@@ -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/trunk/src/martian/README.txt
===================================================================
--- martian/trunk/src/martian/README.txt	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/README.txt	2008-05-14 15:57:19 UTC (rev 86732)
@@ -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/trunk/src/martian/components.py
===================================================================
--- martian/trunk/src/martian/components.py	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/components.py	2008-05-14 15:57:19 UTC (rev 86732)
@@ -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/trunk/src/martian/directive.py
===================================================================
--- martian/trunk/src/martian/directive.py	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/directive.py	2008-05-14 15:57:19 UTC (rev 86732)
@@ -4,7 +4,7 @@
 from zope.interface.interfaces import IInterface
 
 from martian import util
-from martian.error import GrokImportError
+from martian.error import GrokImportError, GrokError
 
 class StoreOnce(object):
 
@@ -82,7 +82,6 @@
 
 DICT = StoreDict()
 
-_SENTINEL = object()
 _USE_DEFAULT = object()
 
 class ClassScope(object):
@@ -91,6 +90,9 @@
     def check(self, frame):
         return util.frame_is_class(frame) and not is_fake_module(frame)
 
+    def get(self, directive, component, module, default):
+        return directive.store.get(directive, component, default)
+    
 CLASS = ClassScope()
 
 class ClassOrModuleScope(object):
@@ -99,6 +101,12 @@
     def check(self, frame):
         return util.frame_is_class(frame) or util.frame_is_module(frame)
 
+    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()
 
 class ModuleScope(object):
@@ -107,6 +115,9 @@
     def check(self, frame):
         return util.frame_is_module(frame) or is_fake_module(frame)
 
+    def get(self, directive, component, module, default):
+        return directive.store.get(directive, module, default)
+    
 MODULE = ModuleScope()
 
 class Directive(object):
@@ -149,31 +160,43 @@
     def factory(self, value):
         return value
 
-    def get_default(self, component):
-        return self.default
-
     @classmethod
     def dotted_name(cls):
         return cls.__module__ + '.' + cls.__name__
 
     @classmethod
-    def get(cls, component, module=None):
-        # Create an instance of the directive without calling __init__
-        self = cls.__new__(cls)
+    def set(cls, component, value):
+        cls.store.setattr(component, cls, value)
 
-        value = self.store.get(self, component, _USE_DEFAULT)
-        if value is _USE_DEFAULT and module is not None:
-            value = self.store.get(self, module, _USE_DEFAULT)
-        if value is _USE_DEFAULT:
-            value = self.get_default(component)
+    @classmethod
+    def bind(cls, default=None, get_default=None, name=None):
+        return BoundDirective(cls, default, get_default, name)
 
-        return value
 
-    @classmethod
-    def set(cls, component, value):
-        cls.store.setattr(component, cls, value)
+class BoundDirective(object):
 
+    def __init__(self, directive, default=None, get_default=None, name=None):
+        self.directive = directive
+        self.default = default
+        if name is None:
+            name = directive.__name__
+        self.name = name
+        if get_default is not None:
+            self.get_default = get_default
 
+    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,
+                                    default=_USE_DEFAULT)
+        if value is _USE_DEFAULT:
+            value = self.get_default(component, module, **data)
+        return value
+
 class MultipleTimesDirective(Directive):
     store = MULTIPLE
     default = []

Modified: martian/trunk/src/martian/directive.txt
===================================================================
--- martian/trunk/src/martian/directive.txt	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/directive.txt	2008-05-14 15:57:19 UTC (rev 86732)
@@ -1,5 +1,5 @@
-Directives New Style
-====================
+Directives
+==========
 
 When grokking a class, the grokking procedure can be informed by
 directives, on a class, or a module. If a directive is absent, the
@@ -28,10 +28,11 @@
   >>> class Foo(object):
   ...    description(u"This is a description")
 
-After setting it, we can use the ``get`` method on the directive to
-retrieve it from the class again::
+After setting the description, we bind the directive and obtain a
+bound directive object.  This object has means for retrieving the data
+set by the directive, in particular the ``get`` method::
 
-  >>> description.get(Foo)
+  >>> description.bind().get(Foo)
   u'This is a description'
 
 Directives in different namespaces get stored differently. We'll
@@ -43,9 +44,9 @@
   >>> class Foo(object):
   ...     description(u"Description1")
   ...     description2(u"Description2")
-  >>> description.get(Foo)
+  >>> description.bind().get(Foo)
   u'Description1'
-  >>> description2.get(Foo)
+  >>> description2.bind().get(Foo)
   u'Description2'
 
 If we check the value of a class without the directive, we see the
@@ -53,14 +54,14 @@
 
   >>> class Foo(object):
   ...     pass
-  >>> description.get(Foo)
+  >>> description.bind().get(Foo)
   u''
 
 In certain cases we need to set a value on a component as if the directive was
 actually used::
 
   >>> description.set(Foo, u'value as set')
-  >>> description.get(Foo)
+  >>> description.bind().get(Foo)
   u'value as set'
 
 Subclasses of the original class will inherit the properties set by the
@@ -72,7 +73,7 @@
   >>> class Bar(Foo):
   ...     pass
   ...
-  >>> description.get(Bar)
+  >>> description.bind().get(Bar)
   'This is a foo.'
 
 When we use the directive outside of class scope, we get an error
@@ -124,18 +125,24 @@
 By default, the ``default`` property is None which is why we can omit
 specifying it here.
 
+This directive has been declared ``CLASS_OR_MODULE``, so you will
+always have to pass a module to the directive. Since we don't have a
+module yet we'll simply create a dummy, empty, fallback module::
+
+  >>> dummy = object()
+
 We can use this directive now on a class::
 
   >>> class Foo(object):
   ...   layer('Test')
-  >>> layer.get(Foo)
+  >>> layer.bind().get(Foo, dummy)
   'Test'
 
 The defaulting to ``None`` works::
 
   >>> class Foo(object):
   ...   pass
-  >>> layer.get(Foo) is None
+  >>> layer.bind().get(Foo, dummy) is None
   True
 
 We can also use it in a module::
@@ -144,13 +151,13 @@
   ...    layer('Test2')
   ...    class Foo(object):
   ...       pass
-  >>> test_module = fake_import(testmodule)
+  >>> testmodule = fake_import(testmodule)
 
 When we now try to access ``layer`` on ``Foo``, we find the
 module-level default which we just set. We pass the module as the
 second argument to the ``get`` method to have it fall back on this::
 
-  >>> layer.get(testmodule.Foo, testmodule)
+  >>> layer.bind().get(testmodule.Foo, testmodule)
   'Test2'
 
 Let's look at a module where the directive is not used::
@@ -163,9 +170,15 @@
 In this case, the value cannot be found so the system falls back on
 the default, ``None``::
 
-  >>> layer.get(testmodule.Foo, testmodule) is None
+  >>> layer.bind().get(testmodule.Foo, testmodule) is None
   True
 
+Let's now look at this using a directive with CLASS scope only::
+
+  >>> class layer2(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+
 Using a directive multiple times
 --------------------------------
 
@@ -184,14 +197,14 @@
 
 We can now retrieve the value and we'll get a list::
 
-  >>> multi.get(Foo)
+  >>> multi.bind().get(Foo)
   [u'Once', u'Twice']
 
 The default value for a MultipleTimesDirective is an empty list::
 
   >>> class Bar(object):
   ...   pass
-  >>> multi.get(Bar)
+  >>> multi.bind().get(Bar)
   []
 
 Whenever the directive is used on a sub class of a component, the values set by
@@ -200,7 +213,7 @@
   >>> class Qux(Foo):
   ...     multi(u'Triple')
   ...
-  >>> multi.get(Qux)
+  >>> multi.bind().get(Qux)
   [u'Once', u'Twice', u'Triple']
 
 
@@ -226,7 +239,7 @@
 
 We can now retrieve the value and we'll get a to the items::
 
-  >>> d = multi.get(Bar)
+  >>> d = multi.bind().get(Bar)
   >>> print sorted(d.items())
   [(u'once', u'Once'), (u'twice', u'Twice')]
 
@@ -276,7 +289,7 @@
   ...   multi(3, 'DDD')
   ...   multi(4, 'EEE')
 
-  >>> d = multi.get(Fropple)
+  >>> d = multi.bind().get(Fropple)
   >>> print sorted(d.items())
   [(1, 'CCC'), (2, 'BBB'), (3, 'DDD'), (4, 'EEE')]
 
@@ -295,7 +308,7 @@
   ...     multi('Two')
   ...
   >>> module_with_directive = fake_import(module_with_directive)
-  >>> print multi.get(module_with_directive)
+  >>> print multi.bind().get(module=module_with_directive)
   ['One', 'Two']
 
   >>> from martian import MODULE
@@ -313,7 +326,7 @@
   ...     multi(2, 'Two')
   ...
   >>> module_with_directive = fake_import(module_with_directive)
-  >>> d = multi.get(module_with_directive)
+  >>> d = multi.bind().get(module=module_with_directive)
   >>> print sorted(d.items())
   [(1, 'One'), (2, 'Two')]
 
@@ -329,17 +342,21 @@
   >>> class name(Directive):
   ...     scope = CLASS
   ...     store = ONCE
-  ...     def get_default(self, component):
-  ...         return component.__name__.lower()
+  ...
+  >>> def default_name_lowercase(component, module, **data):
+  ...     return component.__name__.lower()
+  ...
+  >>> bound_name = name.bind(get_default=default_name_lowercase)
 
   >>> class Foo(object):
   ...   name('bar')
-  >>> name.get(Foo)
+
+  >>> bound_name.get(Foo)
   'bar'
 
   >>> class Foo(object):
   ...   pass
-  >>> name.get(Foo)
+  >>> bound_name.get(Foo)
   'foo'
 
 A marker directive
@@ -357,14 +374,14 @@
 
 Class ``Foo`` is now marked::
 
-  >>> mark.get(Foo)
+  >>> mark.bind().get(Foo)
   True
 
 When we have a class that isn't marked, we get the default value, ``False``::
 
   >>> class Bar(object):
   ...    pass
-  >>> mark.get(Bar)
+  >>> mark.bind().get(Bar)
   False
 
 If we pass in an argument, we get an error::
@@ -539,7 +556,7 @@
 As you would expect, the directive will correctly identify this class as a
 baseclass:
 
-  >>> baseclass.get(MyBase)
+  >>> baseclass.bind().get(MyBase)
   True
 
 But, if we create a subclass of this base class, the subclass won't inherit
@@ -548,7 +565,7 @@
   >>> class SubClass(MyBase):
   ...     pass
   ...
-  >>> baseclass.get(SubClass)
+  >>> baseclass.bind().get(SubClass)
   False
 
 Naturally, the directive will also report a false answer if the class doesn't
@@ -557,5 +574,5 @@
   >>> class NoBase(object):
   ...     pass
   ...
-  >>> baseclass.get(NoBase)
+  >>> baseclass.bind().get(NoBase)
   False

Modified: martian/trunk/src/martian/tests/test_all.py
===================================================================
--- martian/trunk/src/martian/tests/test_all.py	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/tests/test_all.py	2008-05-14 15:57:19 UTC (rev 86732)
@@ -16,7 +16,7 @@
     module = new.module(fake_module.__name__)
     glob = {}
     for name in dir(fake_module):
-        if name.startswith('__'):
+        if name.startswith('__') and '.' not in name:
             continue
         obj = getattr(fake_module, name)
         glob[name] = obj

Modified: martian/trunk/src/martian/util.py
===================================================================
--- martian/trunk/src/martian/util.py	2008-05-14 15:52:50 UTC (rev 86731)
+++ martian/trunk/src/martian/util.py	2008-05-14 15:57:19 UTC (rev 86732)
@@ -49,7 +49,7 @@
     return sys._getframe(2).f_globals['__name__']
 
 def is_baseclass(name, component):
-    return (isclass(component) and martian.baseclass.get(component))
+    return (isclass(component) and martian.baseclass.bind().get(component))
 
 def defined_locally(obj, dotted_name):
     obj_module = getattr(obj, '__grok_module__', None)



More information about the Checkins mailing list