1 # Copyright (C) 2010-2012 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 under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
18 """Define the `@callback` decorator.
20 See :pep:`318` for an introduction to decorators.
25 from .caller import caller_name
29 """Return `True` if `x` is iterable.
33 >>> is_iterable('abc')
35 >>> is_iterable((1,2,3))
40 ... for i in range(5):
52 """Enable callbacks on `method`.
54 This decorator should make it easy to setup callbacks in a rich
55 GUI. You only need to decorate potential hooks, and maintain a
56 single dict with all the callbacks for the class. This beats
57 passing each of the callbacks into the class' `__init__` function
63 Callbacks are called with the class instance, method instance, and
64 returned arguments of the method they're attached to.
66 >>> def c(self, method, *args):
67 ... print '\\n '.join([
69 ... 'class: %s' % self,
70 ... 'method: %s' % method,
71 ... 'returned: %s' % args])
73 For some class, decorate any functions you're interested in
74 attaching callbacks too. Also, add a `_callbacks` attribute
75 holding the callbacks, keyed by function name.
78 ... def __init__(self):
79 ... self._callbacks = {'xyz': c}
84 ... print 'usual xyz business'
85 ... return (0, 1, 1, 2, 3, 5)
90 ... print 'usual abc business'
94 Here's our callback on `xyz`.
96 >>> r = x.xyz() # doctest: +ELLIPSIS
99 class: <hooke.util.callback.X object at 0x...>
100 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
101 returned: (0, 1, 1, 2, 3, 5)
105 The decorated method preserves the original docstring.
107 >>> print x.xyz.__doc__
110 So far, we haven't attached a callback to `abc`.
115 Now we attach the callback to `abc`.
117 >>> x._callbacks['abc'] = c
118 >>> r = x.abc() # doctest: +ELLIPSIS
121 class: <hooke.util.callback.X object at 0x...>
122 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
125 You can also place an iterable in the `_callbacks` dict to run an
126 array of callbacks in series.
128 >>> def d(self, method, *args):
129 ... print 'callback d'
130 >>> x._callbacks['abc'] = [d, c, d]
131 >>> r = x.abc() # doctest: +ELLIPSIS
135 class: <hooke.util.callback.X object at 0x...>
136 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
140 def new_m(self, *args, **kwargs):
141 result = method(self, *args, **kwargs)
142 callback = self._callbacks.get(method.func_name, None)
143 mn = getattr(self, method.func_name)
144 log = logging.getLogger('hooke')
145 log.debug('callback: %s (%s) calling %s' % (method.func_name, mn, callback))
146 if is_iterable(callback):
149 elif callback != None:
150 callback(self, mn, result)
152 new_m.func_name = method.func_name
153 new_m.func_doc = method.func_doc
154 new_m.original_method = method
157 def in_callback(self, *args, **kwargs):
158 """Enable callbacks inside methods.
160 Sometimes :func:`callback` isn't granular enough. This function
161 can accomplish the same thing from inside your method, giving you
162 control over the arguments passed and the time at which the call
163 is made. It draws from the same `._callbacks` dictionary.
168 Callbacks are called with the class instance, method instance, and
169 returned arguments of the method they're attached to.
171 >>> def c(self, method, *args, **kwargs):
172 ... print '\\n '.join([
174 ... 'class: %s' % self,
175 ... 'method: %s' % method,
176 ... 'args: %s' % (args,),
177 ... 'kwargs: %s' % kwargs])
179 Now place `in_callback` calls inside any interesting methods.
181 >>> class X (object):
182 ... def __init__(self):
183 ... self._callbacks = {'xyz': c}
186 ... "xyz's docstring"
187 ... print 'usual xyz business'
188 ... in_callback(self, 5, my_kw=17)
189 ... return (0, 1, 1, 2, 3, 5)
192 ... "abc's docstring"
193 ... in_callback(self, p1=3.14, p2=159)
194 ... print 'usual abc business'
198 Here's our callback in `xyz`.
200 >>> r = x.xyz() # doctest: +ELLIPSIS
203 class: <hooke.util.callback.X object at 0x...>
204 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
206 kwargs: {'my_kw': 17}
210 Note that we haven't attached a callback to `abc`.
215 Now we attach the callback to `abc`.
217 >>> x._callbacks['abc'] = c
218 >>> r = x.abc() # doctest: +ELLIPSIS
220 class: <hooke.util.callback.X object at 0x...>
221 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
223 kwargs: {'p2': 159, 'p1': 3.1400000000000001}
226 You can also place an iterable in the `_callbacks` dict to run an
227 array of callbacks in series.
229 >>> def d(self, method, *args, **kwargs):
230 ... print 'callback d'
231 >>> x._callbacks['abc'] = [d, c, d]
232 >>> r = x.abc() # doctest: +ELLIPSIS
235 class: <hooke.util.callback.X object at 0x...>
236 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
238 kwargs: {'p2': 159, 'p1': 3.14...}
242 method_name = caller_name(depth=2)
243 callback = self._callbacks.get(method_name, None)
244 mn = getattr(self, method_name)
245 log = logging.getLogger('hooke')
246 log.debug('callback: %s (%s) calling %s' % (method_name, mn, callback))
247 if is_iterable(callback):
249 cb(self, mn, *args, **kwargs)
250 elif callback != None:
251 callback(self, mn, *args, **kwargs)