pythonzopezope.component

Why is my MultiAdapter failing to register?


I'm currently experimenting with the ZCA and I've run into a bit of a hitch. The script below defines an IFuncttion interface, which is used to create function objects that can be chained (i.e. monads).

In this example, I'm trying to create a simple writer monad as an adapter, but getMultiAdapter is raising a ComponentLookupError. Why is this?

from zope import component, interface


class IFunction(interface.Interface):
    name = interface.Attribute('Name of the function object')

    def __call__(*args, **kw):
        """Call the function"""


class Function(object):
    name = ''
    interface.implements(IFunction)

    def __call__(self, *args, **kw):
        return self.run(*args, **kw)

    def run(self, *args, **kw):
        raise NotImplementedError


class MWriter(object):
    component.adapts(IFunction, IFunction)
    interface.implements(IFunction)

    def __init__(self, prv, nxt):
        self.prev, self.next = prv, nxt

    def bind(self, x, log=None):
        log = log or []
        result, line = self.prev(x)
        log.append(line)
        return self.next(result, log)

    def __call__(self, *args, **kw):
        return self.bind(*args, **kw)


class AddOne(Function):
    name = 'addone'

    def run(self, x):
        return x + 1


class MulTwo(Function):
    name = 'multwo'

    def run(self, x):
        return x * 2

component.provideAdapter(MWriter)
print component.getMultiAdapter((AddOne(), MulTwo()), MWriter)(11, [])

Solution

  • You should not pass in the adapter you want to look up to component.getMultiAdapter(). The second argument to that function is the name used for named adapters, but your registration did not use a name.

    Simply remove that second argument:

    >>> component.getMultiAdapter((AddOne(), MulTwo()))
    <__main__.MWriter object at 0x1072516d0>
    

    Unfortunately, calling MWriter() still fails because you expect self.prev() to return a tuple:

    result, line = self.prev(x)
    

    but AddOne() returns just the one integer:

    class AddOne(Function):
        name = 'addone'
    
        def run(self, x):
            return x + 1
    

    so you get an exception:

    >>> component.getMultiAdapter((AddOne(), MulTwo()))(11)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 37, in __call__
      File "<string>", line 32, in bind
    TypeError: 'int' object is not iterable
    

    Perhaps you wanted each Function() to return the name as well as the result:

    class Function(object):
        name = ''
        interface.implements(IFunction)
    
        def __call__(self, *args, **kw):
            return self.run(*args, **kw), self.name
    
        def run(self, *args, **kw):
            raise NotImplementedError
    

    but then calling self.next() fails because it doesn't accept the extra log argument passed in.