[Checkins] SVN: martian/trunk/ Merged philikon-methodgrokker branch:

Philipp von Weitershausen philikon at philikon.de
Thu May 29 06:20:37 EDT 2008


Log message for revision 87011:
  Merged philikon-methodgrokker branch:
  
  * Added a ``MethodGrokker`` base class for grokkers that want to grok
    methods of a class rather than the whole class itself.  It works
    quite similar to the ``ClassGrokker`` regarding directive
    definition, except that directives evaluated not only on class (and
    possibly module) level but also for each method.  That way,
    directives can also be applied to methods (as decorators) in case
    they support it.
  
  

Changed:
  U   martian/trunk/CHANGES.txt
  U   martian/trunk/src/martian/README.txt
  U   martian/trunk/src/martian/__init__.py
  U   martian/trunk/src/martian/components.py
  A   martian/trunk/src/martian/tests/public_methods_from_class.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-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/CHANGES.txt	2008-05-29 10:20:36 UTC (rev 87011)
@@ -4,8 +4,17 @@
 0.9.7 (unreleased)
 ==================
 
-* ...
+Feature changes
+---------------
 
+* Added a ``MethodGrokker`` base class for grokkers that want to grok
+  methods of a class rather than the whole class itself.  It works
+  quite similar to the ``ClassGrokker`` regarding directive
+  definition, except that directives evaluated not only on class (and
+  possibly module) level but also for each method.  That way,
+  directives can also be applied to methods (as decorators) in case
+  they support it.
+
 0.9.6 (2008-05-14)
 ==================
 

Modified: martian/trunk/src/martian/README.txt
===================================================================
--- martian/trunk/src/martian/README.txt	2008-05-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/src/martian/README.txt	2008-05-29 10:20:36 UTC (rev 87011)
@@ -550,6 +550,77 @@
   hippopotamus
   True
 
+MethodGrokker
+-------------
+
+A special kind of class grokker is the ``MethodGrokker``.  It inspects
+the class at hand and calls ``execute`` for each *method* the class
+provides.
+
+Consider the following baseclass for circus animals:
+
+  >>> class CircusAnimal(animal.Animal):
+  ...   def begin_show(self):
+  ...     pass
+  ...   def end_show(self):
+  ...      pass
+
+Circus animals define lots of methods which we'll collect using this
+grokker:
+
+  >>> circus_animals = {}
+  >>> from martian import MethodGrokker
+  >>> class CircusAnimalGrokker(MethodGrokker):
+  ...   component_class = CircusAnimal
+  ...   def execute(self, class_, method, **kw):
+  ...     circus_animals.setdefault(class_.__name__, []).append(method.__name__)
+  ...     return True
+  ...
+
+Now consider the following circus animals:
+
+  >>> class Monkey(CircusAnimal):
+  ...   def climb(self):
+  ...     pass
+  ...   def _take_dump(self):
+  ...     pass
+  ...
+  >>> class Camel(CircusAnimal):
+  ...   def walk(self):
+  ...     pass
+  ...   def spit(self):
+  ...     pass
+
+  >>> circus_animal_grokker = CircusAnimalGrokker()
+  >>> circus_animal_grokker.grok('Monkey', Monkey)
+  True
+  >>> circus_animal_grokker.grok('Camel', Camel)
+  True
+
+Let's look at the results:
+
+  >>> for circus_animal, methods in sorted(circus_animals.items()):
+  ...     print "%s can %s." % (circus_animal, " and ".join(sorted(methods)))
+  ...
+  Camel can spit and walk.
+  Monkey can climb.
+
+As we see, private methods (those beginning with underscores) have
+been ignored.  Furthermore, methods inherited from the component
+baseclass (in this case ``CircusAnimal``) have also been ignored.
+
+If we wrote a class without any methods, we would encounter an error:
+
+  >>> class Snail(CircusAnimal):
+  ...   pass
+
+  >>> circus_animal_grokker.grok('Snail', Snail)
+  Traceback (most recent call last):
+    ...
+  GrokError: <class 'Snail'> does not define any public
+  methods. Please add methods to this class to enable its
+  registration.
+
 MultiClassGrokker
 -----------------
 

Modified: martian/trunk/src/martian/__init__.py
===================================================================
--- martian/trunk/src/martian/__init__.py	2008-05-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/src/martian/__init__.py	2008-05-29 10:20:36 UTC (rev 87011)
@@ -2,6 +2,7 @@
     ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name,
     grok_package, grok_module)
 from martian.components import GlobalGrokker, ClassGrokker, InstanceGrokker
+from martian.components import MethodGrokker
 from martian.util import scan_for_classes
 from martian.directive import Directive, MarkerDirective, MultipleTimesDirective
 from martian.directive import ONCE, MULTIPLE, DICT

Modified: martian/trunk/src/martian/components.py
===================================================================
--- martian/trunk/src/martian/components.py	2008-05-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/src/martian/components.py	2008-05-29 10:20:36 UTC (rev 87011)
@@ -14,8 +14,9 @@
 
 from zope.interface import implements
 
+from martian import util
+from martian.error import GrokError
 from martian.interfaces import IGrokker, IComponentGrokker
-from martian import util
 
 NOT_DEFINED = object()
 
@@ -64,6 +65,45 @@
     def execute(self, class_, **data):
         raise NotImplementedError
 
+
+class MethodGrokker(ClassGrokker):
+
+    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 class or module
+        for directive in self.directives:
+            kw[directive.name] = directive.get(class_, module, **kw)
+
+        # Ignore methods that are present on the component baseclass.
+        basemethods = set(util.public_methods_from_class(self.component_class))
+        methods = set(util.public_methods_from_class(class_)) - basemethods
+        if not methods:
+            raise GrokError("%r does not define any public methods. "
+                            "Please add methods to this class to enable "
+                            "its registration." % class_, class_)
+
+        results = []
+        for method in methods:
+            # Directives may also be applied to methods, so let's
+            # check each directive and potentially override the
+            # class-level value with a value from the method *locally*.
+            data = kw.copy()
+            for bound_dir in self.directives:
+                directive = bound_dir.directive
+                class_value = data[bound_dir.name]
+                data[bound_dir.name] = directive.store.get(directive, method,
+                                                           default=class_value)
+            results.append(self.execute(class_, method, **data))
+
+        return max(results)
+
+    def execute(self, class_, method, **data):
+        raise NotImplementedError
+
+
 class InstanceGrokker(ComponentGrokkerBase):
     """Grokker that groks instances in a module.
     """

Copied: martian/trunk/src/martian/tests/public_methods_from_class.txt (from rev 86972, martian/branches/philikon-methodgrokker/src/martian/tests/public_methods_from_class.txt)
===================================================================
--- martian/trunk/src/martian/tests/public_methods_from_class.txt	                        (rev 0)
+++ martian/trunk/src/martian/tests/public_methods_from_class.txt	2008-05-29 10:20:36 UTC (rev 87011)
@@ -0,0 +1,32 @@
+Consider the following class
+
+  >>> class A(object):
+  ...     an_attribute = 42
+  ...
+  ...     def __init__(self):
+  ...         pass # this method is ignored
+  ...
+  ...     def __call__(self):
+  ...         pass # this method is ignored
+  ...
+  ...     def __double_underscored(self):
+  ...         pass # this method is ignored
+  ...
+  ...     def _single_underscored(self):
+  ...         pass # this method is ignored
+  ...
+  ...     def should_be_public(self):
+  ...         pass # this method is found
+  ...
+  ...     def should_also_be_public(self):
+  ...         pass # this method is found
+  ...
+
+With martian's ``public_methods_from_class`` helper we can extract all
+public methods from this class, in other words, all methods that do
+not begin with an underscore:
+
+  >>> from martian import util
+  >>> methods = util.public_methods_from_class(A)
+  >>> sorted([m.__name__ for m in methods])
+  ['should_also_be_public', 'should_be_public']

Modified: martian/trunk/src/martian/tests/test_all.py
===================================================================
--- martian/trunk/src/martian/tests/test_all.py	2008-05-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/src/martian/tests/test_all.py	2008-05-29 10:20:36 UTC (rev 87011)
@@ -5,13 +5,6 @@
 class FakeModule(object):
     pass
 
-def class_annotation_nobase(obj, name, default):
-    """This will only look in the given class obj for the annotation.
-
-    It will not look in the inheritance chain.
-    """
-    return obj.__dict__.get('__%s__' % name.replace('.', '_'), default)
-
 def fake_import(fake_module):
     module = new.module(fake_module.__name__)
     glob = {}
@@ -79,5 +72,8 @@
         doctest.DocFileSuite('scan_for_classes.txt',
                              package='martian.tests',
                              optionflags=optionflags),
+        doctest.DocFileSuite('public_methods_from_class.txt',
+                             package='martian.tests',
+                             optionflags=optionflags),
         ])
     return suite

Modified: martian/trunk/src/martian/util.py
===================================================================
--- martian/trunk/src/martian/util.py	2008-05-29 10:19:05 UTC (rev 87010)
+++ martian/trunk/src/martian/util.py	2008-05-29 10:20:36 UTC (rev 87011)
@@ -107,6 +107,10 @@
     methods = [c for c in candidates if inspect.ismethod(c)]
     return methods
 
+def public_methods_from_class(class_):
+    return [m for m in methods_from_class(class_) if \
+            not m.__name__.startswith('_')]
+
 def frame_is_module(frame):
     return frame.f_locals is frame.f_globals
 



More information about the Checkins mailing list