Ran update_copyright.py
[hooke.git] / hooke / util / callback.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
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.
9 #
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.
14 #
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/>.
18
19 """Define the `@callback` decorator.
20
21 See :pep:`318` for an introduction to decorators.
22 """
23
24 from .caller import caller_name
25
26
27 def is_iterable(x):
28     """Return `True` if `x` is iterable.
29
30     Examples
31     --------
32     >>> is_iterable('abc')
33     True
34     >>> is_iterable((1,2,3))
35     True
36     >>> is_iterable(5)
37     False
38     >>> def c():
39     ...     for i in range(5):
40     ...         yield i
41     >>> is_iterable(c())
42     True
43     """
44     try:
45         iter(x)
46         return True
47     except TypeError:
48         return False
49
50 def callback(method):
51     """Enable callbacks on `method`.
52
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
57     individually.
58
59     Examples
60     --------
61
62     Callbacks are called with the class instance, method instance, and
63     returned arguments of the method they're attached to.
64
65     >>> def c(self, method, *args):
66     ...     print '\\n  '.join([
67     ...             'callback:',
68     ...             'class:    %s' % self,
69     ...             'method:   %s' % method,
70     ...             'returned: %s' % args])
71
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.
75
76     >>> class X (object):
77     ...     def __init__(self):
78     ...         self._callbacks = {'xyz': c}
79     ...
80     ...     @callback
81     ...     def xyz(self):
82     ...         "xyz's docstring"
83     ...         print 'usual xyz business'
84     ...         return (0, 1, 1, 2, 3, 5)
85     ...
86     ...     @callback
87     ...     def abc(self):
88     ...         "abc's docstring"
89     ...         print 'usual abc business'
90     ...
91     >>> x = X()
92
93     Here's our callback on `xyz`.
94
95     >>> r = x.xyz()  # doctest: +ELLIPSIS
96     usual xyz business
97     callback:
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)
101     >>> r
102     (0, 1, 1, 2, 3, 5)
103
104     The decorated method preserves the original docstring.
105
106     >>> print x.xyz.__doc__
107     xyz's docstring
108
109     So far, we haven't attached a callback to `abc`.
110
111     >>> r = x.abc()
112     usual abc business
113
114     Now we attach the callback to `abc`.
115
116     >>> x._callbacks['abc'] = c
117     >>> r = x.abc()  # doctest: +ELLIPSIS
118     usual abc business
119     callback:
120       class:    <hooke.util.callback.X object at 0x...>
121       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
122       returned: None
123
124     You can also place an iterable in the `_callbacks` dict to run an
125     array of callbacks in series.
126
127     >>> def d(self, method, *args):
128     ...     print 'callback d'
129     >>> x._callbacks['abc'] = [d, c, d]
130     >>> r = x.abc()  # doctest: +ELLIPSIS
131     usual abc business
132     callback d
133     callback:
134       class:    <hooke.util.callback.X object at 0x...>
135       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
136       returned: None
137     callback d
138     """
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):
144             for cb in callback:
145                 cb(self, nm, result)
146         elif callback != None:
147             callback(self, nm, result)
148         return result
149     new_m.func_name = method.func_name
150     new_m.func_doc = method.func_doc
151     new_m.original_method = method
152     return new_m
153
154 def in_callback(self, *args, **kwargs):
155     """Enable callbacks inside methods.
156
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.
161
162     Examples
163     --------
164
165     Callbacks are called with the class instance, method instance, and
166     returned arguments of the method they're attached to.
167
168     >>> def c(self, method, *args, **kwargs):
169     ...     print '\\n  '.join([
170     ...             'callback:',
171     ...             'class:    %s' % self,
172     ...             'method:   %s' % method,
173     ...             'args:     %s' % (args,),
174     ...             'kwargs:   %s' % kwargs])
175
176     Now place `in_callback` calls inside any interesting methods.
177
178     >>> class X (object):
179     ...     def __init__(self):
180     ...         self._callbacks = {'xyz': c}
181     ...
182     ...     def xyz(self):
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)
187     ...
188     ...     def abc(self):
189     ...         "abc's docstring"
190     ...         in_callback(self, p1=3.14, p2=159)
191     ...         print 'usual abc business'
192     ...
193     >>> x = X()
194
195     Here's our callback in `xyz`.
196
197     >>> r = x.xyz()  # doctest: +ELLIPSIS
198     usual xyz business
199     callback:
200       class:    <hooke.util.callback.X object at 0x...>
201       method:   <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
202       args:     (5,)
203       kwargs:   {'my_kw': 17}
204     >>> r
205     (0, 1, 1, 2, 3, 5)
206
207     Note that we haven't attached a callback to `abc`.
208
209     >>> r = x.abc()
210     usual abc business
211
212     Now we attach the callback to `abc`.
213
214     >>> x._callbacks['abc'] = c
215     >>> r = x.abc()  # doctest: +ELLIPSIS
216     callback:
217       class:    <hooke.util.callback.X object at 0x...>
218       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
219       args:     ()
220       kwargs:   {'p2': 159, 'p1': 3.1400000000000001}
221     usual abc business
222
223     You can also place an iterable in the `_callbacks` dict to run an
224     array of callbacks in series.
225
226     >>> def d(self, method, *args, **kwargs):
227     ...     print 'callback d'
228     >>> x._callbacks['abc'] = [d, c, d]
229     >>> r = x.abc()  # doctest: +ELLIPSIS
230     callback d
231     callback:
232       class:    <hooke.util.callback.X object at 0x...>
233       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
234       args:     ()
235       kwargs:   {'p2': 159, 'p1': 3.14...}
236     callback d
237     usual abc business
238     """
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):
243         for cb in callback:
244             cb(self, nm, *args, **kwargs)
245     elif callback != None:
246         callback(self, nm, *args, **kwargs)