interface

interface is a library for declaring interfaces and for statically asserting that classes implement those interfaces. It provides stricter semantics than Python’s built-in abc module, and it aims to produce exceptionally useful error messages when interfaces aren’t satisfied.

interface supports Python 2.7 and Python 3.4+.

Quick Start

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass


class MyClass(implements(MyInterface)):

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

    def method2(self, x, y):
        return x + y

Why Interfaces?

What is an Interface?

In software generally, an interface is a description of the capabilities provided by a unit of code. In object-oriented languages like Python, interfaces are often defined by a collection of method signatures which must be provided by a class.

In interface, an interface is a subclass of interface.Interface that defines one or more methods with empty bodies. For example, the interface definition for a simple Key-Value Store might look like this:

class KeyValueStore(interface.Interface):

    def get(self, key):
        """Get the value for ``key``.
        """

    def set(self, key, value):
        """Set the value for ``key`` to ``value``.
        """

    def delete(self, key):
        """Delete the value for ``key``.
        """

Why Are Interfaces Useful?

Interfaces are useful for specifying the contract between two units of code. By marking that a type implements an interface, we give a programatically-verifiable guarantee that the implementation provides the methods specified by the interface signature. That guarantee makes it easier to write code that can work with any implementation of an interface, and it also serves as a form of documentation.

Using interface

Declaring Interfaces

An interface describes a collection of methods and properties that should be provided by implementors.

We create an interface by subclassing from interface.Interface and defining stubs for methods that should be part of the interface:

class MyInterface(interface.Interface):

    def method1(self, x, y, z):
        pass

    def method2(self):
        pass

Implementing Interfaces

To declare that a class implements an interface I, that class should subclass from implements(I):

class MyClass(interface.implements(MyInterface)):

    def method1(self, x, y, z):
        return x + y + z

    def method2(self):
        return "foo"

A class can implement more than one interface:

class MathStudent(Interface):

    def argue(self, topic):
        pass

    def calculate(self, x, y):
        pass


class PhilosophyStudent(Interface):

    def argue(self, topic):
        pass

    def pontificate(self):
        pass


class LiberalArtsStudent(implements(MathStudent, PhilosophyStudent)):

    def argue(self, topic):
        print(topic, "is", ["good", "bad"][random.choice([0, 1])])

    def calculate(self, x, y):
        return x + y

    def pontificate(self):
        print("I think what Wittgenstein was **really** saying is...")

Notice that interfaces can have intersecting methods as long as their signatures match.

Properties

Interfaces can declare non-method attributes that should be provided by implementations using property:

class MyInterface(interface.Interface):

    @property
    def my_property(self):
        pass

Implementations are required to provide a property with the same name.

class MyClass(interface.implements(MyInterface)):

    @property
    def my_property(self):
        return 3

Default Implementations

Sometimes we have a method that should be part of an interface, but which can be implemented in terms of other interface methods. When this happens, you can use interface.default to provide a default implementation of a method.

class ReadOnlyMapping(interface.Interface):

    def get(self, key):
        pass

    def keys(self):
        pass

    @interface.default
    def get_all(self):
        out = {}
        for k in self.keys():
            out[k] = self.get(k)
        return out

Implementors are not required to implement methods with defaults:

class MyReadOnlyMapping(interface.implements(ReadOnlyMapping)):
    def __init__(self, data):
        self._data = data

    def get(self, key):
        return self._data[key]

    def keys(self):
        return self._data.keys()

    # get_all(self) will automatically be copied from the interface default.

Default implementations should always be implemented in terms of other interface methods. This ensures that the default is valid for any implementation of the interface.

In Python 3, default will show a warning if a default implementation uses non-interface members of an object:

class ReadOnlyMapping(interface.Interface):

    def get(self, key):
        pass

    def keys(self):
        pass

    @interface.default
    def get_all(self):
        # This is supposed to be a default implementation for **any**
        # ReadOnlyMapping, but this implementation assumes that 'self' has
        # an _data attribute that isn't part of the interface!
        return self._data.keys()

Running the above example displays a warning about the default implementation of get_all:

$ python example.py
example.py:4: UnsafeDefault: Default for ReadOnlyMapping.get_all uses non-interface attributes.

The following attributes are used but are not part of the interface:
  - _data

Consider changing ReadOnlyMapping.get_all or making these attributes part of ReadOnlyMapping.
   class ReadOnlyMapping(interface.Interface):
Default Properties

default and property can be used together to create default properties:

class ReadOnlyMappingWithSpecialKey(interface.Interface):

    def get(self, key):
        pass

    @interface.default
    @property
    def special_key(self):
        return self.get('special_key')

Note

The order of decorators in the example above is important: @default must go above @property.

Interface Subclassing

Interfaces can inherit requirements from other interfaces via subclassing. For example, if we want to create interfaces for read-write and read-only mappings, we could do so as follows:

class ReadOnlyMapping(interface.Interface):
    def get(self, key):
        pass

    def keys(self):
        pass


class ReadWriteMapping(ReadOnlyMapping):

    def set(self, key, value):
        pass

    def delete(self, key):
        pass

An interface that subclasses from another interface inherits all the function signature requirements from its parent interface. In the example above, a class implementing ReadWriteMapping would have to implement get, keys, set, and delete.

Warning

Subclassing from an interface is not the same as implementing an interface. Subclassing from an interface creates a new interface that adds additional methods to the parent interface. Implementing an interface creates a new class whose method signatures must be compatible with the interface being implemented.

Error Detection

interface aims to provide clear, detailed, and complete error messages when an interface definition isn’t satisfied.

An implementation can fail to implement an interface in a variety of ways:

Missing Methods

Implementations must define all the methods declared in an interface.

from interface import implements, Interface

class MathStudent(Interface):

    def argue(self, topic):
        pass

    def prove(self, theorem):
        pass

    def calculate(self, x, y, z):
        pass


class Freshman(implements(MathStudent)):

    def argue(self, topic):
        print(topic, "is terrible!")

The above example produces the following error message:

Traceback (most recent call last):
  ...
interface.interface.InvalidImplementation:
class Freshman failed to implement interface MathStudent:

The following methods of MathStudent were not implemented:
  - calculate(self, x, y, z)
  - prove(self, theorem)

Methods with Incompatible Signatures

Implementations must define interface methods with compatible signatures:

from interface import implements, Interface

class MathStudent(Interface):

    def argue(self, topic):
        pass

    def prove(self, theorem):
        pass

    def calculate(self, x, y, z):
        pass


class SloppyMathStudent(implements(MathStudent)):

    def argue(self, topic):
        print(topic, "is terrible!")

    def prove(self, lemma):
        print("That's almost a theorem, right?")

    def calculate(self, x, y):
        return x + y
Traceback (most recent call last):
  ...
interface.interface.InvalidImplementation:
class SloppyMathStudent failed to implement interface MathStudent:

The following methods of MathStudent were implemented with invalid signatures:
  - calculate(self, x, y) != calculate(self, x, y, z)
  - prove(self, lemma) != prove(self, theorem)

Method/Property Mismatches

If an interface defines an attribute as a property, the corresponding implementation attribute must also be a property:

class Philosopher(Interface):
    @property
    def favorite_philosopher(self):
        pass

class AnalyticPhilosopher(implements(Philosopher)):

    def favorite_philosopher(self):  # oops, should have been a property!
        return "Ludwig Wittgenstein"
Traceback (most recent call last):
  ...
interface.interface.InvalidImplementation:
class AnalyticPhilosopher failed to implement interface Philosopher:

The following methods of Philosopher were implemented with incorrect types:
  - favorite_philosopher: 'function' is not a subtype of expected type 'property'

interface vs. abc

The Python standard library abc (Abstract Base Class) module is often used to define and verify interfaces.

interface differs from Python’s abc module in two important ways:

  1. Interface requirements are checked at class creation time, rather than at instance creation time. This means that interface can tell you if a class fails to implement an interface even if you never create any instances of that class.

  2. interface requires that method signatures of implementations are compatible with signatures declared in interfaces. For example, the following code using abc does not produce an error:

    >>> from abc import ABCMeta, abstractmethod
    >>> class Base(metaclass=ABCMeta):
    ...
    ...     @abstractmethod
    ...     def method(self, a, b):
    ...         pass
    ...
    >>> class Implementation(MyABC):
    ...
    ...     def method(self):  # Missing a and b parameters.
    ...         return "This shouldn't work."
    ...
    >>> impl = Implementation()
    >>>
    

    The equivalent code using interface produces an error telling us that the implementation method doesn’t match the interface:

    >>> from interface import implements, Interface
    >>> class I(Interface):
    ...     def method(self, a, b):
    ...         pass
    ...
    >>> class C(implements(I)):
    ...     def method(self):
    ...         return "This shouldn't work"
    ...
    TypeError:
    class C failed to implement interface I:
    
    The following methods were implemented but had invalid signatures:
      - method(self) != method(self, a, b)
    

Examples

Suppose we’re writing an application that needs to load and save user preferences.

We expect that we may want to manage preferences differently in different contexts, so we separate out our preference-management code into its own class, and our main application looks like this:

class MyApplication:

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

    def save_resolution(self, resolution):
        self.preferences.set('resolution', resolution)

    def get_resolution(self):
        return self.preferences.get('resolution')

    def save_volume(self, volume):
        self.preferences.set('volume', volume)

    def get_volume(self):
        return self.preferences.get('volume')

    ...

When we ship our application to users, we store preferences as a json file on the local hard drive:

class JSONFileStore:

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

    def get(self, key):
        with open(self.path) as f:
            return json.load(f)[key]

    def set(self, key, value):
        with open(self.path) as f:
            data = json.load(f)

        data[key] = value

        with open(self.path, 'w') as f:
            json.dump(data, f)

In testing, however, we want to use a simpler key-value store that stores preferences in memory:

class InMemoryStore:

    def __init__(self):
        self.data = {}

    def get(self, key):
        return self.data[key]

    def set(self, key, value):
        self.data[key] = value

Later on, we add a cloud-sync feature to our application, so we add a third implementation that stores user preferences in a database:

class SQLStore:
    def __init__(self, user, connection):
        self.user = user
        self.connection = connection

    def get(self, key):
        self.connection.execute(
            "SELECT * FROM preferences where key=%s and user=%s",
            [self.key, self.user],
        )

    def set(self, key, value):
        self.connection.execute(
            "INSERT INTO preferences VALUES (%s, %s, %s)",
            [self.user, self.key, value],
)

As the number of KeyValueStore implementations grows, it becomes more and more difficult for us to make changes to our application. If we add a new method to any of the key-value stores, we can’t use it in the application unless we add the same method to the other implementations, but in a large codebase we might not even know what other implementations exist!

By declaring KeyValueStore as an Interface we can get interface to help us keep our implementations in sync:

class KeyValueStore(interface.Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

class JSONFileStore(implements(KeyValueStore)):
    ...

class InMemoryStore(implements(KeyValueStore)):
    ...

class SQLStore(implements(KeyValueStore)):
    ...

Now, if we add a method to the interface without adding it to an implementation, we’ll get a helpful error at class definition time.

For example, if we add a get_default method to the interface but forget to add it to SQLStore:

class KeyValueStore(interface.Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    def get_default(self, key, default):
        pass


class SQLStore(interface.implements(KeyValueStore)):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    # We forgot to define get_default!

We get the following error at import time:

$ python example.py
Traceback (most recent call last):
  File "example.py", line 16, in <module>
    class SQLStore(interface.implements(KeyValueStore)):
  File "/home/ssanderson/projects/interface/interface/interface.py", line 394, in __new__
    raise errors[0]
  File "/home/ssanderson/projects/interface/interface/interface.py", line 376, in __new__
    defaults_from_iface = iface.verify(newtype)
  File "/home/ssanderson/projects/interface/interface/interface.py", line 191, in verify
    raise self._invalid_implementation(type_, missing, mistyped, mismatched)
interface.interface.InvalidImplementation:
class SQLStore failed to implement interface KeyValueStore:

The following methods of KeyValueStore were not implemented:
  - get_default(self, key, default)

API Reference

class interface.Interface[source]

Base class for interface definitions.

An interface defines a set of methods and/or attributes that should be provided by one or more classes, which are called “implementations” of the interface.

Classes can declare that they implement an interface I by subclassing implements(I). During class construction, the base class generated by implements(I) will verify that the new class correctly implements all the methods required by I.

Interfaces cannot be instantiated.

Examples

Defining an Interface:

class KeyValueStore(Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    def delete(self, key):
        pass

Implementing an Interface:

class InMemoryKeyValueStore(implements(KeyValueStore)):

    def __init__(self):
        self.data = {}

    def get(self, key):
        return self.data[key]

    def set(self, key, value):
        self.data[key] = value

    def delete(self, key):
        del self.data[key]

See also

implements()

classmethod from_class(existing_class, subset=None, name=None)[source]

Create an interface from an existing class.

Parameters:
  • existing_class (type) – The type from which to extract an interface.
  • subset (list[str], optional) – List of methods that should be included in the interface. Default is to use all attributes not defined in an empty class.
  • name (str, optional) – Name of the generated interface. Default is existing_class.__name__ + 'Interface'.
Returns:

interface – A new interface class with stubs generated from existing_class.

Return type:

type

interface.implements(*interfaces)

Make a base for a class that implements one or more interfaces.

Parameters:*interfaces (tuple) – One or more subclasses of Interface.
Returns:base – A type validating that subclasses must implement all interface methods of types in interfaces.
Return type:type

Examples

Implementing an Interface:

class MyInterface(Interface):
     def method1(self, x):
         pass

     def method2(self, y):
         pass

class MyImplementation(implements(MyInterface)):
     def method1(self, x):
         return x + 1

     def method2(self, y):
         return y * 2

Implementing Multiple Interfaces:

class I1(Interface):
    def method1(self, x):
        pass

class I2(Interface):
    def method2(self, y):
        pass

class ImplBoth(implements(I1, I2)):
    def method1(self, x):
        return x + 1

    def method2(self, y):
        return y * 2
class interface.default(implementation)[source]

Default implementation of a function in terms of interface methods.

Indices and tables