3 """Define the `@callback` decorator.
5 See :pep:`318` for an introduction to decorators.
8 from .caller import caller_name
12 """Return `True` if `x` is iterable.
16 >>> is_iterable('abc')
18 >>> is_iterable((1,2,3))
23 ... for i in range(5):
35 """Enable callbacks on `method`.
37 This decorator should make it easy to setup callbacks in a rich
38 GUI. You only need to decorate potential hooks, and maintain a
39 single dict with all the callbacks for the class. This beats
40 passing each of the callbacks into the class' `__init__` function
46 Callbacks are called with the class instance, method instance, and
47 returned arguments of the method they're attached to.
49 >>> def c(self, method, *args):
50 ... print '\\n '.join([
52 ... 'class: %s' % self,
53 ... 'method: %s' % method,
54 ... 'returned: %s' % args])
56 For some class, decorate any functions you're interested in
57 attaching callbacks too. Also, add a `_callbacks` attribute
58 holding the callbacks, keyed by function name.
61 ... def __init__(self):
62 ... self._callbacks = {'xyz': c}
67 ... print 'usual xyz business'
68 ... return (0, 1, 1, 2, 3, 5)
73 ... print 'usual abc business'
77 Here's our callback on `xyz`.
79 >>> r = x.xyz() # doctest: +ELLIPSIS
82 class: <hooke.util.callback.X object at 0x...>
83 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
84 returned: (0, 1, 1, 2, 3, 5)
88 The decorated method preserves the original docstring.
90 >>> print x.xyz.__doc__
93 So far, we haven't attached a callback to `abc`.
98 Now we attach the callback to `abc`.
100 >>> x._callbacks['abc'] = c
101 >>> r = x.abc() # doctest: +ELLIPSIS
104 class: <hooke.util.callback.X object at 0x...>
105 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
108 You can also place an iterable in the `_callbacks` dict to run an
109 array of callbacks in series.
111 >>> def d(self, method, *args):
112 ... print 'callback d'
113 >>> x._callbacks['abc'] = [d, c, d]
114 >>> r = x.abc() # doctest: +ELLIPSIS
118 class: <hooke.util.callback.X object at 0x...>
119 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
123 def new_m(self, *args, **kwargs):
124 result = method(self, *args, **kwargs)
125 callback = self._callbacks.get(method.func_name, None)
126 nm = getattr(self, method.func_name)
127 if is_iterable(callback):
130 elif callback != None:
131 callback(self, nm, result)
133 new_m.func_name = method.func_name
134 new_m.func_doc = method.func_doc
135 new_m.original_method = method
138 def in_callback(self, *args, **kwargs):
139 """Enable callbacks inside methods.
141 Sometimes :func:`callback` isn't granular enough. This function
142 can accomplish the same thing from inside your method, giving you
143 control over the arguments passed and the time at which the call
144 is made. It draws from the same `._callbacks` dictionary.
149 Callbacks are called with the class instance, method instance, and
150 returned arguments of the method they're attached to.
152 >>> def c(self, method, *args, **kwargs):
153 ... print '\\n '.join([
155 ... 'class: %s' % self,
156 ... 'method: %s' % method,
157 ... 'args: %s' % (args,),
158 ... 'kwargs: %s' % kwargs])
160 Now place `in_callback` calls inside any interesting methods.
162 >>> class X (object):
163 ... def __init__(self):
164 ... self._callbacks = {'xyz': c}
167 ... "xyz's docstring"
168 ... print 'usual xyz business'
169 ... in_callback(self, 5, my_kw=17)
170 ... return (0, 1, 1, 2, 3, 5)
173 ... "abc's docstring"
174 ... in_callback(self, p1=3.14, p2=159)
175 ... print 'usual abc business'
179 Here's our callback in `xyz`.
181 >>> r = x.xyz() # doctest: +ELLIPSIS
184 class: <hooke.util.callback.X object at 0x...>
185 method: <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
187 kwargs: {'my_kw': 17}
191 Note that we haven't attached a callback to `abc`.
196 Now we attach the callback to `abc`.
198 >>> x._callbacks['abc'] = c
199 >>> r = x.abc() # doctest: +ELLIPSIS
201 class: <hooke.util.callback.X object at 0x...>
202 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
204 kwargs: {'p2': 159, 'p1': 3.1400000000000001}
207 You can also place an iterable in the `_callbacks` dict to run an
208 array of callbacks in series.
210 >>> def d(self, method, *args, **kwargs):
211 ... print 'callback d'
212 >>> x._callbacks['abc'] = [d, c, d]
213 >>> r = x.abc() # doctest: +ELLIPSIS
216 class: <hooke.util.callback.X object at 0x...>
217 method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
219 kwargs: {'p2': 159, 'p1': 3.14...}
223 method_name = caller_name(depth=2)
224 callback = self._callbacks.get(method_name, None)
225 nm = getattr(self, method_name)
226 if is_iterable(callback):
228 cb(self, nm, *args, **kwargs)
229 elif callback != None:
230 callback(self, nm, *args, **kwargs)