PageRenderTime 128ms CodeModel.GetById 111ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/django/dispatch/dispatcher.py

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