See :pep:`318` for an introduction to decorators.
"""
+from .caller import caller_name
+
+
+def is_iterable(x):
+ """Return `True` if `x` is iterable.
+
+ Examples
+ --------
+ >>> is_iterable('abc')
+ True
+ >>> is_iterable((1,2,3))
+ True
+ >>> is_iterable(5)
+ False
+ >>> def c():
+ ... for i in range(5):
+ ... yield i
+ >>> is_iterable(c())
+ True
+ """
+ try:
+ iter(x)
+ return True
+ except TypeError:
+ return False
def callback(method):
"""Enable callbacks on `method`.
>>> r
(0, 1, 1, 2, 3, 5)
- Note that we haven't attached a callback to `abc`.
+ The decorated method preserves the original docstring.
+
+ >>> print x.xyz.__doc__
+ xyz's docstring
+
+ So far, we haven't attached a callback to `abc`.
>>> r = x.abc()
usual abc business
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]
result = method(self, *args, **kwargs)
callback = self._callbacks.get(method.func_name, None)
nm = getattr(self, method.func_name)
- try:
+ if is_iterable(callback):
for cb in callback:
cb(self, nm, result)
- except TypeError:
- if callback != None:
- callback(self, nm, result)
+ elif 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
+
+def in_callback(self, *args, **kwargs):
+ """Enable callbacks inside methods.
+
+ Sometimes :func:`callback` isn't granular enough. This function
+ can accomplish the same thing from inside your method, giving you
+ control over the arguments passed and the time at which the call
+ is made. It draws from the same `._callbacks` dictionary.
+
+ 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, **kwargs):
+ ... print '\\n '.join([
+ ... 'callback:',
+ ... 'class: %s' % self,
+ ... 'method: %s' % method,
+ ... 'args: %s' % (args,),
+ ... 'kwargs: %s' % kwargs])
+
+ Now place `in_callback` calls inside any interesting methods.
+
+ >>> class X (object):
+ ... def __init__(self):
+ ... self._callbacks = {'xyz': c}
+ ...
+ ... def xyz(self):
+ ... "xyz's docstring"
+ ... print 'usual xyz business'
+ ... in_callback(self, 5, my_kw=17)
+ ... return (0, 1, 1, 2, 3, 5)
+ ...
+ ... def abc(self):
+ ... "abc's docstring"
+ ... in_callback(self, p1=3.14, p2=159)
+ ... print 'usual abc business'
+ ...
+ >>> x = X()
+
+ Here's our callback in `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...>>
+ args: (5,)
+ kwargs: {'my_kw': 17}
+ >>> 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
+ callback:
+ class: <hooke.util.callback.X object at 0x...>
+ method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
+ args: ()
+ kwargs: {'p2': 159, 'p1': 3.1400000000000001}
+ usual abc business
+
+ You can also place an iterable in the `_callbacks` dict to run an
+ array of callbacks in series.
+
+ >>> def d(self, method, *args, **kwargs):
+ ... print 'callback d'
+ >>> x._callbacks['abc'] = [d, c, d]
+ >>> r = x.abc() # doctest: +ELLIPSIS
+ callback d
+ callback:
+ class: <hooke.util.callback.X object at 0x...>
+ method: <bound method X.abc of <hooke.util.callback.X object at 0x...>>
+ args: ()
+ kwargs: {'p2': 159, 'p1': 3.14...}
+ callback d
+ usual abc business
+ """
+ method_name = caller_name(depth=2)
+ callback = self._callbacks.get(method_name, None)
+ nm = getattr(self, method_name)
+ if is_iterable(callback):
+ for cb in callback:
+ cb(self, nm, *args, **kwargs)
+ elif callback != None:
+ callback(self, nm, *args, **kwargs)
--- /dev/null
+# Copyright
+
+"""Define :func:`caller_name`.
+
+This is useful, for example, to declare the `@callback` decorator for
+making GUI writing less tedious. See :mod:`hooke.util.callback` and
+:mod:`hooke.ui.gui` for examples.
+"""
+
+import sys
+
+
+def frame(depth=1):
+ """Return the frame for the function `depth` up the call stack.
+
+ Notes
+ -----
+ The `ZeroDivisionError` trick is from stdlib's traceback.py. See
+ the Python Refrence Manual on `traceback objects`_ and `frame
+ objects`_.
+
+ .. _traceback objects:
+ http://docs.python.org/reference/datamodel.html#index-873
+ .. _frame objects:
+ http://docs.python.org/reference/datamodel.html#index-870
+ """
+ try:
+ raise ZeroDivisionError
+ except ZeroDivisionError:
+ traceback = sys.exc_info()[2]
+ f = traceback.tb_frame
+ for i in range(depth):
+ f = f.f_back
+ return f
+
+def caller_name(depth=1):
+ """Return the name of the function `depth` up the call stack.
+
+ Examples
+ --------
+
+ >>> def x(depth):
+ ... y(depth)
+ >>> def y(depth):
+ ... print caller_name(depth)
+ >>> x(1)
+ y
+ >>> x(2)
+ x
+ >>> x(0)
+ caller_name
+
+ Notes
+ -----
+ See the Python Refrence manual on `frame objects`_ and
+ `code objects`_.
+
+ .. _frame objects:
+ http://docs.python.org/reference/datamodel.html#index-870
+ .. _code objects:
+ http://docs.python.org/reference/datamodel.html#index-866
+ """
+ f = frame(depth=depth+1)
+ return f.f_code.co_name