2
# Copyright (c) 2001-2004 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))