summaryrefslogtreecommitdiff
path: root/foundry/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'foundry/utils.py')
-rw-r--r--foundry/utils.py106
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"""
3Random Stuff
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: May 02, 2010
8
9Random stuff that doesn't deserve it's own module
10but is still needed for the rest of the program.
11"""
12
13class 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
38def 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