summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Kaplan-Moss <jacob@jacobian.org>2009-05-20 16:13:55 +0000
committerJacob Kaplan-Moss <jacob@jacobian.org>2009-05-20 16:13:55 +0000
commitc935d7ffe37cf9376aa30cb74c6fdaffe346255f (patch)
tree540480b7b2c58502e5b770b274ff58fb35378c9d
parentca365b4113b89e269d8a2f731a288cff5be97422 (diff)
downloaddjango-c935d7ffe37cf9376aa30cb74c6fdaffe346255f.tar.gz
Fixed #11134: signals recievers that disconnect during their processing no longer mess things up for other handlers. Thanks, Honza Kral.
While I was at it I also cleaned up the formatting of the docstrings a bit. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10831 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/dispatch/dispatcher.py171
-rw-r--r--tests/modeltests/signals/tests.py28
2 files changed, 126 insertions, 73 deletions
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index 07377d6411..e9198fc934 100644
--- a/django/dispatch/dispatcher.py
+++ b/django/dispatch/dispatcher.py
@@ -14,15 +14,21 @@ def _make_id(target):
return id(target)
class Signal(object):
- """Base class for all signals
+ """
+ Base class for all signals
Internal attributes:
- receivers -- { receriverkey (id) : weakref(receiver) }
+
+ 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.
+ """
+ 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:
@@ -30,36 +36,39 @@ class Signal(object):
self.providing_args = set(providing_args)
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
- """Connect receiver to sender for signal
+ """
+ Connect receiver to sender for signal.
- receiver -- a function or an instance method which is to
- receive signals. Receivers must be
- hashable objects.
+ 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).
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
+ 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
@@ -99,22 +108,27 @@ class Signal(object):
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
"""
+ 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:
@@ -127,21 +141,23 @@ class Signal(object):
break
def send(self, sender, **named):
- """Send signal from sender to all connected receivers.
+ """
+ Send signal from sender to all connected receivers.
- sender -- the sender of the signal
- Either a specific object or None.
+ 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.
+ 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
@@ -152,23 +168,28 @@ class Signal(object):
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.
"""
+ 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
@@ -185,13 +206,14 @@ class Signal(object):
return responses
def _live_receivers(self, senderkey):
- """Filter sequence of receivers to get resolved, live receivers
+ """
+ Filter sequence of receivers to get resolved, live receivers.
- This checks for weak references
- and resolves them, then returning only 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:
@@ -199,12 +221,15 @@ class Signal(object):
# Dereference the weak reference.
receiver = receiver()
if receiver is not None:
- yield receiver
+ receivers.append(receiver)
else:
- yield receiver
+ receivers.append(receiver)
+ return receivers
def _remove_receiver(self, receiver):
- """Remove dead receivers from connections."""
+ """
+ Remove dead receivers from connections.
+ """
to_remove = []
for key, connected_receiver in self.receivers:
diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
new file mode 100644
index 0000000000..329636c306
--- /dev/null
+++ b/tests/modeltests/signals/tests.py
@@ -0,0 +1,28 @@
+from django.db.models import signals
+from django.test import TestCase
+from modeltests.signals.models import Person
+
+class MyReceiver(object):
+ def __init__(self, param):
+ self.param = param
+ self._run = False
+
+ def __call__(self, signal, sender, **kwargs):
+ self._run = True
+ signal.disconnect(receiver=self, sender=sender)
+
+class SignalTests(TestCase):
+ def test_disconnect_in_dispatch(self):
+ """
+ Test that signals that disconnect when being called don't mess future
+ dispatching.
+ """
+ a, b = MyReceiver(1), MyReceiver(2)
+ signals.post_save.connect(sender=Person, receiver=a)
+ signals.post_save.connect(sender=Person, receiver=b)
+ p = Person.objects.create(first_name='John', last_name='Smith')
+
+ self.failUnless(a._run)
+ self.failUnless(b._run)
+ self.assertEqual(signals.post_save.receivers, [])
+