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