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