| import weakref |
| try: |
| set |
| except NameError: |
| from sets import Set as set # Python 2.3 fallback |
| |
| from django.dispatch import saferef |
| |
| WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) |
| |
| def _make_id(target): |
| if hasattr(target, 'im_func'): |
| return (id(target.im_self), id(target.im_func)) |
| return id(target) |
| |
| class Signal(object): |
| """Base class for all signals |
| |
| Internal attributes: |
| receivers -- { receriverkey (id) : weakref(receiver) } |
| """ |
| |
| def __init__(self, providing_args=None): |
| """providing_args -- A list of the arguments this signal can pass along in |
| a send() call. |
| """ |
| self.receivers = [] |
| if providing_args is None: |
| providing_args = [] |
| self.providing_args = set(providing_args) |
| |
| def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): |
| """Connect receiver to sender for signal |
| |
| receiver -- a function or an instance method which is to |
| receive signals. Receivers must be |
| hashable objects. |
| |
| if weak is True, then receiver must be weak-referencable |
| (more precisely saferef.safeRef() must be able to create |
| a reference to the receiver). |
| |
| Receivers must be able to accept keyword arguments. |
| |
| If receivers have a dispatch_uid attribute, the receiver will |
| not be added if another receiver already exists with that |
| dispatch_uid. |
| |
| sender -- the sender to which the receiver should respond |
| Must either be of type Signal, or None to receive events |
| from any sender. |
| |
| weak -- whether to use weak references to the receiver |
| By default, the module will attempt to use weak |
| references to the receiver objects. If this parameter |
| is false, then strong references will be used. |
| |
| dispatch_uid -- an identifier used to uniquely identify a particular |
| instance of a receiver. This will usually be a string, though it |
| may be anything hashable. |
| |
| returns None |
| """ |
| from django.conf import settings |
| |
| if settings.DEBUG: |
| import inspect |
| assert inspect.getargspec(receiver)[2] is not None, \ |
| "Signal receivers must accept keyword arguments (**kwargs)." |
| |
| if dispatch_uid: |
| lookup_key = (dispatch_uid, _make_id(sender)) |
| else: |
| lookup_key = (_make_id(receiver), _make_id(sender)) |
| |
| if weak: |
| receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) |
| |
| for r_key, _ in self.receivers: |
| if r_key == lookup_key: |
| break |
| else: |
| self.receivers.append((lookup_key, receiver)) |
| |
| def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): |
| """Disconnect receiver from sender for signal |
| |
| receiver -- the registered receiver to disconnect. May be none if |
| dispatch_uid is specified. |
| sender -- the registered sender to disconnect |
| weak -- the weakref state to disconnect |
| dispatch_uid -- the unique identifier of the receiver to disconnect |
| |
| disconnect reverses the process of connect. |
| |
| If weak references are used, disconnect need not be called. |
| The receiver will be remove from dispatch automatically. |
| |
| returns None |
| """ |
| |
| if dispatch_uid: |
| lookup_key = (dispatch_uid, _make_id(sender)) |
| else: |
| lookup_key = (_make_id(receiver), _make_id(sender)) |
| |
| for idx, (r_key, _) in enumerate(self.receivers): |
| if r_key == lookup_key: |
| del self.receivers[idx] |
| |
| def send(self, sender, **named): |
| """Send signal from sender to all connected receivers. |
| |
| sender -- the sender of the signal |
| Either a specific object or None. |
| |
| named -- named arguments which will be passed to receivers. |
| |
| Returns a list of tuple pairs [(receiver, response), ... ]. |
| |
| If any receiver raises an error, the error propagates back |
| through send, terminating the dispatch loop, so it is quite |
| possible to not have all receivers called if a raises an |
| error. |
| """ |
| |
| responses = [] |
| if not self.receivers: |
| return responses |
| |
| for receiver in self._live_receivers(_make_id(sender)): |
| response = receiver(signal=self, sender=sender, **named) |
| responses.append((receiver, response)) |
| return responses |
| |
| def send_robust(self, sender, **named): |
| """Send signal from sender to all connected receivers catching errors |
| |
| sender -- the sender of the signal |
| Can be any python object (normally one registered with |
| a connect if you actually want something to occur). |
| |
| named -- named arguments which will be passed to receivers. |
| These arguments must be a subset of the argument names |
| defined in providing_args. |
| |
| Return a list of tuple pairs [(receiver, response), ... ], |
| may raise DispatcherKeyError |
| |
| if any receiver raises an error (specifically any subclass of Exception), |
| the error instance is returned as the result for that receiver. |
| """ |
| |
| responses = [] |
| if not self.receivers: |
| return responses |
| |
| # Call each receiver with whatever arguments it can accept. |
| # Return a list of tuple pairs [(receiver, response), ... ]. |
| for receiver in self._live_receivers(_make_id(sender)): |
| try: |
| response = receiver(signal=self, sender=sender, **named) |
| except Exception, err: |
| responses.append((receiver, err)) |
| else: |
| responses.append((receiver, response)) |
| return responses |
| |
| def _live_receivers(self, senderkey): |
| """Filter sequence of receivers to get resolved, live receivers |
| |
| This checks for weak references |
| and resolves them, then returning only live |
| receivers. |
| """ |
| none_senderkey = _make_id(None) |
| |
| for (receiverkey, r_senderkey), receiver in self.receivers: |
| if r_senderkey == none_senderkey or r_senderkey == senderkey: |
| if isinstance(receiver, WEAKREF_TYPES): |
| # Dereference the weak reference. |
| receiver = receiver() |
| if receiver is not None: |
| yield receiver |
| else: |
| yield receiver |
| |
| def _remove_receiver(self, receiver): |
| """Remove dead receivers from connections.""" |
| |
| to_remove = [] |
| for key, connected_receiver in self.receivers: |
| if connected_receiver == receiver: |
| to_remove.append(key) |
| for key in to_remove: |
| for idx, (r_key, _) in enumerate(self.receivers): |
| if r_key == key: |
| del self.receivers[idx] |