Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / hook.py
1
2 # Copyright (c) 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, name)):
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))