| import weakref |
| import threading |
| |
| 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): |
| """ |
| Create a new signal. |
| |
| 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) |
| self.lock = threading.Lock() |
| |
| def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): |
| """ |
| Connect receiver to sender for signal. |
| |
| Arguments: |
| |
| 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. |
| """ |
| from django.conf import settings |
| |
| # If DEBUG is on, check that we got a good receiver |
| if settings.DEBUG: |
| import inspect |
| assert callable(receiver), "Signal receivers must be callable." |
| |
| # Check for **kwargs |
| # Not all callables are inspectable with getargspec, so we'll |
| # try a couple different ways but in the end fall back on assuming |
| # it is -- we don't want to prevent registration of valid but weird |
| # callables. |
| try: |
| argspec = inspect.getargspec(receiver) |
| except TypeError: |
| try: |
| argspec = inspect.getargspec(receiver.__call__) |
| except (TypeError, AttributeError): |
| argspec = None |
| if argspec: |
| assert argspec[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) |
| |
| self.lock.acquire() |
| try: |
| for r_key, _ in self.receivers: |
| if r_key == lookup_key: |
| break |
| else: |
| self.receivers.append((lookup_key, receiver)) |
| finally: |
| self.lock.release() |
| |
| def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): |
| """ |
| Disconnect receiver from sender for signal. |
| |
| If weak references are used, disconnect need not be called. The receiver |
| will be remove from dispatch automatically. |
| |
| Arguments: |
| |
| 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 |
| """ |
| if dispatch_uid: |
| lookup_key = (dispatch_uid, _make_id(sender)) |
| else: |
| lookup_key = (_make_id(receiver), _make_id(sender)) |
| |
| self.lock.acquire() |
| try: |
| for index in xrange(len(self.receivers)): |
| (r_key, _) = self.receivers[index] |
| if r_key == lookup_key: |
| del self.receivers[index] |
| break |
| finally: |
| self.lock.release() |
| |
| def send(self, sender, **named): |
| """ |
| Send signal from sender to all connected receivers. |
| |
| 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. |
| |
| Arguments: |
| |
| 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), ... ]. |
| """ |
| 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. |
| |
| Arguments: |
| |
| 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) |
| receivers = [] |
| |
| 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: |
| receivers.append(receiver) |
| else: |
| receivers.append(receiver) |
| return receivers |
| |
| def _remove_receiver(self, receiver): |
| """ |
| Remove dead receivers from connections. |
| """ |
| |
| self.lock.acquire() |
| try: |
| to_remove = [] |
| for key, connected_receiver in self.receivers: |
| if connected_receiver == receiver: |
| to_remove.append(key) |
| for key in to_remove: |
| last_idx = len(self.receivers) - 1 |
| # enumerate in reverse order so that indexes are valid even |
| # after we delete some items |
| for idx, (r_key, _) in enumerate(reversed(self.receivers)): |
| if r_key == key: |
| del self.receivers[last_idx-idx] |
| finally: |
| self.lock.release() |
| |
| |
| def receiver(signal, **kwargs): |
| """ |
| A decorator for connecting receivers to signals. Used by passing in the |
| signal and keyword arguments to connect:: |
| |
| @receiver(post_save, sender=MyModel) |
| def signal_receiver(sender, **kwargs): |
| ... |
| |
| """ |
| def _decorator(func): |
| signal.connect(func, **kwargs) |
| return func |
| return _decorator |