[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