| 1 |
import weakref |
|---|
| 2 |
try: |
|---|
| 3 |
set |
|---|
| 4 |
except NameError: |
|---|
| 5 |
from sets import Set as set # Python 2.3 fallback |
|---|
| 6 |
|
|---|
| 7 |
from django.dispatch import saferef |
|---|
| 8 |
|
|---|
| 9 |
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) |
|---|
| 10 |
|
|---|
| 11 |
def _make_id(target): |
|---|
| 12 |
if hasattr(target, 'im_func'): |
|---|
| 13 |
return (id(target.im_self), id(target.im_func)) |
|---|
| 14 |
return id(target) |
|---|
| 15 |
|
|---|
| 16 |
class Signal(object): |
|---|
| 17 |
"""Base class for all signals |
|---|
| 18 |
|
|---|
| 19 |
Internal attributes: |
|---|
| 20 |
receivers -- { receriverkey (id) : weakref(receiver) } |
|---|
| 21 |
""" |
|---|
| 22 |
|
|---|
| 23 |
def __init__(self, providing_args=None): |
|---|
| 24 |
"""providing_args -- A list of the arguments this signal can pass along in |
|---|
| 25 |
a send() call. |
|---|
| 26 |
""" |
|---|
| 27 |
self.receivers = [] |
|---|
| 28 |
if providing_args is None: |
|---|
| 29 |
providing_args = [] |
|---|
| 30 |
self.providing_args = set(providing_args) |
|---|
| 31 |
|
|---|
| 32 |
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): |
|---|
| 33 |
"""Connect receiver to sender for signal |
|---|
| 34 |
|
|---|
| 35 |
receiver -- a function or an instance method which is to |
|---|
| 36 |
receive signals. Receivers must be |
|---|
| 37 |
hashable objects. |
|---|
| 38 |
|
|---|
| 39 |
if weak is True, then receiver must be weak-referencable |
|---|
| 40 |
(more precisely saferef.safeRef() must be able to create |
|---|
| 41 |
a reference to the receiver). |
|---|
| 42 |
|
|---|
| 43 |
Receivers must be able to accept keyword arguments. |
|---|
| 44 |
|
|---|
| 45 |
If receivers have a dispatch_uid attribute, the receiver will |
|---|
| 46 |
not be added if another receiver already exists with that |
|---|
| 47 |
dispatch_uid. |
|---|
| 48 |
|
|---|
| 49 |
sender -- the sender to which the receiver should respond |
|---|
| 50 |
Must either be of type Signal, or None to receive events |
|---|
| 51 |
from any sender. |
|---|
| 52 |
|
|---|
| 53 |
weak -- whether to use weak references to the receiver |
|---|
| 54 |
By default, the module will attempt to use weak |
|---|
| 55 |
references to the receiver objects. If this parameter |
|---|
| 56 |
is false, then strong references will be used. |
|---|
| 57 |
|
|---|
| 58 |
dispatch_uid -- an identifier used to uniquely identify a particular |
|---|
| 59 |
instance of a receiver. This will usually be a string, though it |
|---|
| 60 |
may be anything hashable. |
|---|
| 61 |
|
|---|
| 62 |
returns None |
|---|
| 63 |
""" |
|---|
| 64 |
from django.conf import settings |
|---|
| 65 |
|
|---|
| 66 |
# If DEBUG is on, check that we got a good receiver |
|---|
| 67 |
if settings.DEBUG: |
|---|
| 68 |
import inspect |
|---|
| 69 |
assert callable(receiver), "Signal receivers must be callable." |
|---|
| 70 |
|
|---|
| 71 |
# Check for **kwargs |
|---|
| 72 |
# Not all callables are inspectable with getargspec, so we'll |
|---|
| 73 |
# try a couple different ways but in the end fall back on assuming |
|---|
| 74 |
# it is -- we don't want to prevent registration of valid but weird |
|---|
| 75 |
# callables. |
|---|
| 76 |
try: |
|---|
| 77 |
argspec = inspect.getargspec(receiver) |
|---|
| 78 |
except TypeError: |
|---|
| 79 |
try: |
|---|
| 80 |
argspec = inspect.getargspec(receiver.__call__) |
|---|
| 81 |
except (TypeError, AttributeError): |
|---|
| 82 |
argspec = None |
|---|
| 83 |
if argspec: |
|---|
| 84 |
assert argspec[2] is not None, \ |
|---|
| 85 |
"Signal receivers must accept keyword arguments (**kwargs)." |
|---|
| 86 |
|
|---|
| 87 |
if dispatch_uid: |
|---|
| 88 |
lookup_key = (dispatch_uid, _make_id(sender)) |
|---|
| 89 |
else: |
|---|
| 90 |
lookup_key = (_make_id(receiver), _make_id(sender)) |
|---|
| 91 |
|
|---|
| 92 |
if weak: |
|---|
| 93 |
receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) |
|---|
| 94 |
|
|---|
| 95 |
for r_key, _ in self.receivers: |
|---|
| 96 |
if r_key == lookup_key: |
|---|
| 97 |
break |
|---|
| 98 |
else: |
|---|
| 99 |
self.receivers.append((lookup_key, receiver)) |
|---|
| 100 |
|
|---|
| 101 |
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): |
|---|
| 102 |
"""Disconnect receiver from sender for signal |
|---|
| 103 |
|
|---|
| 104 |
receiver -- the registered receiver to disconnect. May be none if |
|---|
| 105 |
dispatch_uid is specified. |
|---|
| 106 |
sender -- the registered sender to disconnect |
|---|
| 107 |
weak -- the weakref state to disconnect |
|---|
| 108 |
dispatch_uid -- the unique identifier of the receiver to disconnect |
|---|
| 109 |
|
|---|
| 110 |
disconnect reverses the process of connect. |
|---|
| 111 |
|
|---|
| 112 |
If weak references are used, disconnect need not be called. |
|---|
| 113 |
The receiver will be remove from dispatch automatically. |
|---|
| 114 |
|
|---|
| 115 |
returns None |
|---|
| 116 |
""" |
|---|
| 117 |
|
|---|
| 118 |
if dispatch_uid: |
|---|
| 119 |
lookup_key = (dispatch_uid, _make_id(sender)) |
|---|
| 120 |
else: |
|---|
| 121 |
lookup_key = (_make_id(receiver), _make_id(sender)) |
|---|
| 122 |
|
|---|
| 123 |
for idx, (r_key, _) in enumerate(self.receivers): |
|---|
| 124 |
if r_key == lookup_key: |
|---|
| 125 |
del self.receivers[idx] |
|---|
| 126 |
|
|---|
| 127 |
def send(self, sender, **named): |
|---|
| 128 |
"""Send signal from sender to all connected receivers. |
|---|
| 129 |
|
|---|
| 130 |
sender -- the sender of the signal |
|---|
| 131 |
Either a specific object or None. |
|---|
| 132 |
|
|---|
| 133 |
named -- named arguments which will be passed to receivers. |
|---|
| 134 |
|
|---|
| 135 |
Returns a list of tuple pairs [(receiver, response), ... ]. |
|---|
| 136 |
|
|---|
| 137 |
If any receiver raises an error, the error propagates back |
|---|
| 138 |
through send, terminating the dispatch loop, so it is quite |
|---|
| 139 |
possible to not have all receivers called if a raises an |
|---|
| 140 |
error. |
|---|
| 141 |
""" |
|---|
| 142 |
|
|---|
| 143 |
responses = [] |
|---|
| 144 |
if not self.receivers: |
|---|
| 145 |
return responses |
|---|
| 146 |
|
|---|
| 147 |
for receiver in self._live_receivers(_make_id(sender)): |
|---|
| 148 |
response = receiver(signal=self, sender=sender, **named) |
|---|
| 149 |
responses.append((receiver, response)) |
|---|
| 150 |
return responses |
|---|
| 151 |
|
|---|
| 152 |
def send_robust(self, sender, **named): |
|---|
| 153 |
"""Send signal from sender to all connected receivers catching errors |
|---|
| 154 |
|
|---|
| 155 |
sender -- the sender of the signal |
|---|
| 156 |
Can be any python object (normally one registered with |
|---|
| 157 |
a connect if you actually want something to occur). |
|---|
| 158 |
|
|---|
| 159 |
named -- named arguments which will be passed to receivers. |
|---|
| 160 |
These arguments must be a subset of the argument names |
|---|
| 161 |
defined in providing_args. |
|---|
| 162 |
|
|---|
| 163 |
Return a list of tuple pairs [(receiver, response), ... ], |
|---|
| 164 |
may raise DispatcherKeyError |
|---|
| 165 |
|
|---|
| 166 |
if any receiver raises an error (specifically any subclass of Exception), |
|---|
| 167 |
the error instance is returned as the result for that receiver. |
|---|
| 168 |
""" |
|---|
| 169 |
|
|---|
| 170 |
responses = [] |
|---|
| 171 |
if not self.receivers: |
|---|
| 172 |
return responses |
|---|
| 173 |
|
|---|
| 174 |
# Call each receiver with whatever arguments it can accept. |
|---|
| 175 |
# Return a list of tuple pairs [(receiver, response), ... ]. |
|---|
| 176 |
for receiver in self._live_receivers(_make_id(sender)): |
|---|
| 177 |
try: |
|---|
| 178 |
response = receiver(signal=self, sender=sender, **named) |
|---|
| 179 |
except Exception, err: |
|---|
| 180 |
responses.append((receiver, err)) |
|---|
| 181 |
else: |
|---|
| 182 |
responses.append((receiver, response)) |
|---|
| 183 |
return responses |
|---|
| 184 |
|
|---|
| 185 |
def _live_receivers(self, senderkey): |
|---|
| 186 |
"""Filter sequence of receivers to get resolved, live receivers |
|---|
| 187 |
|
|---|
| 188 |
This checks for weak references |
|---|
| 189 |
and resolves them, then returning only live |
|---|
| 190 |
receivers. |
|---|
| 191 |
""" |
|---|
| 192 |
none_senderkey = _make_id(None) |
|---|
| 193 |
|
|---|
| 194 |
for (receiverkey, r_senderkey), receiver in self.receivers: |
|---|
| 195 |
if r_senderkey == none_senderkey or r_senderkey == senderkey: |
|---|
| 196 |
if isinstance(receiver, WEAKREF_TYPES): |
|---|
| 197 |
# Dereference the weak reference. |
|---|
| 198 |
receiver = receiver() |
|---|
| 199 |
if receiver is not None: |
|---|
| 200 |
yield receiver |
|---|
| 201 |
else: |
|---|
| 202 |
yield receiver |
|---|
| 203 |
|
|---|
| 204 |
def _remove_receiver(self, receiver): |
|---|
| 205 |
"""Remove dead receivers from connections.""" |
|---|
| 206 |
|
|---|
| 207 |
to_remove = [] |
|---|
| 208 |
for key, connected_receiver in self.receivers: |
|---|
| 209 |
if connected_receiver == receiver: |
|---|
| 210 |
to_remove.append(key) |
|---|
| 211 |
for key in to_remove: |
|---|
| 212 |
for idx, (r_key, _) in enumerate(self.receivers): |
|---|
| 213 |
if r_key == key: |
|---|
| 214 |
del self.receivers[idx] |
|---|