[Checkins] SVN: grok/trunk/martian/src/martian/ Introduce MultiMartian, a martian that can detect various martians.

Martijn Faassen faassen at infrae.com
Tue May 1 14:31:21 EDT 2007


Log message for revision 74968:
  Introduce MultiMartian, a martian that can detect various martians. 
  Note that test-coverage for the new code introduced is not yet complete.
  

Changed:
  U   grok/trunk/martian/src/martian/README.txt
  U   grok/trunk/martian/src/martian/core.py
  U   grok/trunk/martian/src/martian/interfaces.py
  U   grok/trunk/martian/src/martian/tests/test_all.py

-=-
Modified: grok/trunk/martian/src/martian/README.txt
===================================================================
--- grok/trunk/martian/src/martian/README.txt	2007-05-01 16:10:37 UTC (rev 74967)
+++ grok/trunk/martian/src/martian/README.txt	2007-05-01 18:31:21 UTC (rev 74968)
@@ -212,15 +212,13 @@
 Let's look at a special martian that can grok a Python module::
 
   >>> from martian import ModuleMartian
-  >>> module_martian = ModuleMartian()
 
 The idea is that the ``ModuleMartian`` groks any components in a
 module that it recognizes. A ``ModuleMartian`` does not work alone. It
-needs to be supplied with one or more martians that can grok
-components to be found in a module. Let's register the
-``filetype_martian`` with our ``module_martian``::
+needs to be supplied with a martian that can grok the components
+to be founded in a module. Let's register use our ``filetype_martian``::
 
-  >>> module_martian.register(filetype_martian)
+  >>> module_martian = ModuleMartian(filetype_martian)
 
 We now define a module that defines a few filetype handlers to be
 grokked::
@@ -285,3 +283,163 @@
   >>> module_martian.grok('filehandler', filehandler)
   >>> filehandler.handle('test.txt')
   'Text file'
+
+InstanceMartian
+---------------
+
+Previously we've introduced a martian that can look for module-level
+functions. We also can also define martians to look for other things, such
+instances. Let's define a module that defines what we want to grok::
+
+  >>> class color(FakeModule):
+  ...   class Color(object):
+  ...     def __init__(self, r, g, b):
+  ...       self.r = r
+  ...       self.g = g
+  ...       self.b = b
+  ...     def __repr__(self):
+  ...       return '<Color %s %s %s>' % (self.r, self.g, self.b) 
+  ...   all_colors = {}
+  >>> color = fake_import(color)
+
+We now want a martian that can recognize colors and put them in the
+``all_colors`` dictionary, with the names as the keys, and the color
+object as the values. We can use ``InstanceMartian`` to construct it::
+
+  >>> from martian import InstanceMartian
+  >>> class ColorMartian(InstanceMartian):
+  ...   component_class = color.Color
+  ...   def grok(self, name, obj):
+  ...       color.all_colors[name] = obj
+
+An ``InstanceMartian`` matches only instances of the given
+``component_class``. Let's try that::
+
+  >>> color_martian = ColorMartian()
+  >>> black = color.Color(0, 0, 0) # we DO consider black as a color :)
+  >>> color_martian.match('black', black)
+  True
+  >>> not_a_color = object()
+  >>> color_martian.match('foo', not_a_color)
+  False
+
+Now let's grok the color::
+
+  >>> color_martian.grok('black', black)
+
+It ends up in the ``all_colors`` dictionary::
+
+  >>> color.all_colors
+  {'black': <Color 0 0 0>}
+
+If we put ``color_martian`` into a ``ModuleMartian``, we can now grok
+multiple colors in a module::
+
+  >>> Color = color.Color
+  >>> class colors(FakeModule):
+  ...   red = Color(255, 0, 0)
+  ...   green = Color(0, 255, 0)
+  ...   blue = Color(0, 0, 255)
+  ...   white = Color(255, 255, 255)
+  >>> colors = fake_import(colors)
+  >>> colors_martian = ModuleMartian(color_martian)
+  >>> colors_martian.grok('colors', colors)
+  >>> sorted(color.all_colors.items())
+  [('black', <Color 0 0 0>), 
+   ('blue', <Color 0 0 255>), 
+   ('green', <Color 0 255 0>), 
+   ('red', <Color 255 0 0>), 
+   ('white', <Color 255 255 255>)]
+
+Subclasses of ``Color`` are also grokked::
+
+  >>> class subcolors(FakeModule):
+  ...   class SpecialColor(Color):
+  ...     pass
+  ...   octarine = SpecialColor(-255, 0, -255)
+  >>> subcolors = fake_import(subcolors)
+  >>> colors_martian.grok('subcolors', subcolors)
+  >>> 'octarine' in color.all_colors
+  True
+
+MultiInstanceMartian
+--------------------
+
+In the previous section we have created a particular martian that
+looks for instances of a component class, in this case
+``Color``. Let's introduce another ``InstanceMartian`` that looks for
+instances of ``Sound``::
+  
+  >>> class sound(FakeModule):
+  ...   class Sound(object):
+  ...     def __init__(self, desc):
+  ...       self.desc = desc
+  ...     def __repr__(self):
+  ...       return '<Sound %s>' % (self.desc) 
+  ...   all_sounds = {}
+  >>> sound = fake_import(sound)
+
+  >>> class SoundMartian(InstanceMartian):
+  ...   component_class = sound.Sound
+  ...   def grok(self, name, obj):
+  ...       sound.all_sounds[name] = obj
+  >>> sound_martian = SoundMartian()
+ 
+What if we now want to look for ``Sound`` and ``Color`` instances at
+the same time? We have to use the ``color_martian`` and
+``sound_martian`` at the same time, and we can do this with a
+``MultiInstanceMartian``::
+
+  >>> from martian.core import MultiInstanceMartian
+  >>> multi_martian = MultiInstanceMartian()
+  >>> multi_martian.register(color_martian)
+  >>> multi_martian.register(sound_martian)
+
+Let's grok a new color with our ``multi_martian``::
+
+  >>> grey = Color(100, 100, 100)
+  >>> multi_martian.grok('grey', grey)
+  >>> 'grey' in color.all_colors
+  True
+
+Let's grok a sound with our ``multi_martian``::
+  
+  >>> moo = sound.Sound('Moo!')
+  >>> multi_martian.grok('moo', moo)
+  >>> 'moo' in sound.all_sounds
+  True
+
+We can also grok other objects, but this will have no effect::
+
+  >>> something_else = object()
+  >>> multi_martian.grok('something_else', something_else)
+
+Let's put our ``multi_martian`` in a ``ModuleMartian``::
+
+  >>> module_martian = ModuleMartian(multi_martian)
+
+We can now grok a module for both ``Color`` and ``Sound`` instances::
+
+  >>> Sound = sound.Sound
+  >>> class lightandsound(FakeModule):
+  ...   dark_red = Color(150, 0, 0)
+  ...   scream = Sound('scream')
+  ...   dark_green = Color(0, 150, 0)
+  ...   cheer = Sound('cheer')
+  >>> lightandsound = fake_import(lightandsound)
+  >>> module_martian.grok('lightandsound', lightandsound)
+  >>> 'dark_red' in color.all_colors
+  True
+  >>> 'dark_green' in color.all_colors
+  True
+  >>> 'scream' in sound.all_sounds
+  True
+  >>> 'cheer' in sound.all_sounds
+  True
+
+ClassMartian
+------------
+
+GlobalMartian
+-------------
+

Modified: grok/trunk/martian/src/martian/core.py
===================================================================
--- grok/trunk/martian/src/martian/core.py	2007-05-01 16:10:37 UTC (rev 74967)
+++ grok/trunk/martian/src/martian/core.py	2007-05-01 18:31:21 UTC (rev 74968)
@@ -2,79 +2,99 @@
 
 from zope.interface import implements
 
-from martian.interfaces import INestedMartian
+from martian.interfaces import IMartian, IMultiMartian
 from martian import components, util
 
+def is_baseclass(name, component):
+    return (type(component) is type and
+            (name.endswith('Base') or
+             util.class_annotation_nobase(component, 'grok.baseclass', False)))
+        
 class ModuleMartian(components.MartianBase):
-    implements(INestedMartian)
+    implements(IMartian)
 
-    def __init__(self):
-        self._martians = {}
-        
-    def register(self, martian):
-        # make sure martians are only registered once
-        key = martian.__class__
-        if key in self._martians:
-            return
-        self._martians[key] = martian
+    def __init__(self, martian):
+        self._martian = martian
 
     def match(self, name, module):
         return isinstance(module, types.ModuleType)
     
     def grok(self, name, module, **kw):
-        scanned_results = self._scan(module)
-        # run through all martians registering found components in order
-        for martian in self._get_ordered_martians():
-            # if we run into a GlobalMartian, just do simple registration.
-            # this allows us to hook up martians that actually
-            # do not respond to anything in the module but for instance
-            # go to the filesystem to find templates
-            if isinstance(martian, components.GlobalMartian):
-                martian.grok(name, module, **kw)
-                continue
+        martian = self._martian
+        
+        if isinstance(martian, components.GlobalMartian):
+            martian.grok(name, module, **kw)
+            return
 
-            found_components = scanned_results.get(martian.component_class, [])
-
-            for name, component in found_components:
-                # this is a base class as it ends with Base, skip
-                if type(component) is type:
-                    if name.endswith('Base'):
-                        continue
-                    elif util.class_annotation_nobase(component,
-                                                      'grok.baseclass', False):
-                        continue
-                martian.grok(name, component, **kw)    
-
-    def _scan(self, module):
-        found_components = {}
-        for martian in self._martians.values():
-            if isinstance(martian, components.GlobalMartian):
-                continue
-            found_components[martian.component_class] = []
-
-        martians = self._get_ordered_martians()
-
         for name in dir(module):
             if name.startswith('__grok_'):
                 continue
             obj = getattr(module, name)
             if not util.defined_locally(obj, module.__name__):
                 continue
-            # XXX find way to get rid of this inner loop by doing hash table
-            # lookup?
+            if is_baseclass(name, obj):
+                continue
+            if not martian.match(name, obj):
+                continue
+            martian.grok(name, obj, **kw)
+
+class MultiMartianBase(components.MartianBase):
+    implements(IMultiMartian)
+
+    def __init__(self):
+        self._martians = {}
+        
+    def register(self, martian):
+        key = martian.component_class
+        martians = self._martians.setdefault(key, [])
+        if martian not in martians:
+            martians.append(martian)
+    
+    def match(self, name, obj):
+        for martians in self._martians.values():
             for martian in martians:
                 if martian.match(name, obj):
-                    found_components[martian.component_class].append(
-                        (name, obj))
-                    if not martian.continue_scanning:
-                        break
+                    return True
+        return False
 
-        return found_components
+    def grok(self, name, obj, **kw):
+        used_martians = set()
+        for base in self.get_bases(obj):
+            martians = self._martians.get(base)
+            if martians is None:
+                continue
+            for martian in martians:
+                if martian not in used_martians:
+                    martian.grok(name, obj, **kw)
+                    used_martians.add(martian)
+        
+class MultiInstanceMartian(MultiMartianBase):
+    def get_bases(self, obj):
+        # XXX how to work with old-style classes?
+        return obj.__class__.__mro__
 
-    def _get_ordered_martians(self):
-        # sort martians by priority
-        martians = sorted(self._martians.values(),
-                          key=lambda martian: martian.priority)
-        # we want to handle high priority first
-        martians.reverse()
-        return martians
+class MultiClassMartian(MultiMartianBase):
+    def get_bases(self, obj):
+        # XXX how to work with old-style classes?
+        return obj.__mro__
+
+class MultiMartian(components.MartianBase):
+    implements(IMultiMartian)
+    
+    def __init__(self):
+        self._multi_instance_martian = MultiInstanceMartian()
+        self._multi_class_martian = MultiClassMartian()
+        
+    def register(self, martian):
+        if isinstance(martian, InstanceMartian):
+            self._multi_instance_martian.register(martian)
+        elif isinstance(martian, ClassMartian):
+            self._multi_class_martian.register(martian)
+        else:
+            assert 0, "Unknown type of martian: %r" % martian
+
+    def grok(self, name, obj, **kw):
+        if type(obj) is type:
+            self._multi_class_martian.grok(name, obj, **kw)
+        else:
+            self._multi_instance_martian.grok(name, obj, **kw)

Modified: grok/trunk/martian/src/martian/interfaces.py
===================================================================
--- grok/trunk/martian/src/martian/interfaces.py	2007-05-01 16:10:37 UTC (rev 74967)
+++ grok/trunk/martian/src/martian/interfaces.py	2007-05-01 18:31:21 UTC (rev 74968)
@@ -40,13 +40,13 @@
     """
     component_class = Attribute('Class of the component to match')
     
-class INestedMartian(IComponentMartian):
-    """A martian that contains other martians.
+class IMultiMartian(IComponentMartian):
+    """A martian that is composed out of multiple martians.
     """
     def register(martian):
-        """Register a martian to nest within this one.
+        """Register a martian.
         """
-        
+
 class IModuleInfo(Interface):
     def getModule():
         """Get the module object this module info is representing.

Modified: grok/trunk/martian/src/martian/tests/test_all.py
===================================================================
--- grok/trunk/martian/src/martian/tests/test_all.py	2007-05-01 16:10:37 UTC (rev 74967)
+++ grok/trunk/martian/src/martian/tests/test_all.py	2007-05-01 18:31:21 UTC (rev 74968)
@@ -5,6 +5,13 @@
 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 = {}
@@ -17,6 +24,19 @@
             obj = obj.im_func
         except AttributeError:
             pass
+        __module__ = None
+        try:
+            __module__ == obj.__dict__.get('__module__')
+        except AttributeError:
+            try:
+                __module__ = obj.__module__
+            except AttributeError:
+                pass
+        if __module__ is None or __module__ == '__builtin__':
+            try:
+                obj.__module__ = module.__name__
+            except AttributeError:
+                pass
         setattr(module, name, obj)
         glob[name] = obj
     # provide correct globals for functions



More information about the Checkins mailing list