1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# vim: set filencoding=utf8
"""
Random Stuff
@author: Mike Crute (mcrute@gmail.com)
@organization: SoftGroup Interactive, Inc.
@date: May 02, 2010
Random stuff that doesn't deserve it's own module
but is still needed for the rest of the program.
"""
class frozendict(dict):
"""
A frozen dictionary implementation can not be modified once
it has been constructed, much like a tuple. Frozen dictionaries
are hashable.
"""
def __new__(cls, indict):
inst = dict.__new__(cls)
inst.__hash = hash(tuple(sorted(indict.items())))
inst.__slots__ = indict.keys()
dict.__init__(inst, indict)
return inst
@property
def __blocked(self):
raise AttributeError("Can't modify frozendict instance.")
__delitem__ = __setitem__ = clear = pop = __blocked
popitem = setdefault = update = __blocked
__hash__ = lambda self: self.__hash
__repr__ = lambda self: "frozendict({1})".format(dict.__repr__(self))
def implements(interface, debug=False):
"""
Verify that a class conforms to a specified interface.
This decorator is not perfect, for example it can not
check exceptions or return values. But it does ensure
that all public methods exist and their arguments
conform to the interface.
The debug flag allows overriding checking of the runtime
flag for testing purposes, it should never be set in
production code.
NOTE: This decorator does nothing if -d is not passed
to the Python runtime.
"""
import sys
if not sys.flags.debug and not debug:
return lambda func: func
# Defer this import until we know we're supposed to run
import inspect
def get_filtered_members(item):
"Gets non-private or non-protected symbols."
return dict([(key, value) for key, value in inspect.getmembers(item)
if not key.startswith('_')])
def build_contract(item):
"""
Builds a function contract string. The contract
string will ignore the name of positional params
but will consider the name of keyword arguments.
"""
argspec = inspect.getargspec(item)
if argspec.defaults:
num_keywords = len(argspec.defaults)
args = ['_'] * (len(argspec.args) - num_keywords)
args.extend(argspec.args[num_keywords-1:])
else:
args = ['_'] * len(argspec.args)
if argspec.varargs:
args.append('*args')
if argspec.keywords:
args.append('**kwargs')
return ', '.join(args)
def tester(klass):
"Verifies conformance to the interface."
interface_elements = get_filtered_members(interface)
class_elements = get_filtered_members(klass)
for key, value in interface_elements.items():
assert key in class_elements, \
"{0!r} is required but missing.".format(key)
if inspect.isfunction(value) or inspect.ismethod(value):
contract = build_contract(value)
implementation = build_contract(class_elements[key])
assert implementation == contract, \
"{0!r} doesn't conform to interface.".format(key)
return klass
return tester
|