Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.0.X] Fixed #11134: signals recievers that disconnect during their …

…processing no longer mess things up for other handlers. Thanks, Honza Kral. Backport of [10831] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@10832 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 10637a94cb215473a5aff3d1c7fa719a95d699ba 1 parent e001738
Jacob Kaplan-Moss authored May 20, 2009
171  django/dispatch/dispatcher.py
@@ -14,15 +14,21 @@ def _make_id(target):
14 14
     return id(target)
15 15
 
16 16
 class Signal(object):
17  
-    """Base class for all signals
  17
+    """
  18
+    Base class for all signals
18 19
     
19 20
     Internal attributes:
20  
-        receivers -- { receriverkey (id) : weakref(receiver) }
  21
+    
  22
+        receivers
  23
+            { receriverkey (id) : weakref(receiver) }
21 24
     """
22 25
     
23 26
     def __init__(self, providing_args=None):
24  
-        """providing_args -- A list of the arguments this signal can pass along in
25  
-                       a send() call.
  27
+        """
  28
+        Create a new signal.
  29
+        
  30
+        providing_args
  31
+            A list of the arguments this signal can pass along in a send() call.
26 32
         """
27 33
         self.receivers = []
28 34
         if providing_args is None:
@@ -30,36 +36,39 @@ def __init__(self, providing_args=None):
30 36
         self.providing_args = set(providing_args)
31 37
 
32 38
     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
33  
-        """Connect receiver to sender for signal
  39
+        """
  40
+        Connect receiver to sender for signal.
34 41
     
35  
-        receiver -- a function or an instance method which is to
36  
-            receive signals.  Receivers must be
37  
-            hashable objects.
  42
+        Arguments:
  43
+        
  44
+            receiver
  45
+                A function or an instance method which is to receive signals.
  46
+                Receivers must be hashable objects.
38 47
 
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).
  48
+                if weak is True, then receiver must be weak-referencable (more
  49
+                precisely saferef.safeRef() must be able to create a reference
  50
+                to the receiver).
42 51
         
43  
-            Receivers must be able to accept keyword arguments.
  52
+                Receivers must be able to accept keyword arguments.
44 53
 
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.
  54
+                If receivers have a dispatch_uid attribute, the receiver will
  55
+                not be added if another receiver already exists with that
  56
+                dispatch_uid.
48 57
 
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.
  58
+            sender
  59
+                The sender to which the receiver should respond Must either be
  60
+                of type Signal, or None to receive events from any sender.
52 61
 
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.
  62
+            weak
  63
+                Whether to use weak references to the receiver By default, the
  64
+                module will attempt to use weak references to the receiver
  65
+                objects. If this parameter is false, then strong references will
  66
+                be used.
57 67
         
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
  68
+            dispatch_uid
  69
+                An identifier used to uniquely identify a particular instance of
  70
+                a receiver. This will usually be a string, though it may be
  71
+                anything hashable.
63 72
         """
64 73
         from django.conf import settings
65 74
         
@@ -99,22 +108,27 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
99 108
             self.receivers.append((lookup_key, receiver))
100 109
 
101 110
     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 111
         """
  112
+        Disconnect receiver from sender for signal.
117 113
 
  114
+        If weak references are used, disconnect need not be called. The receiver
  115
+        will be remove from dispatch automatically.
  116
+    
  117
+        Arguments:
  118
+        
  119
+            receiver
  120
+                The registered receiver to disconnect. May be none if
  121
+                dispatch_uid is specified.
  122
+            
  123
+            sender
  124
+                The registered sender to disconnect
  125
+            
  126
+            weak
  127
+                The weakref state to disconnect
  128
+            
  129
+            dispatch_uid
  130
+                the unique identifier of the receiver to disconnect
  131
+        """
118 132
         if dispatch_uid:
119 133
             lookup_key = (dispatch_uid, _make_id(sender))
120 134
         else:
@@ -127,21 +141,23 @@ def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
127 141
                 break
128 142
 
129 143
     def send(self, sender, **named):
130  
-        """Send signal from sender to all connected receivers.
  144
+        """
  145
+        Send signal from sender to all connected receivers.
131 146
 
132  
-        sender -- the sender of the signal
133  
-            Either a specific object or None.
  147
+        If any receiver raises an error, the error propagates back through send,
  148
+        terminating the dispatch loop, so it is quite possible to not have all
  149
+        receivers called if a raises an error.
  150
+
  151
+        Arguments:
  152
+        
  153
+            sender
  154
+                The sender of the signal Either a specific object or None.
134 155
     
135  
-        named -- named arguments which will be passed to receivers.
  156
+            named
  157
+                Named arguments which will be passed to receivers.
136 158
 
137 159
         Returns a list of tuple pairs [(receiver, response), ... ].
138  
-
139  
-        If any receiver raises an error, the error propagates back
140  
-        through send, terminating the dispatch loop, so it is quite
141  
-        possible to not have all receivers called if a raises an
142  
-        error.
143 160
         """
144  
-
145 161
         responses = []
146 162
         if not self.receivers:
147 163
             return responses
@@ -152,23 +168,28 @@ def send(self, sender, **named):
152 168
         return responses
153 169
 
154 170
     def send_robust(self, sender, **named):
155  
-        """Send signal from sender to all connected receivers catching errors
156  
-
157  
-        sender -- the sender of the signal
158  
-            Can be any python object (normally one registered with
159  
-            a connect if you actually want something to occur).
160  
-
161  
-        named -- named arguments which will be passed to receivers.
162  
-            These arguments must be a subset of the argument names
163  
-            defined in providing_args.
164  
-
165  
-        Return a list of tuple pairs [(receiver, response), ... ],
166  
-        may raise DispatcherKeyError
167  
-
168  
-        if any receiver raises an error (specifically any subclass of Exception),
169  
-        the error instance is returned as the result for that receiver.
170 171
         """
  172
+        Send signal from sender to all connected receivers catching errors.
171 173
 
  174
+        Arguments:
  175
+        
  176
+            sender
  177
+                The sender of the signal Can be any python object (normally one
  178
+                registered with a connect if you actually want something to
  179
+                occur).
  180
+
  181
+            named
  182
+                Named arguments which will be passed to receivers. These
  183
+                arguments must be a subset of the argument names defined in
  184
+                providing_args.
  185
+
  186
+        Return a list of tuple pairs [(receiver, response), ... ]. May raise
  187
+        DispatcherKeyError.
  188
+
  189
+        if any receiver raises an error (specifically any subclass of
  190
+        Exception), the error instance is returned as the result for that
  191
+        receiver.
  192
+        """
172 193
         responses = []
173 194
         if not self.receivers:
174 195
             return responses
@@ -185,13 +206,14 @@ def send_robust(self, sender, **named):
185 206
         return responses
186 207
 
187 208
     def _live_receivers(self, senderkey):
188  
-        """Filter sequence of receivers to get resolved, live receivers
  209
+        """
  210
+        Filter sequence of receivers to get resolved, live receivers.
189 211
 
190  
-        This checks for weak references
191  
-        and resolves them, then returning only live
192  
-        receivers.
  212
+        This checks for weak references and resolves them, then returning only
  213
+        live receivers.
193 214
         """
194 215
         none_senderkey = _make_id(None)
  216
+        receivers = []
195 217
 
196 218
         for (receiverkey, r_senderkey), receiver in self.receivers:
197 219
             if r_senderkey == none_senderkey or r_senderkey == senderkey:
@@ -199,12 +221,15 @@ def _live_receivers(self, senderkey):
199 221
                     # Dereference the weak reference.
200 222
                     receiver = receiver()
201 223
                     if receiver is not None:
202  
-                        yield receiver
  224
+                        receivers.append(receiver)
203 225
                 else:
204  
-                    yield receiver
  226
+                    receivers.append(receiver)
  227
+        return receivers
205 228
 
206 229
     def _remove_receiver(self, receiver):
207  
-        """Remove dead receivers from connections."""
  230
+        """
  231
+        Remove dead receivers from connections.
  232
+        """
208 233
 
209 234
         to_remove = []
210 235
         for key, connected_receiver in self.receivers:
28  tests/modeltests/signals/tests.py
... ...
@@ -0,0 +1,28 @@
  1
+from django.db.models import signals
  2
+from django.test import TestCase
  3
+from modeltests.signals.models import Person
  4
+
  5
+class MyReceiver(object):
  6
+    def __init__(self, param):
  7
+        self.param = param
  8
+        self._run = False
  9
+
  10
+    def __call__(self, signal, sender, **kwargs):
  11
+        self._run = True
  12
+        signal.disconnect(receiver=self, sender=sender)
  13
+
  14
+class SignalTests(TestCase):
  15
+    def test_disconnect_in_dispatch(self):
  16
+        """
  17
+        Test that signals that disconnect when being called don't mess future
  18
+        dispatching.
  19
+        """
  20
+        a, b = MyReceiver(1), MyReceiver(2)
  21
+        signals.post_save.connect(sender=Person, receiver=a)
  22
+        signals.post_save.connect(sender=Person, receiver=b)
  23
+        p = Person.objects.create(first_name='John', last_name='Smith')
  24
+        
  25
+        self.failUnless(a._run)
  26
+        self.failUnless(b._run)
  27
+        self.assertEqual(signals.post_save.receivers, [])
  28
+        

0 notes on commit 10637a9

Please sign in to comment.
Something went wrong with that request. Please try again.