/circuits/core/handlers.py

https://bitbucket.org/prologic/circuits/ · Python · 128 lines · 96 code · 9 blank · 23 comment · 6 complexity · 35f09e28ee5df909fe9ba4d14f399a8f MD5 · raw file

  1. # Package: handlers
  2. # Date: 11th April 2010
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """
  5. This module define the @handler decorator/function and the HandlesType type.
  6. """
  7. from inspect import getargspec
  8. from collections import Callable
  9. def handler(*names, **kwargs):
  10. """Creates an Event Handler
  11. This decorator can be applied to methods of classes derived from
  12. :class:`circuits.core.components.BaseComponent`. It marks the method as a
  13. handler for the events passed as arguments to the ``@handler`` decorator.
  14. The events are specified by their name.
  15. The decorated method's arguments must match the arguments passed to the
  16. :class:`circuits.core.events.Event` on creation. Optionally, the
  17. method may have an additional first argument named *event*. If declared,
  18. the event object that caused the handler to be invoked is assigned to it.
  19. By default, the handler is invoked by the component's root
  20. :class:`~.manager.Manager` for events that are propagated on the channel
  21. determined by the BaseComponent's *channel* attribute.
  22. This may be overridden by specifying a different channel as a keyword
  23. parameter of the decorator (``channel=...``).
  24. Keyword argument ``priority`` influences the order in which handlers
  25. for a specific event are invoked. The higher the priority, the earlier
  26. the handler is executed.
  27. If you want to override a handler defined in a base class of your
  28. component, you must specify ``override=True``, else your method becomes
  29. an additional handler for the event.
  30. **Return value**
  31. Normally, the results returned by the handlers for an event are simply
  32. collected in the :class:`circuits.core.events.Event`'s :attr:`value`
  33. attribute. As a special case, a handler may return a
  34. :class:`types.GeneratorType`. This signals to the dispatcher that the
  35. handler isn't ready to deliver a result yet.
  36. Rather, it has interrupted it's execution with a ``yield None``
  37. statement, thus preserving its current execution state.
  38. The dispatcher saves the returned generator object as a task.
  39. All tasks are reexamined (i.e. their :meth:`next()` method is invoked)
  40. when the pending events have been executed.
  41. This feature avoids an unnecessarily complicated chaining of event
  42. handlers. Imagine a handler A that needs the results from firing an
  43. event E in order to complete. Then without this feature, the final
  44. action of A would be to fire event E, and another handler for
  45. an event ``SuccessE`` would be required to complete handler A's
  46. operation, now having the result from invoking E available
  47. (actually it's even a bit more complicated).
  48. Using this "suspend" feature, the handler simply fires event E and
  49. then yields ``None`` until e.g. it finds a result in E's :attr:`value`
  50. attribute. For the simplest scenario, there even is a utility
  51. method :meth:`circuits.core.manager.Manager.callEvent` that combines
  52. firing and waiting.
  53. """
  54. def wrapper(f):
  55. if names and isinstance(names[0], bool) and not names[0]:
  56. f.handler = False
  57. return f
  58. f.handler = True
  59. f.names = names
  60. f.priority = kwargs.get("priority", 0)
  61. f.channel = kwargs.get("channel", None)
  62. f.override = kwargs.get("override", False)
  63. args = getargspec(f)[0]
  64. if args and args[0] == "self":
  65. del args[0]
  66. f.event = getattr(f, "event", bool(args and args[0] == "event"))
  67. return f
  68. return wrapper
  69. class Unknown(object):
  70. """Unknown Dummy Component"""
  71. def reprhandler(handler):
  72. format = "<handler[%s.%s] (%s.%s)>"
  73. channel = getattr(handler, "channel", "*")
  74. if channel is None:
  75. channel = "*"
  76. from circuits.core.manager import Manager
  77. if isinstance(channel, Manager):
  78. channel = "<instance of " + channel.__class__.__name__ + ">"
  79. names = ".".join(handler.names)
  80. instance = getattr(
  81. handler, "im_self", getattr(
  82. handler, "__self__", Unknown()
  83. )
  84. ).__class__.__name__
  85. method = handler.__name__
  86. return format % (channel, names, instance, method)
  87. class HandlerMetaClass(type):
  88. def __init__(cls, name, bases, ns):
  89. super(HandlerMetaClass, cls).__init__(name, bases, ns)
  90. callables = (x for x in ns.items() if isinstance(x[1], Callable))
  91. for name, callable in callables:
  92. if not (name.startswith("_") or hasattr(callable, "handler")):
  93. setattr(cls, name, handler(name)(callable))