[Python] Sphinx Compatible Forwarding Patterns in Python

We develop a Python example that showcases the forwarding pattern while handling docstrings in a Sphinx compatible way. We maintain this compatibility in two ways: first, using a metaclass, and second, using decorators. Along the way, we discover a few things about binding instance and class attributes.

A Simple Example

We define two classes: a SimpleWrapper class that forwards to a Wrappee class.

SimpleWrapper

class SimpleWrapper(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee

    def foo(self, val):
        """
        Forward to wrappee.foo()
        """
        return self.wrappee(val)

Wrappee

class Wrappee(object):
    forwarded_methods = ('foo',)

    def __init__(self, offset):
        self.offset = float(offset)

    def foo(self, val):
        """
        Args:
            val(float):     the input

        Returns:
            float:          val, plus offset
        """

        return val + self.offset

If it was just a single method, e.g. SimpleWrapper.foo(), something like the above would probably be sufficient.

However, suppose we have multiple methods to forward. In this scenario we might prefer
  • to streamline the definition of the Wrapper, and
  • to have Sphinx pick up the docstrings from the Wrappee

Streamlining Wrapper

We propose that the definition of Wrapper should not include repetitive, boilerplate forwarding code. On the other hand, we want Sphinx to pick up the forwarded methods.

To achieve these two goals, we use a metaclass to build the Wrapper type and loop over the forwarded_methods assigning the functions to the new class. When Sphinx examines a Wrapper class, it will extract the boilerplate forwarding code because the metaclass added definitions for each of the forwarding methods.

WrapperMetaclass

class WrapperMetaclass(type):
    def __init__(cls, name, bases, dct):
        super(WrapperMetaclass, cls).__init__(name, bases, dct)
        for method_name in Wrappee.forwarded_methods:
            setattr(cls, method_name, Wrappee.__dict__[method_name])

Wrapper

There is some small additional complexity in Wrapper wherein we must modify the magic function __getattribute__() to return foo() as a bound method to the wrappee instance.

In this case, overriding __getattr__() will not suffice as the fallback behavior of __getattr__() will create the wrong binding, a binding to self.

class Wrapper(object):
    __metaclass__ = WrapperMetaclass

    def __init__(self, wrappee):
        self.wrappee = wrappee

    def bar(self):
        """
        A placeholder to compare against the 'foo' method installed 
        by the metaclass
        """
        pass

    def __getattribute__(self, attr):
        if attr in Wrappee.forwarded_methods:
            f = type(self).__dict__[attr]
            return f.__get__(self.wrappee, Wrappee)
        else:
            return super(Wrapper, self).__getattribute__(attr)

Decorators

Alternatively, suppose that we prefer self-documenting boilerplate code. In this case, only the docstrings must be updated, and we can do this without recourse to metaclasses.

Decorated Wrapper

We use a parameterized decorator which, itself, returns a decorator.

def forwarded(cls):
    def decorator(f):
        attr        = getattr(cls, f.__name__)
        f.__doc__   = attr.__doc__
        return f
    return decorator

Application of the parameterized decorator is apparent: one simply passes the Wrappee class.

class DecoratedWrapper(object):

    def __init__(self, wrappee):
        self.wrappee = wrappee

    @forwarded(Wrappee)
    def foo(self, val):
        self.wrappee.foo(val)

Results

It is interesting to consider the various ways of accessing a function and the implications on the respective calling contexts.

In [1]: from Forwarding import *

In [2]: wrappee = Wrappee(1)

In [3]: wrapper = Wrapper(wrappee)

In [4]: type(wrapper).__dict__['foo']
Out[4]: <function Forwarding.foo>

In [5]: type(wrapper).__dict__['bar']
Out[5]: <function Forwarding.bar>

In [6]: getattr(wrapper, 'foo')
Out[6]: <bound method Wrappee.foo of <Forwarding.Wrappee object at 0x7f6c0f5ba3d0>>

In [7]: getattr(wrapper, 'bar')
Out[7]: <bound method Wrapper.bar of <Forwarding.Wrapper object at 0x7f6c0f5ba410>>

Note that the class __dict__ defines both foo() and bar() as functions. However, getattr() binds the former to the Wrappee instance and the latter to the Wrapper instance. This is the behavior we wanted.

Moreover, the docstring is preserved from the Wrappee class.

In [8]: print wrapper.foo.__doc__

        Args:
            val(float):     the input

        Returns:
            float:          val, plus offset

Autoclass Behavior

For both the metaclass and decorator appraoches, when we use the Sphinx autoclass directive, we get exactly what we expect.

class Forwarding.Wrapper(wrappee)
bar()

A placeholder to compare against the ‘foo’ method installed by the metaclass

foo(val)
Parameters:val (float) – the input
Returns:float – val, plus offset

The decorated method DecoratedWrapper.foo() has the same docstring and, accordingly, generates the same Sphinx documentation.

class Forwarding.DecoratedWrapper(wrappee)
foo(val)
Parameters:val (float) – the input
Returns:float – val, plus offset