1.1.4 Defining and Subclassing Interfaces
Description: Defining and Subclassing Interfaces
Keywords: ref

##
1.1.4 Defining and Subclassing Interfaces

The easiest way to define an interface with the `protocols` package is to subclass `protocols.Interface`. `Interface` does not supply any data or methods of its own, so you are free to define whatever you need. There are two common styles of defining interfaces, illustrated below:

    from protocols import Interface, AbstractBase

    # "Pure" interface style

    class IReadMapping(Interface):

        """A getitem-only mapping"""

        def __getitem__(key):
            """Return value for key"""

    # Abstract Base Class (ABC) style

    class AbstractMapping(AbstractBase):

        """A getitem-only mapping"""

        def __getitem__(self,key):
            """Return value for key"""
            raise NotImplementedError

The ``pure'' style emphasizes the interface as seen by the caller, and is not intended to be subclassed for implementation. Notice that the `self` parameter is not included in its method definitions, because `self` is not supplied when calling the methods. The ``ABC'' style, on the other hand, emphasizes implementation, as it is intended to be subclassed for that purpose. Therefore, it includes method bodies, even for abstract methods. Each style has different uses: ``ABC'' is a popular rapid development style, while the ``pure'' approach has some distinct documentation advantages.

`protocols.AbstractBase` may be used as a base class for either style, but `protocols.Interface` is only usable for the "pure" interface style, as it supports the convenience adaptation API (see section 1.1.3).

(Note: both base classes use an explicit metaclass, so keep in mind that if you want to subclass an abstract base for implementation using a different metaclass, you may need to create a third metaclass that combines `protocols.AbstractBaseMeta` with your desired metaclass.)

Subclassing a subclass of `Interface` (or `AbstractBase`) creates a new interface (or ABC) that implies the first interface (or ABC). This means that any object that supports the second interface (or ABC), is considered to implicitly support the first interface (or ABC). For example:

    class IReadWriteMapping(IReadMapping):

        """Mapping with getitem and setitem only"""

        def __setitem__(key,value):
            """Store value for key, return None"""

The `IReadWriteMapping` interface implies the `IReadMapping` interface. Therefore, any object that supports `IReadWriteMapping` is understood to also support the `IReadMapping` interface. The reverse, however, is not true.

Inheritance is only one way to declare that one interface implies another, however, and its uses are limited. Let's say for example, that some package `A` supplies objects that support `IReadWriteMapping`, while package `B` needs objects that support `IReadMapping`. But each package declared its own interface, neither inheriting from the other.

As developers reading the documentation of these interfaces, it is obvious to us that `IReadWriteMapping` implies `IReadMapping`, because we understand what they do. But there is no way for Python to know this, unless we explicitly state it, like this:

    import protocols
    from A import IReadWriteMapping
    from B import IReadMapping

    protocols.declareAdapter(
        protocols.NO_ADAPTER_NEEDED,
        provides = [IReadMapping],
        forProtocols = [IReadWriteMapping]
    )

In the above example, we use the `protocols` declaration API to say that no adapter is needed to support the `B.IReadMapping` interface for objects that already support the `A.IReadWriteMapping` interface.

At this point, if we supply an object that supports `IReadWriteMapping`, to a function that expects an `IReadMapping`, it should work, as long as we call `adapt(ob,IReadMapping)` (or `IReadMapping(ob)`) first, or the code we're calling does so.

There are still other ways to declare that one interface implies another. For example, if the author of our example package `B` knew about package A and its `IReadWriteMapping` interface, he or she might have defined `IReadMapping` this way:

    import protocols
    from protocols import Interface

    from A import IReadWriteMapping

    class IReadMapping(Interface):

        """A getitem-only mapping"""

        protocols.advise(
            protocolIsSubsetOf = [IReadWriteMapping]
        )

        def __getitem__(key):
            """Return value for key"""

This is syntax sugar for creating the interface first, and then using `protocols.declareAdapter(NO_ADAPTER_NEEDED)`. Of course, you can only use this approach if you are the author of the interface! Otherwise, you must use `declareAdapter()` after the fact, as in the previous example.

In later sections, we will begin looking at the `protocols` declaration APIs - like `declareAdapter()` and `advise()` - in more detail. But first, we must look briefly at the interfaces that the `protocols` package expects from the protocols, adapters, and other objects supplied as parameters to the declaration API.