Adding implements decorator default tip
authorMike Crute <mcrute@gmail.com>
Wed Jun 09 15:12:43 2010 -0400 (23 months ago)
changeset 1877fe8d4bcbd0
parent 17 2984955fe661
Adding implements decorator
python/implements.py
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/python/implements.py	Wed Jun 09 15:12:43 2010 -0400
     1.3 @@ -0,0 +1,81 @@
     1.4 +# vim: set filencoding=utf8
     1.5 +"""
     1.6 +Implements Decorator
     1.7 +
     1.8 +@author: Mike Crute (mcrute@gmail.com)
     1.9 +@organization: SoftGroup Interactive, Inc.
    1.10 +@date: May 02, 2010
    1.11 +
    1.12 +Used by:
    1.13 +    foundry
    1.14 +"""
    1.15 +
    1.16 +def implements(interface, debug=False):
    1.17 +    """
    1.18 +    Verify that a class conforms to a specified interface.
    1.19 +    This decorator is not perfect, for example it can not
    1.20 +    check exceptions or return values. But it does ensure
    1.21 +    that all public methods exist and their arguments
    1.22 +    conform to the interface.
    1.23 +
    1.24 +    The debug flag allows overriding checking of the runtime
    1.25 +    flag for testing purposes, it should never be set in
    1.26 +    production code.
    1.27 +
    1.28 +    NOTE: This decorator does nothing if -d is not passed
    1.29 +    to the Python runtime.
    1.30 +    """
    1.31 +    import sys
    1.32 +    if not sys.flags.debug and not debug:
    1.33 +        return lambda func: func
    1.34 +
    1.35 +    # Defer this import until we know we're supposed to run
    1.36 +    import inspect
    1.37 +
    1.38 +    def get_filtered_members(item):
    1.39 +        "Gets non-private or non-protected symbols."
    1.40 +        return dict([(key, value) for key, value in inspect.getmembers(item)
    1.41 +                if not key.startswith('_')])
    1.42 +
    1.43 +    def build_contract(item):
    1.44 +        """
    1.45 +        Builds a function contract string. The contract
    1.46 +        string will ignore the name of positional params
    1.47 +        but will consider the name of keyword arguments.
    1.48 +        """
    1.49 +        argspec = inspect.getargspec(item)
    1.50 +
    1.51 +        if argspec.defaults:
    1.52 +            num_keywords = len(argspec.defaults)
    1.53 +            args = ['_'] * (len(argspec.args) - num_keywords)
    1.54 +            args.extend(argspec.args[num_keywords-1:])
    1.55 +        else:
    1.56 +            args = ['_'] * len(argspec.args)
    1.57 +
    1.58 +        if argspec.varargs:
    1.59 +            args.append('*args')
    1.60 +
    1.61 +        if argspec.keywords:
    1.62 +            args.append('**kwargs')
    1.63 +
    1.64 +        return ', '.join(args)
    1.65 +
    1.66 +    def tester(klass):
    1.67 +        "Verifies conformance to the interface."
    1.68 +        interface_elements = get_filtered_members(interface)
    1.69 +        class_elements = get_filtered_members(klass)
    1.70 +
    1.71 +        for key, value in interface_elements.items():
    1.72 +            assert key in class_elements, \
    1.73 +                    "{0!r} is required but missing.".format(key)
    1.74 +
    1.75 +            if inspect.isfunction(value) or inspect.ismethod(value):
    1.76 +                contract = build_contract(value)
    1.77 +                implementation = build_contract(class_elements[key])
    1.78 +
    1.79 +                assert implementation == contract, \
    1.80 +                        "{0!r} doesn't conform to interface.".format(key)
    1.81 +
    1.82 +        return klass
    1.83 +
    1.84 +    return tester