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)