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
This commit is contained in:
parent
ca365b4113
commit
c935d7ffe3
|
@ -14,15 +14,21 @@ def _make_id(target):
|
||||||
return id(target)
|
return id(target)
|
||||||
|
|
||||||
class Signal(object):
|
class Signal(object):
|
||||||
"""Base class for all signals
|
"""
|
||||||
|
Base class for all signals
|
||||||
|
|
||||||
Internal attributes:
|
Internal attributes:
|
||||||
receivers -- { receriverkey (id) : weakref(receiver) }
|
|
||||||
|
receivers
|
||||||
|
{ receriverkey (id) : weakref(receiver) }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, providing_args=None):
|
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 = []
|
self.receivers = []
|
||||||
if providing_args is None:
|
if providing_args is None:
|
||||||
|
@ -30,36 +36,39 @@ class Signal(object):
|
||||||
self.providing_args = set(providing_args)
|
self.providing_args = set(providing_args)
|
||||||
|
|
||||||
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
|
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
|
Arguments:
|
||||||
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.
|
receiver
|
||||||
|
A function or an instance method which is to receive signals.
|
||||||
|
Receivers must be hashable objects.
|
||||||
|
|
||||||
If receivers have a dispatch_uid attribute, the receiver will
|
if weak is True, then receiver must be weak-referencable (more
|
||||||
not be added if another receiver already exists with that
|
precisely saferef.safeRef() must be able to create a reference
|
||||||
dispatch_uid.
|
to the receiver).
|
||||||
|
|
||||||
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
|
Receivers must be able to accept keyword arguments.
|
||||||
instance of a receiver. This will usually be a string, though it
|
|
||||||
may be anything hashable.
|
|
||||||
|
|
||||||
returns None
|
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
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -99,22 +108,27 @@ class Signal(object):
|
||||||
self.receivers.append((lookup_key, receiver))
|
self.receivers.append((lookup_key, receiver))
|
||||||
|
|
||||||
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
|
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:
|
if dispatch_uid:
|
||||||
lookup_key = (dispatch_uid, _make_id(sender))
|
lookup_key = (dispatch_uid, _make_id(sender))
|
||||||
else:
|
else:
|
||||||
|
@ -127,21 +141,23 @@ class Signal(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
def send(self, sender, **named):
|
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
|
If any receiver raises an error, the error propagates back through send,
|
||||||
Either a specific object or None.
|
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), ... ].
|
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 = []
|
responses = []
|
||||||
if not self.receivers:
|
if not self.receivers:
|
||||||
return responses
|
return responses
|
||||||
|
@ -152,23 +168,28 @@ class Signal(object):
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def send_robust(self, sender, **named):
|
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 = []
|
responses = []
|
||||||
if not self.receivers:
|
if not self.receivers:
|
||||||
return responses
|
return responses
|
||||||
|
@ -185,13 +206,14 @@ class Signal(object):
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def _live_receivers(self, senderkey):
|
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
|
This checks for weak references and resolves them, then returning only
|
||||||
and resolves them, then returning only live
|
live receivers.
|
||||||
receivers.
|
|
||||||
"""
|
"""
|
||||||
none_senderkey = _make_id(None)
|
none_senderkey = _make_id(None)
|
||||||
|
receivers = []
|
||||||
|
|
||||||
for (receiverkey, r_senderkey), receiver in self.receivers:
|
for (receiverkey, r_senderkey), receiver in self.receivers:
|
||||||
if r_senderkey == none_senderkey or r_senderkey == senderkey:
|
if r_senderkey == none_senderkey or r_senderkey == senderkey:
|
||||||
|
@ -199,12 +221,15 @@ class Signal(object):
|
||||||
# Dereference the weak reference.
|
# Dereference the weak reference.
|
||||||
receiver = receiver()
|
receiver = receiver()
|
||||||
if receiver is not None:
|
if receiver is not None:
|
||||||
yield receiver
|
receivers.append(receiver)
|
||||||
else:
|
else:
|
||||||
yield receiver
|
receivers.append(receiver)
|
||||||
|
return receivers
|
||||||
|
|
||||||
def _remove_receiver(self, receiver):
|
def _remove_receiver(self, receiver):
|
||||||
"""Remove dead receivers from connections."""
|
"""
|
||||||
|
Remove dead receivers from connections.
|
||||||
|
"""
|
||||||
|
|
||||||
to_remove = []
|
to_remove = []
|
||||||
for key, connected_receiver in self.receivers:
|
for key, connected_receiver in self.receivers:
|
||||||
|
|
|
@ -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, [])
|
||||||
|
|
Loading…
Reference in New Issue