lazr.delegates

The lazr.delegates package makes it easy to write objects that delegate behavior to another object. The new object adds some property or behavior on to the other object, while still providing the underlying interface, and delegating behavior.

Usage

The @delegate_to class decorator makes a class implement zero or more interfaces by delegating the implementation to another object. In the case of a class providing an adapter, that object will be the context, but it can really be any object stored in an attribute. So while the interfaces use an inheritance mechanism, the classes use a composition mechanism.

For example, we can define two interfaces IFoo0 and IFoo1 where the latter inherits from the former. The first interface defines an attribute.

>>> from zope.interface import Interface, Attribute
>>> class IFoo0(Interface):
...     one = Attribute('attribute in IFoo0')

The second (i.e. derived) interface defines a method and an attribute.

>>> class IFoo1(IFoo0):
...     def bar():
...         """A method in IFoo1"""
...     baz = Attribute('attribute in IFoo1')

We also define two classes that mirror the interfaces, and do something interesting.

>>> class Foo0:
...     one = 'one'

>>> class Foo1(Foo0):
...     def bar(self):
...         return 'bar'
...     baz = 'I am baz'

Finally, to tie everything together, we can define a class that delegates the implementation of IFoo1 to an attribute on the instance. By default, self.context is used as the delegate attribute.

>>> from lazr.delegates import delegate_to
>>> @delegate_to(IFoo1)
... class SomeClass:
...     def __init__(self, context):
...         self.context = context

When the class doing the delegation is instantiated, an instance of the class implementing the interface is passed in.

>>> delegate = Foo1()
>>> s = SomeClass(delegate)

Now, the bar() method comes from Foo1.

>>> print(s.bar())
bar

The baz attribute also comes from Foo1.

>>> print(s.baz)
I am baz

The one attribute comes from Foo0.

>>> print(s.one)
one

Even though the interface of SomeClass is defined through the delegate, the interface is still provided by the instance.

>>> IFoo1.providedBy(s)
True

Custom context

The @delegate_to decorator takes an optional keyword argument to customize the attribute containing the object to delegate to.

>>> @delegate_to(IFoo1, context='myfoo')
... class SomeOtherClass:
...     def __init__(self, foo):
...         self.myfoo = foo

The attributes and methods are still delegated correctly.

>>> s = SomeOtherClass(delegate)
>>> print(s.bar())
bar
>>> print(s.baz)
I am baz

Multiple interfaces

The @delegate_to decorator accepts more than one interface. Note however, that the context attribute must implement all of the named interfaces.

>>> class IFoo2(Interface):
...     another = Attribute('another attribute')

Here is a class that implements the interface. It inherits from the implementation class that provides the IFoo0 interface. Thus does this class implement both interfaces.

>>> class Foo2(Foo0):
...     another = 'I am another foo'

Again, we tie it all together.

>>> @delegate_to(IFoo0, IFoo2)
... class SomeOtherClass:
...     def __init__(self, context):
...         self.context = context

Now, the instance of this class has all the expected attributes, and provides the expected interfaces.

>>> s = SomeOtherClass(Foo2())
>>> print(s.another)
I am another foo
>>> IFoo0.providedBy(s)
True
>>> IFoo2.providedBy(s)
True