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.
26 from .caller import caller_name
30 """Return `True` if `x` is iterable.
34 >>> is_iterable('abc')
36 >>> is_iterable((1,2,3))
41 ... for i in range(5):
53 """Enable callbacks on `method`.
55 This decorator should make it easy to setup callbacks in a rich
56 GUI. You only need to decorate potential hooks, and maintain a
57 single dict with all the callbacks for the class. This beats
58 passing each of the callbacks into the class' `__init__` function
64 Callbacks are called with the class instance, method instance, and
65 returned arguments of the method they're attached to.
67 >>> def c(self, method, *args):
68 ... print '\\n '.join([
70 ... 'class: %s' % self,
71 ... 'method: %s' % method,
72 ... 'returned: %s' % args])
74 For some class, decorate any functions you're interested in
75 attaching callbacks too. Also, add a `_callbacks` attribute
76 holding the callbacks, keyed by function name.
79 ... def __init__(self):
80 ... self._callbacks = {'xyz': c}
85 ... print 'usual xyz business'
86 ... return (0, 1, 1, 2, 3, 5)
91 ... print 'usual abc business'
95 Here's our callback on `xyz`.
97 >>> r = x.xyz() # doctest: +ELLIPSIS
100 class: <hooke.util.callback.X object at 0x...>
101 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
102 returned: (0, 1, 1, 2, 3, 5)
106 The decorated method preserves the original docstring.
108 >>> print x.xyz.__doc__
111 So far, we haven't attached a callback to `abc`.
116 Now we attach the callback to `abc`.
118 >>> x._callbacks['abc'] = c
119 >>> r = x.abc() # doctest: +ELLIPSIS
122 class: <hooke.util.callback.X object at 0x...>
123 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
126 You can also place an iterable in the `_callbacks` dict to run an
127 array of callbacks in series.
129 >>> def d(self, method, *args):
130 ... print 'callback d'
131 >>> x._callbacks['abc'] = [d, c, d]
132 >>> r = x.abc() # doctest: +ELLIPSIS
136 class: <hooke.util.callback.X object at 0x...>
137 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
141 def new_m(self, *args, **kwargs):
142 result = method(self, *args, **kwargs)
143 callback = self._callbacks.get(method.func_name, None)
144 mn = getattr(self, method.func_name)
145 log = logging.getLogger('hooke')
146 log.debug('callback: %s (%s) calling %s' % (method.func_name, mn, callback))
147 if is_iterable(callback):
150 elif callback != None:
151 callback(self, mn, result)
153 new_m.func_name = method.func_name
154 new_m.func_doc = method.func_doc
155 new_m.original_method = method
158 def in_callback(self, *args, **kwargs):
159 """Enable callbacks inside methods.
161 Sometimes :func:`callback` isn't granular enough. This function
162 can accomplish the same thing from inside your method, giving you
163 control over the arguments passed and the time at which the call
164 is made. It draws from the same `._callbacks` dictionary.
169 Callbacks are called with the class instance, method instance, and
170 returned arguments of the method they're attached to.
172 >>> def c(self, method, *args, **kwargs):
173 ... print '\\n '.join([
175 ... 'class: %s' % self,
176 ... 'method: %s' % method,
177 ... 'args: %s' % (args,),
178 ... 'kwargs: %s' % kwargs])
180 Now place `in_callback` calls inside any interesting methods.
182 >>> class X (object):
183 ... def __init__(self):
184 ... self._callbacks = {'xyz': c}
187 ... "xyz's docstring"
188 ... print 'usual xyz business'
189 ... in_callback(self, 5, my_kw=17)
190 ... return (0, 1, 1, 2, 3, 5)
193 ... "abc's docstring"
194 ... in_callback(self, p1=3.14, p2=159)
195 ... print 'usual abc business'
199 Here's our callback in `xyz`.
201 >>> r = x.xyz() # doctest: +ELLIPSIS
204 class: <hooke.util.callback.X object at 0x...>
205 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
207 kwargs: {'my_kw': 17}
211 Note that we haven't attached a callback to `abc`.
216 Now we attach the callback to `abc`.
218 >>> x._callbacks['abc'] = c
219 >>> r = x.abc() # doctest: +ELLIPSIS
221 class: <hooke.util.callback.X object at 0x...>
222 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
224 kwargs: {'p2': 159, 'p1': 3.1400000000000001}
227 You can also place an iterable in the `_callbacks` dict to run an
228 array of callbacks in series.
230 >>> def d(self, method, *args, **kwargs):
231 ... print 'callback d'
232 >>> x._callbacks['abc'] = [d, c, d]
233 >>> r = x.abc() # doctest: +ELLIPSIS
236 class: <hooke.util.callback.X object at 0x...>
237 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
239 kwargs: {'p2': 159, 'p1': 3.14...}
243 method_name = caller_name(depth=2)
244 callback = self._callbacks.get(method_name, None)
245 mn = getattr(self, method_name)
246 log = logging.getLogger('hooke')
247 log.debug('callback: %s (%s) calling %s' % (method_name, mn, callback))
248 if is_iterable(callback):
250 cb(self, mn, *args, **kwargs)
251 elif callback != None:
252 callback(self, mn, *args, **kwargs)