1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Define the `@callback` decorator.
21 See :pep:`318` for an introduction to decorators.
24 from .caller import caller_name
28 """Return `True` if `x` is iterable.
32 >>> is_iterable('abc')
34 >>> is_iterable((1,2,3))
39 ... for i in range(5):
51 """Enable callbacks on `method`.
53 This decorator should make it easy to setup callbacks in a rich
54 GUI. You only need to decorate potential hooks, and maintain a
55 single dict with all the callbacks for the class. This beats
56 passing each of the callbacks into the class' `__init__` function
62 Callbacks are called with the class instance, method instance, and
63 returned arguments of the method they're attached to.
65 >>> def c(self, method, *args):
66 ... print '\\n '.join([
68 ... 'class: %s' % self,
69 ... 'method: %s' % method,
70 ... 'returned: %s' % args])
72 For some class, decorate any functions you're interested in
73 attaching callbacks too. Also, add a `_callbacks` attribute
74 holding the callbacks, keyed by function name.
77 ... def __init__(self):
78 ... self._callbacks = {'xyz': c}
83 ... print 'usual xyz business'
84 ... return (0, 1, 1, 2, 3, 5)
89 ... print 'usual abc business'
93 Here's our callback on `xyz`.
95 >>> r = x.xyz() # doctest: +ELLIPSIS
98 class: <hooke.util.callback.X object at 0x...>
99 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
100 returned: (0, 1, 1, 2, 3, 5)
104 The decorated method preserves the original docstring.
106 >>> print x.xyz.__doc__
109 So far, we haven't attached a callback to `abc`.
114 Now we attach the callback to `abc`.
116 >>> x._callbacks['abc'] = c
117 >>> r = x.abc() # doctest: +ELLIPSIS
120 class: <hooke.util.callback.X object at 0x...>
121 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
124 You can also place an iterable in the `_callbacks` dict to run an
125 array of callbacks in series.
127 >>> def d(self, method, *args):
128 ... print 'callback d'
129 >>> x._callbacks['abc'] = [d, c, d]
130 >>> r = x.abc() # doctest: +ELLIPSIS
134 class: <hooke.util.callback.X object at 0x...>
135 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
139 def new_m(self, *args, **kwargs):
140 result = method(self, *args, **kwargs)
141 callback = self._callbacks.get(method.func_name, None)
142 nm = getattr(self, method.func_name)
143 if is_iterable(callback):
146 elif callback != None:
147 callback(self, nm, result)
149 new_m.func_name = method.func_name
150 new_m.func_doc = method.func_doc
151 new_m.original_method = method
154 def in_callback(self, *args, **kwargs):
155 """Enable callbacks inside methods.
157 Sometimes :func:`callback` isn't granular enough. This function
158 can accomplish the same thing from inside your method, giving you
159 control over the arguments passed and the time at which the call
160 is made. It draws from the same `._callbacks` dictionary.
165 Callbacks are called with the class instance, method instance, and
166 returned arguments of the method they're attached to.
168 >>> def c(self, method, *args, **kwargs):
169 ... print '\\n '.join([
171 ... 'class: %s' % self,
172 ... 'method: %s' % method,
173 ... 'args: %s' % (args,),
174 ... 'kwargs: %s' % kwargs])
176 Now place `in_callback` calls inside any interesting methods.
178 >>> class X (object):
179 ... def __init__(self):
180 ... self._callbacks = {'xyz': c}
183 ... "xyz's docstring"
184 ... print 'usual xyz business'
185 ... in_callback(self, 5, my_kw=17)
186 ... return (0, 1, 1, 2, 3, 5)
189 ... "abc's docstring"
190 ... in_callback(self, p1=3.14, p2=159)
191 ... print 'usual abc business'
195 Here's our callback in `xyz`.
197 >>> r = x.xyz() # doctest: +ELLIPSIS
200 class: <hooke.util.callback.X object at 0x...>
201 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
203 kwargs: {'my_kw': 17}
207 Note that we haven't attached a callback to `abc`.
212 Now we attach the callback to `abc`.
214 >>> x._callbacks['abc'] = c
215 >>> r = x.abc() # doctest: +ELLIPSIS
217 class: <hooke.util.callback.X object at 0x...>
218 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
220 kwargs: {'p2': 159, 'p1': 3.1400000000000001}
223 You can also place an iterable in the `_callbacks` dict to run an
224 array of callbacks in series.
226 >>> def d(self, method, *args, **kwargs):
227 ... print 'callback d'
228 >>> x._callbacks['abc'] = [d, c, d]
229 >>> r = x.abc() # doctest: +ELLIPSIS
232 class: <hooke.util.callback.X object at 0x...>
233 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
235 kwargs: {'p2': 159, 'p1': 3.14...}
239 method_name = caller_name(depth=2)
240 callback = self._callbacks.get(method_name, None)
241 nm = getattr(self, method_name)
242 if is_iterable(callback):
244 cb(self, nm, *args, **kwargs)
245 elif callback != None:
246 callback(self, nm, *args, **kwargs)