PageRenderTime 48ms CodeModel.GetById 33ms app.highlight 11ms RepoModel.GetById 2ms app.codeStats 0ms

/circuits/core/components.py

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