/circuits/core/components.py

https://bitbucket.org/prologic/circuits/ · Python · 247 lines · 130 code · 44 blank · 73 comment · 35 complexity · 698c0a2c48c788d47345d2aa14001d45 MD5 · raw file

  1. # Package: components
  2. # Date: 11th April 2010
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """
  5. This module defines the BaseComponent and its subclass Component.
  6. """
  7. from itertools import chain
  8. from types import MethodType
  9. from inspect import getmembers
  10. from collections import Callable
  11. from .manager import Manager
  12. from .handlers import handler, HandlerMetaClass
  13. from .events import Event, registered, unregistered
  14. class prepare_unregister(Event):
  15. """
  16. This event is fired when a component is about to be unregistered
  17. from the component tree. Unregistering a component actually
  18. detaches the complete subtree that the unregistered component
  19. is the root of. Components that need to know if they
  20. are removed from the main tree (e.g. because they maintain
  21. relationships to other components in the tree) handle this
  22. event, check if the component being unregistered is one
  23. of their ancestors and act accordingly.
  24. :param component: the component that will be unregistered
  25. :type type: :class:`~.BaseComponent`
  26. """
  27. complete = True
  28. def __init__(self, *args, **kwargs):
  29. super(prepare_unregister, self).__init__(*args, **kwargs)
  30. def in_subtree(self, component):
  31. """
  32. Convenience method that checks if the given *component*
  33. is in the subtree that is about to be detached.
  34. """
  35. while True:
  36. if component == self.args[0]:
  37. return True
  38. if component == component.root:
  39. return False
  40. component = component.parent
  41. class BaseComponent(Manager):
  42. """
  43. This is the base class for all components in a circuits based application.
  44. Components can (and should, except for root components) be registered
  45. with a parent component.
  46. BaseComponents can declare methods as event handlers using the
  47. handler decoration (see :func:`circuits.core.handlers.handler`). The
  48. handlers are invoked for matching events from the
  49. component's channel (specified as the component's ``channel`` attribute).
  50. BaseComponents inherit from :class:`circuits.core.manager.Manager`.
  51. This provides components with the
  52. :func:`circuits.core.manager.Manager.fireEvent` method that can
  53. be used to fire events as the result of some computation.
  54. Apart from the ``fireEvent()`` method, the Manager nature is important
  55. for root components that are started or run.
  56. :ivar channel: a component can be associated with a specific channel
  57. by setting this attribute. This should either be done by
  58. specifying a class attribute *channel* in the derived class or by
  59. passing a keyword parameter *channel="..."* to *__init__*.
  60. If specified, the component's handlers receive events on the
  61. specified channel only, and events fired by the component will
  62. be sent on the specified channel (this behavior may be overridden,
  63. see :class:`~circuits.core.events.Event`, :meth:`~.fireEvent` and
  64. :func:`~circuits.core.handlers.handler`). By default, the channel
  65. attribute is set to "*", meaning that events are fired on all
  66. channels and received from all channels.
  67. """
  68. channel = "*"
  69. def __new__(cls, *args, **kwargs):
  70. self = super(BaseComponent, cls).__new__(cls)
  71. handlers = dict(
  72. [(k, v) for k, v in list(cls.__dict__.items())
  73. if getattr(v, "handler", False)]
  74. )
  75. overridden = lambda x: x in handlers and handlers[x].override
  76. for base in cls.__bases__:
  77. if issubclass(cls, base):
  78. for k, v in list(base.__dict__.items()):
  79. p1 = isinstance(v, Callable)
  80. p2 = getattr(v, "handler", False)
  81. p3 = overridden(k)
  82. if p1 and p2 and not p3:
  83. name = "%s_%s" % (base.__name__, k)
  84. method = MethodType(v, self)
  85. setattr(self, name, method)
  86. return self
  87. def __init__(self, *args, **kwargs):
  88. "initializes x; see x.__class__.__doc__ for signature"
  89. super(BaseComponent, self).__init__(*args, **kwargs)
  90. self.channel = kwargs.get("channel", self.channel) or "*"
  91. for k, v in getmembers(self):
  92. if getattr(v, "handler", False) is True:
  93. self.addHandler(v)
  94. # TODO: Document this feature. See Issue #88
  95. if v is not self and isinstance(v, BaseComponent) \
  96. and v not in ('parent', 'root'):
  97. v.register(self)
  98. if hasattr(self, "init") and isinstance(self.init, Callable):
  99. self.init(*args, **kwargs)
  100. @handler("prepare_unregister_complete", channel=self)
  101. def _on_prepare_unregister_complete(self, event, e, value):
  102. self._do_prepare_unregister_complete(event.parent, value)
  103. self.addHandler(_on_prepare_unregister_complete)
  104. def register(self, parent):
  105. """
  106. Inserts this component in the component tree as a child
  107. of the given *parent* node.
  108. :param parent: the parent component after registration has completed.
  109. :type parent: :class:`~.manager.Manager`
  110. This method fires a :class:`~.events.Registered` event to inform
  111. other components in the tree about the new member.
  112. """
  113. self.parent = parent
  114. self.root = parent.root
  115. # Make sure that structure is consistent before firing event
  116. # because event may be handled in a concurrent thread.
  117. if parent is not self:
  118. parent.registerChild(self)
  119. self._updateRoot(parent.root)
  120. self.fire(registered(self, self.parent))
  121. else:
  122. self._updateRoot(parent.root)
  123. return self
  124. def unregister(self):
  125. """
  126. Removes this component from the component tree.
  127. Removing a component from the component tree is a two stage process.
  128. First, the component is marked as to be removed, which prevents it
  129. from receiving further events, and a
  130. :class:`~.components.prepare_unregister` event is fired. This
  131. allows other components to e.g. release references to the component
  132. to be removed before it is actually removed from the component tree.
  133. After the processing of the ``prepare_unregister`` event has completed,
  134. the component is removed from the tree and an
  135. :class:`~.events.unregistered` event is fired.
  136. """
  137. if self.unregister_pending or self.parent == self:
  138. return self
  139. # tick shouldn't be called anymore, although component is still in tree
  140. self._unregister_pending = True
  141. self.root._cache_needs_refresh = True
  142. # Give components a chance to prepare for unregister
  143. evt = prepare_unregister(self)
  144. evt.complete_channels = (self,)
  145. self.fire(evt)
  146. return self
  147. @property
  148. def unregister_pending(self):
  149. return getattr(self, "_unregister_pending", False)
  150. def _do_prepare_unregister_complete(self, e, value):
  151. # Remove component from tree now
  152. delattr(self, "_unregister_pending")
  153. self.fire(unregistered(self, self.parent))
  154. if self.parent is not self:
  155. self.parent.unregisterChild(self)
  156. self.parent = self
  157. self._updateRoot(self)
  158. return self
  159. def _updateRoot(self, root):
  160. self.root = root
  161. for c in self.components:
  162. c._updateRoot(root)
  163. @classmethod
  164. def handlers(cls):
  165. """Returns a list of all event handlers for this Component"""
  166. return list(set(
  167. getattr(cls, k) for k in dir(cls)
  168. if getattr(getattr(cls, k), "handler", False)
  169. ))
  170. @classmethod
  171. def events(cls):
  172. """Returns a list of all events this Component listens to"""
  173. handlers = (
  174. getattr(cls, k).names for k in dir(cls)
  175. if getattr(getattr(cls, k), "handler", False)
  176. )
  177. return list(set(
  178. name for name in chain(*handlers)
  179. if not name.startswith("_")
  180. ))
  181. @classmethod
  182. def handles(cls, *names):
  183. """Returns True if all names are event handlers of this Component"""
  184. return all(name in cls.events() for name in names)
  185. Component = HandlerMetaClass("Component", (BaseComponent,), {})
  186. """
  187. If you use Component instead of BaseComponent as base class for your own
  188. component class, then all methods that are not marked as private
  189. (i.e: start with an underscore) are automatically decorated as handlers.
  190. The methods are invoked for all events from the component's channel
  191. where the event's name matches the method's name.
  192. """