Skip to content

Instantly share code, notes, and snippets.

@ianschenck ianschenck/example.py
Last active Aug 29, 2015

Embed
What would you like to do?
interfaces
import interface
class IFoo(interface.Interface):
def foo(self):
"""foo this object."""
class IBar(interface.Interface):
"""IBar provides a `bar` method."""
def bar(self, a, b=None):
"""bar this object."""
# If the interface isn't implemented, throws an exception at module initialization time.
@interface.implements(IFoo, IBar)
class FooBar(object):
def foo(self):
# do something
pass
def bar(self, a, b=None, c=None):
# do something else
pass
assert issubclass(FooBar, IFoo) # True
assert issubclass(FooBar, IBar) # True
# Instance checks.
assert isinstance(FooBar(), IFoo) # True
assert isinstance(FooBar(), IBar) # True
# You can combine interfaces.
class IFooBar(IFoo, IBar):
pass
interface.implements(IFooBar)(FooBar)
# And it works with properties.
class IBaz(interface.Interface):
name = property()
value = property()
@interface.implements(IBaz)
class Baz(object):
name = "Baz"
@property
def value(self):
return 42
# But you don't *need* implements. And interfaces don't need to be
# explicit.
class ReadlineCloser(interface.Interface):
def readline(self, size=0):
"""Read up to `size` bytes or until a newline."""
def close(self):
"""Close this file."""
with open('somefile.txt') as f:
assert isinstance(f, ReadlineCloser)
import abc
import inspect
__all__ = ['Interface', 'implements']
class Interface(object):
"""Interface is the root of all interfaces.
To declare an interface, sub-class Interface and define
placeholder methods and properties. Any class properly
implementing the interface will return true for `issubclass`, and
objects implementing the interface will return True for
`isinstance`. An implementing class should not sub-class an
interface. See the `@implements` decorator for interface checking
at module initialization.
"""
__metaclass__ = abc.ABCMeta
@classmethod
def __subclasshook__(cls, C):
errors = check_implemented(C, cls)
return len(errors) == 0 or NotImplemented
IGNORED = set(x[0] for x in inspect.getmembers(Interface))
def implements(*interfaces):
"""Check if the decorated class implements all `interfaces`.
:type interfaces: list[Interface]
:raises NotImplementedError: if an interface is not met.
"""
def inner(cls):
for interface in interfaces:
if not issubclass(cls, interface):
errors = check_implemented(cls, interface)
raise NotImplementedError("\n".join(errors))
return cls
return inner
def check_implemented(cls, interface):
"""Check if a class implements a given interface.
:type cls: type
:type interface: type
"""
def _methods(c):
return (inspect.ismethod(c)
or inspect.isfunction(c)
or inspect.ismethoddescriptor(c))
def _props(c):
return not _methods(c)
interface_funcs = dict(inspect.getmembers(interface, _methods))
cls_funcs = dict(inspect.getmembers(cls, _methods))
errors = []
for name, func in interface_funcs.items():
if name in IGNORED:
continue
if name not in cls_funcs:
errors.append("%s method not implemented" % name)
continue
error = func_satifies(cls_funcs[name], func)
if error is not None:
errors.append(error)
continue
# Check properties
interface_props = set(x[0] for x in inspect.getmembers(interface, _props))
cls_props = set(x[0] for x in inspect.getmembers(cls, _props))
unimplemented_props = interface_props - IGNORED - cls_props
for prop in unimplemented_props:
errors.append("%s property not found" % prop)
return errors
def func_satifies(cls_func, iface_func):
"""Determines if method `cls_func` satisfies `interface_func`.
This is not a symmetric comparison, since we have to accept the
implications of variadic functions (via `*args` and `*kwargs`) and
additional arguments on an implementation that may be provided
with defaults.
"""
# It is impossible to inspect built-in methods, so be generous
# and assume they fit.
if inspect.ismethoddescriptor(cls_func) and inspect.isroutine(cls_func):
return
cls_func_spec = inspect.getargspec(cls_func)
iface_func_spec = inspect.getargspec(iface_func)
# If an interface requires variadic, then the implementation must.
if (iface_func_spec.varargs is not None) and (cls_func_spec.varargs is None):
return "%s requires implementation to accept *args" % (
iface_func.func_name)
if (iface_func_spec.keywords is not None) and (cls_func_spec.keywords is None):
return "%s requires implementation to accept **kwargs" % (
iface_func.func_name)
# Positional, required arguments must match only in number. Note:
# that's not actually true, these parameters could be referenced
# by name, but it's much more common for them to be used
# positionally. If the implementor of an interface wants to really
# do it right, they should keep the names identical as well.
iface_required = len(
iface_func_spec.args[0: (
len(iface_func_spec.args) - len(iface_func_spec.defaults or []))])
cls_required = len(
cls_func_spec.args[0: (
len(cls_func_spec.args) - len(cls_func_spec.defaults or []))])
if iface_required != cls_required:
return "%s requires %d positional arguments, %d given" % (
iface_func.func_name, iface_required, cls_required)
# Arguments that are optional always follow positional
# arguments. The only constraint here is that the implementation
# duplicates these arguments in the same order (but may add more
# after).
iface_optional = tuple(iface_func_spec.args[iface_required:])
cls_optional = tuple(cls_func_spec.args[cls_required:])
if iface_optional != cls_optional[:len(iface_optional)]:
return "%s requires optional arguments %s" % (
iface_func.func_name, iface_optional)
return None
it
has
lines
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.