Added hooke.util.callback defining the @callback decorator.
[hooke.git] / hooke / util / callback.py
diff --git a/hooke/util/callback.py b/hooke/util/callback.py
new file mode 100644 (file)
index 0000000..865ff38
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright
+
+"""Define the `@callback` decorator.
+
+See :pep:`318` for an introduction to decorators.
+"""
+
+
+def callback(method):
+    """Enable callbacks on `method`.
+
+    This decorator should make it easy to setup callbacks in a rich
+    GUI.  You only need to decorate potential hooks, and maintain a
+    single dict with all the callbacks for the class.  This beats
+    passing each of the callbacks into the class' `__init__` function
+    individually.
+
+    Examples
+    --------
+
+    Callbacks are called with the class instance, method instance, and
+    returned arguments of the method they're attached to.
+
+    >>> def c(self, method, *args):
+    ...     print '\\n  '.join([
+    ...             'callback:',
+    ...             'class:    %s' % self,
+    ...             'method:   %s' % method,
+    ...             'returned: %s' % args])
+
+    For some class, decorate any functions you're interested in
+    attaching callbacks too.  Also, add a `_callbacks` attribute
+    holding the callbacks, keyed by function name.
+
+    >>> class X (object):
+    ...     def __init__(self):
+    ...         self._callbacks = {'xyz': c}
+    ...
+    ...     @callback
+    ...     def xyz(self):
+    ...         "xyz's docstring"
+    ...         print 'usual xyz business'
+    ...         return (0, 1, 1, 2, 3, 5)
+    ...
+    ...     @callback
+    ...     def abc(self):
+    ...         "abc's docstring"
+    ...         print 'usual abc business'
+    ...
+    >>> x = X()
+
+    Here's our callback on `xyz`.
+
+    >>> r = x.xyz()  # doctest: +ELLIPSIS
+    usual xyz business
+    callback:
+      class:    <hooke.util.callback.X object at 0x...>
+      method:   <bound method X.xyz of <hooke.util.callback.X object at 0x...>>
+      returned: (0, 1, 1, 2, 3, 5)
+    >>> r
+    (0, 1, 1, 2, 3, 5)
+
+    Note that we haven't attached a callback to `abc`.
+
+    >>> r = x.abc()
+    usual abc business
+
+    Now we attach the callback to `abc`.
+
+    >>> x._callbacks['abc'] = c
+    >>> r = x.abc()  # doctest: +ELLIPSIS
+    usual abc business
+    callback:
+      class:    <hooke.util.callback.X object at 0x...>
+      method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
+      returned: None
+
+    You can also place an iterable in the `_callbacks` dict to run an
+    array of callbacks in series.
+    >>> def d(self, method, *args):
+    ...     print 'callback d'
+    >>> x._callbacks['abc'] = [d, c, d]
+    >>> r = x.abc()  # doctest: +ELLIPSIS
+    usual abc business
+    callback d
+    callback:
+      class:    <hooke.util.callback.X object at 0x...>
+      method:   <bound method X.abc of <hooke.util.callback.X object at 0x...>>
+      returned: None
+    callback d
+    """
+    def new_m(self, *args, **kwargs):
+        result = method(self, *args, **kwargs)
+        callback = self._callbacks.get(method.func_name, None)
+        nm = getattr(self, method.func_name)
+        try:
+            for cb in callback:
+                cb(self, nm, result)
+        except TypeError:
+            if callback != None:
+                callback(self, nm, result)
+        return result
+    new_m.func_name = method.func_name
+    new_m.func_doc = method.func_doc
+    new_m.original_method = method
+    return new_m