[Zope-dev] registering adapters and subscribers for string labels

Chris Withers chris at simplistix.co.uk
Fri Apr 25 07:13:10 EDT 2008


Hi All,

I bumped into hurry.workflow yesterday and was reminded again that we
often have the need to register components against string labels.

As an example, I want to register a subscriber to a particular workflow
transition.

Now, the hurry.workflow examples I've seen seem to revolve around a
generic subscriber with a load of if-then-else logic in them - exactly
the kind of stuff the component architecture is designed to avoid ;-)
(not only that, it's *really* inefficient too)

So, I dug up some code I wrote to get around this and thought I'd throw
it out to the list. I have a feeling this should ship somewhere, but I
don't know where. Any suggestions?
(if you look you'll see the code is all tested with a nice little
narrative doctest in actions.py and a selection of unit tests for the
edge cases in the test file, should keep even Marius happy ;-) )

cheers,

Chris

PS: I showed Kapil this code yesterday and he asked "why screw with
globals". The answer is to allow the registrations against names to be
made in zcml - as you can see in the doctest. I'm open to other
suggestions for this ;-)

-- 
Simplistix - Content Management, Zope & Python Consulting
             - http://www.simplistix.co.uk

-------------- next part --------------
"""
This module contains class singletons for any actions
that we want to register adapters against.

>>> from actions import register,get

To create a new one:

>>> register('foo')
'foo'

To get hold of one once it's been registered:

>>> get('foo')
<Action:'foo'>

And why do we want to do all this? Well, so we can register
adapters against string constants like workflow actions:

>>> do_zcml('''
... <adapter
...       for="test_actions.SomeClass actions.foo"
...       provides="test_actions.ISomething"
...       factory="test_actions.SomethingAdapter"
...       />
... ''')

Now we can get an adapter as follows:

>>> from test_actions import SomeClass,ISomething
>>> from zope.component import getMultiAdapter
>>> o = SomeClass()
>>> getMultiAdapter((o,get('foo')),ISomething)
<SomethingAdapter for (<SomeClass instance>, <Action:'foo'>)>

"""

from new import classobj

registry = {}

class Action:

    def name(self):
        return self.__class__.__name__

    def __repr__(self):
        return '<Action:%r>' % self.name()
    
def get(name):
    return registry[name]

def register(name):
    if not registry.has_key(name):
        mod_name = name.replace(' ','_')
        if mod_name in globals():
            raise ValueError(
                '%r is not available for registration as an action name' % mod_name
                )
        k = classobj(name,(Action,),{})
        registry[name]=k()
        globals()[mod_name]=k
    return name
    

    
    

-------------- next part --------------
from actions import get,register,Action
from unittest import TestCase,TestSuite,makeSuite
from zope.app import component
from zope.component import provideAdapter
from zope.configuration import xmlconfig
from zope.interface import Interface,implements
from zope.testing.doctest import DocTestSuite

class SomeClass:

    def __repr__(self):
        return '<SomeClass instance>'

class ISomething(Interface): pass

class SomethingAdapter:

    implements(ISomething)
    
    def __init__(self,*args):
        self.args = args

    def __repr__(self):
        return '<SomethingAdapter for %s>' % repr(self.args)
        
def do_zcml(text):
    xmlconfig.string(('''
<configure
    xmlns="http://namespaces.zope.org/zope">
%s
</configure>
'''%text).strip(),xmlconfig.file('meta.zcml',component))
    
class Tests(TestCase):

    def test_get(self):
        # we test a bogus get here, a kosher one is tested
        # in test_register
        self.assertRaises(KeyError,get,'bogus')
        
    def test_register(self):
        name = register('foo')
        self.assertEqual(name,'foo')
        obj = get('foo')
        self.failUnless(isinstance(obj,Action))
        self.assertEqual(obj.name(),'foo')
        self.assertEqual(repr(obj),"<Action:'foo'>")
        
    def test_duplicate_register(self):
        register('foo')
        obj1 = get('foo')
        register('foo')
        obj2 = get('foo')
        self.failUnless(obj1 is obj2)
        
    def test_adapter(self):
        register('foo bar')
        xmlconfig.string('''
<configure
    xmlns="http://namespaces.zope.org/zope">
<adapter
      for="actions.foo_bar"
      provides="test_actions.ISomething"
      factory="test_actions.SomethingAdapter"
      />
</configure>
        '''.strip(),xmlconfig.file('meta.zcml',component))
        a = ISomething(get('foo bar'))
        self.failUnless(isinstance(a,SomethingAdapter))
        self.assertEqual(len(a.args),1)
        self.failUnless(a.args[0] is get('foo bar'))
        
    def test_register_bad(self):
        self.assertRaises(ValueError,register,'Action')
        self.assertRaises(ValueError,register,'get')
        self.assertRaises(ValueError,register,'register')
        self.assertRaises(ValueError,register,'registry')

def test_suite():
    suite = TestSuite()
    suite.addTest(makeSuite(Tests))
    suite.addTest(DocTestSuite('actions',
                               globs={'do_zcml':do_zcml}))
    return suite



More information about the Zope-Dev mailing list