OLD | NEW |
| (Empty) |
1 | |
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 | |
7 """ | |
8 I define support for hookable instance methods. | |
9 | |
10 These are methods which you can register pre-call and post-call external | |
11 functions to augment their functionality. People familiar with more esoteric | |
12 languages may think of these as \"method combinations\". | |
13 | |
14 This could be used to add optional preconditions, user-extensible callbacks | |
15 (a-la emacs) or a thread-safety mechanism. | |
16 | |
17 The four exported calls are: | |
18 | |
19 - L{addPre} | |
20 - L{addPost} | |
21 - L{removePre} | |
22 - L{removePost} | |
23 | |
24 All have the signature (class, methodName, callable), and the callable they | |
25 take must always have the signature (instance, *args, **kw) unless the | |
26 particular signature of the method they hook is known. | |
27 | |
28 Hooks should typically not throw exceptions, however, no effort will be made by | |
29 this module to prevent them from doing so. Pre-hooks will always be called, | |
30 but post-hooks will only be called if the pre-hooks do not raise any exceptions | |
31 (they will still be called if the main method raises an exception). The return | |
32 values and exception status of the main method will be propogated (assuming | |
33 none of the hooks raise an exception). Hooks will be executed in the order in | |
34 which they are added. | |
35 | |
36 """ | |
37 | |
38 # System Imports | |
39 import string | |
40 | |
41 ### Public Interface | |
42 | |
43 class HookError(Exception): | |
44 "An error which will fire when an invariant is violated." | |
45 | |
46 def addPre(klass, name, func): | |
47 """hook.addPre(klass, name, func) -> None | |
48 | |
49 Add a function to be called before the method klass.name is invoked. | |
50 """ | |
51 | |
52 _addHook(klass, name, PRE, func) | |
53 | |
54 def addPost(klass, name, func): | |
55 """hook.addPost(klass, name, func) -> None | |
56 | |
57 Add a function to be called after the method klass.name is invoked. | |
58 """ | |
59 _addHook(klass, name, POST, func) | |
60 | |
61 def removePre(klass, name, func): | |
62 """hook.removePre(klass, name, func) -> None | |
63 | |
64 Remove a function (previously registered with addPre) so that it | |
65 is no longer executed before klass.name. | |
66 """ | |
67 | |
68 _removeHook(klass, name, PRE, func) | |
69 | |
70 def removePost(klass, name, func): | |
71 """hook.removePre(klass, name, func) -> None | |
72 | |
73 Remove a function (previously registered with addPost) so that it | |
74 is no longer executed after klass.name. | |
75 """ | |
76 _removeHook(klass, name, POST, func) | |
77 | |
78 ### "Helper" functions. | |
79 | |
80 hooked_func = """ | |
81 | |
82 import %(module)s | |
83 | |
84 def %(name)s(*args, **kw): | |
85 klazz = %(module)s.%(klass)s | |
86 for preMethod in klazz.%(preName)s: | |
87 preMethod(*args, **kw) | |
88 try: | |
89 return klazz.%(originalName)s(*args, **kw) | |
90 finally: | |
91 for postMethod in klazz.%(postName)s: | |
92 postMethod(*args, **kw) | |
93 """ | |
94 | |
95 _PRE = '__hook_pre_%s_%s_%s__' | |
96 _POST = '__hook_post_%s_%s_%s__' | |
97 _ORIG = '__hook_orig_%s_%s_%s__' | |
98 | |
99 | |
100 def _XXX(k,n,s): | |
101 "string manipulation garbage" | |
102 x = s % (string.replace(k.__module__,'.','_'), k.__name__, n) | |
103 return x | |
104 | |
105 def PRE(k,n): | |
106 "(private) munging to turn a method name into a pre-hook-method-name" | |
107 return _XXX(k,n,_PRE) | |
108 | |
109 def POST(k,n): | |
110 "(private) munging to turn a method name into a post-hook-method-name" | |
111 return _XXX(k,n,_POST) | |
112 | |
113 def ORIG(k,n): | |
114 "(private) munging to turn a method name into an `original' identifier" | |
115 return _XXX(k,n,_ORIG) | |
116 | |
117 | |
118 def _addHook(klass, name, phase, func): | |
119 "(private) adds a hook to a method on a class" | |
120 _enhook(klass, name) | |
121 | |
122 if not hasattr(klass, phase(klass, name)): | |
123 setattr(klass, phase(klass, name), []) | |
124 | |
125 phaselist = getattr(klass, phase(klass, name)) | |
126 phaselist.append(func) | |
127 | |
128 | |
129 def _removeHook(klass, name, phase, func): | |
130 "(private) removes a hook from a method on a class" | |
131 phaselistname = phase(klass, name) | |
132 if not hasattr(klass, ORIG(klass,name)): | |
133 raise HookError("no hooks present!") | |
134 | |
135 phaselist = getattr(klass, phase(klass, name)) | |
136 try: phaselist.remove(func) | |
137 except ValueError: | |
138 raise HookError("hook %s not found in removal list for %s"% | |
139 (name,klass)) | |
140 | |
141 if not getattr(klass, PRE(klass,name)) and not getattr(klass, POST(klass, na
me)): | |
142 _dehook(klass, name) | |
143 | |
144 def _enhook(klass, name): | |
145 "(private) causes a certain method name to be hooked on a class" | |
146 if hasattr(klass, ORIG(klass, name)): | |
147 return | |
148 | |
149 def newfunc(*args, **kw): | |
150 for preMethod in getattr(klass, PRE(klass, name)): | |
151 preMethod(*args, **kw) | |
152 try: | |
153 return getattr(klass, ORIG(klass, name))(*args, **kw) | |
154 finally: | |
155 for postMethod in getattr(klass, POST(klass, name)): | |
156 postMethod(*args, **kw) | |
157 try: | |
158 newfunc.func_name = name | |
159 except TypeError: | |
160 # Older python's don't let you do this | |
161 pass | |
162 | |
163 oldfunc = getattr(klass, name).im_func | |
164 setattr(klass, ORIG(klass, name), oldfunc) | |
165 setattr(klass, PRE(klass, name), []) | |
166 setattr(klass, POST(klass, name), []) | |
167 setattr(klass, name, newfunc) | |
168 | |
169 def _dehook(klass, name): | |
170 "(private) causes a certain method name no longer to be hooked on a class" | |
171 | |
172 if not hasattr(klass, ORIG(klass, name)): | |
173 raise HookError("Cannot unhook!") | |
174 setattr(klass, name, getattr(klass, ORIG(klass,name))) | |
175 delattr(klass, PRE(klass,name)) | |
176 delattr(klass, POST(klass,name)) | |
177 delattr(klass, ORIG(klass,name)) | |
OLD | NEW |