2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
8 I define support for hookable instance methods.
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\".
14 This could be used to add optional preconditions, user-extensible callbacks
15 (a-la emacs) or a thread-safety mechanism.
17 The four exported calls are:
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.
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
43 class HookError(Exception):
44 "An error which will fire when an invariant is violated."
46 def addPre(klass, name, func):
47 """hook.addPre(klass, name, func) -> None
49 Add a function to be called before the method klass.name is invoked.
52 _addHook(klass, name, PRE, func)
54 def addPost(klass, name, func):
55 """hook.addPost(klass, name, func) -> None
57 Add a function to be called after the method klass.name is invoked.
59 _addHook(klass, name, POST, func)
61 def removePre(klass, name, func):
62 """hook.removePre(klass, name, func) -> None
64 Remove a function (previously registered with addPre) so that it
65 is no longer executed before klass.name.
68 _removeHook(klass, name, PRE, func)
70 def removePost(klass, name, func):
71 """hook.removePre(klass, name, func) -> None
73 Remove a function (previously registered with addPost) so that it
74 is no longer executed after klass.name.
76 _removeHook(klass, name, POST, func)
78 ### "Helper" functions.
84 def %(name)s(*args, **kw):
85 klazz = %(module)s.%(klass)s
86 for preMethod in klazz.%(preName)s:
87 preMethod(*args, **kw)
89 return klazz.%(originalName)s(*args, **kw)
91 for postMethod in klazz.%(postName)s:
92 postMethod(*args, **kw)
95 _PRE = '__hook_pre_%s_%s_%s__'
96 _POST = '__hook_post_%s_%s_%s__'
97 _ORIG = '__hook_orig_%s_%s_%s__'
101 "string manipulation garbage"
102 x = s % (string.replace(k.__module__,'.','_'), k.__name__, n)
106 "(private) munging to turn a method name into a pre-hook-method-name"
107 return _XXX(k,n,_PRE)
110 "(private) munging to turn a method name into a post-hook-method-name"
111 return _XXX(k,n,_POST)
114 "(private) munging to turn a method name into an `original' identifier"
115 return _XXX(k,n,_ORIG)
118 def _addHook(klass, name, phase, func):
119 "(private) adds a hook to a method on a class"
122 if not hasattr(klass, phase(klass, name)):
123 setattr(klass, phase(klass, name), [])
125 phaselist = getattr(klass, phase(klass, name))
126 phaselist.append(func)
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!")
135 phaselist = getattr(klass, phase(klass, name))
136 try: phaselist.remove(func)
138 raise HookError("hook %s not found in removal list for %s"%
141 if not getattr(klass, PRE(klass,name)) and not getattr(klass, POST(klass, name)):
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)):
149 def newfunc(*args, **kw):
150 for preMethod in getattr(klass, PRE(klass, name)):
151 preMethod(*args, **kw)
153 return getattr(klass, ORIG(klass, name))(*args, **kw)
155 for postMethod in getattr(klass, POST(klass, name)):
156 postMethod(*args, **kw)
158 newfunc.func_name = name
160 # Older python's don't let you do this
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)
169 def _dehook(klass, name):
170 "(private) causes a certain method name no longer to be hooked on a class"
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))