Added hooke.util.callback defining the @callback decorator.
[hooke.git] / hooke / util / callback.py
1 # Copyright
2
3 """Define the `@callback` decorator.
4
5 See :pep:`318` for an introduction to decorators.
6 """
7
8
9 def callback(method):
10     """Enable callbacks on `method`.
11
12     This decorator should make it easy to setup callbacks in a rich
13     GUI.  You only need to decorate potential hooks, and maintain a
14     single dict with all the callbacks for the class.  This beats
15     passing each of the callbacks into the class' `__init__` function
16     individually.
17
18     Examples
19     --------
20
21     Callbacks are called with the class instance, method instance, and
22     returned arguments of the method they're attached to.
23
24     >>> def c(self, method, *args):
25     ...     print '\\n  '.join([
26     ...             'callback:',
27     ...             'class:    %s' % self,
28     ...             'method:   %s' % method,
29     ...             'returned: %s' % args])
30
31     For some class, decorate any functions you're interested in
32     attaching callbacks too.  Also, add a `_callbacks` attribute
33     holding the callbacks, keyed by function name.
34
35     >>> class X (object):
36     ...     def __init__(self):
37     ...         self._callbacks = {'xyz': c}
38     ...
39     ...     @callback
40     ...     def xyz(self):
41     ...         "xyz's docstring"
42     ...         print 'usual xyz business'
43     ...         return (0, 1, 1, 2, 3, 5)
44     ...
45     ...     @callback
46     ...     def abc(self):
47     ...         "abc's docstring"
48     ...         print 'usual abc business'
49     ...
50     >>> x = X()
51
52     Here's our callback on `xyz`.
53
54     >>> r = x.xyz()  # doctest: +ELLIPSIS
55     usual xyz business
56     callback:
57       class:    <hooke.util.callback.X object at 0x...>
58       method:   <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
59       returned: (0, 1, 1, 2, 3, 5)
60     >>> r
61     (0, 1, 1, 2, 3, 5)
62
63     Note that we haven't attached a callback to `abc`.
64
65     >>> r = x.abc()
66     usual abc business
67
68     Now we attach the callback to `abc`.
69
70     >>> x._callbacks['abc'] = c
71     >>> r = x.abc()  # doctest: +ELLIPSIS
72     usual abc business
73     callback:
74       class:    <hooke.util.callback.X object at 0x...>
75       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
76       returned: None
77
78     You can also place an iterable in the `_callbacks` dict to run an
79     array of callbacks in series.
80     >>> def d(self, method, *args):
81     ...     print 'callback d'
82     >>> x._callbacks['abc'] = [d, c, d]
83     >>> r = x.abc()  # doctest: +ELLIPSIS
84     usual abc business
85     callback d
86     callback:
87       class:    <hooke.util.callback.X object at 0x...>
88       method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
89       returned: None
90     callback d
91     """
92     def new_m(self, *args, **kwargs):
93         result = method(self, *args, **kwargs)
94         callback = self._callbacks.get(method.func_name, None)
95         nm = getattr(self, method.func_name)
96         try:
97             for cb in callback:
98                 cb(self, nm, result)
99         except TypeError:
100             if callback != None:
101                 callback(self, nm, result)
102         return result
103     new_m.func_name = method.func_name
104     new_m.func_doc = method.func_doc
105     new_m.original_method = method
106     return new_m