Source

intfprgm / intfprgm / __init__.py

Full commit
'''Set of (class) decorators to help with interface programming.

Examples::

    @concrete
    class subclass(parent):
        ...

Released to the public domain.

Nam T. Nguyen, 2012

'''

import dis
import types


def is_abstract(func):
    '''Check if a function is un-implemented.

    An un-implemented method is defined similarly to::

        def method(...):
            raise NotImplementedError()

    This, when translated to bytecode, looks like::

        LOAD_GLOBAL       0 (NotImplementedError)
        CALL_FUNCTION     1
        RAISE_VARARGS     1
        ...

    Or when ``raise NotImplementedError``::

        LOAD_GLOBAL       0 (NotImplementedError)
        RAISE_VARARGS     1
        ...

    The check here is for such patterns.

    Args:

        func (function): A function object

    Return:

        ``True`` if this function has the mentioned pattern,
        ``False`` otherwise

    '''

    # check if first name is NotImplementedError
    if len(func.func_code.co_names) < 1 or \
            func.func_code.co_names[0] != 'NotImplementedError':
        return False
    # and RAISE_VARARGS somewhere after that
    for position in (3, 6):
        if len(func.func_code.co_code) < position:
            continue
        opcode = ord(func.func_code.co_code[position])
        if dis.opname[opcode] == 'RAISE_VARARGS':
            return True
    return False


def get_functions(clz, filter_=lambda x: True):
    '''Return all functions and methods of ``clz`` that satisfy ``filter_``.

    Args:

        clz (class): A class object
        filter_: A filter function. It accepts a function object and return
            a boolean.

    Returns:

        List of function and method objects

    '''

    funcs = []

    for name in dir(clz):
        func = getattr(clz, name)
        if type(func) in (types.FunctionType, types.MethodType) and \
                filter_(func):
            funcs.append(func)

    return funcs


def get_implemented_functions(clz):
    '''Return all implemented (not abstract) functions and methods of ``clz``.

    See func:`get_functions` and func:`is_abstract`.

    Args:

        clz (class): A class object

    Returns:

        List of implemented function and method objects in ``clz``

    '''

    return get_functions(clz, lambda f: not is_abstract(f))


def get_unimplemented_functions(clz):
    '''Return all unimplemented (abstract) functions and methods of ``clz``.

    See func:`get_functions` and func:`is_abstract`.

    Args:

        clz (class): A class object

    Returns:

        List of implemented function and method objects in ``clz``

    '''

    return get_functions(clz, lambda f: is_abstract(f))


def __raise_constructor(s, *args, **kw_args):
    raise SyntaxError(s.__class__.__name__ + ' cannot be instantiated.')


def __raise(*args, **kw_args):
    raise NotImplementedError()


def abstract(orig_class):
    '''A decorator to ensure that a class cannot be instantiated and at least
    one of its methods is unimplemented.

    If this decorator is applied on a function, it will replace that function
    with a stub that raises ``NotImplementedError``.

    '''

    if type(orig_class) is not types.ClassType:
        return __raise

    funcs = get_unimplemented_functions(orig_class)
    if not funcs:
        raise SyntaxError('%s does not have any unimplemented method.' %
            orig_class.__name__)

    orig_class.__init__ = __raise_constructor
    return orig_class


def interface(orig_class):
    '''A decorator to ensure that an interface cannot be instantiated and all
    its methods are abstract.

    '''

    funcs = get_implemented_functions(orig_class)
    if funcs:
        raise SyntaxError('Interface %s cannot implement %s.' % (
                orig_class.__name__, ', '.join(f.func_name for f in funcs)))

    orig_class.__init__ = __raise_constructor
    return orig_class


def concrete(orig_class):
    '''A decorator to ensure that a concrete class has no un-implemented
    methods.

    '''

    funcs = get_unimplemented_functions(orig_class)
    if funcs:
        raise SyntaxError('Concrete class %s must implement %s.' % (
                orig_class.__name__, ', '.join(f.func_name for f in funcs)))

    return orig_class