mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Major refactoring of django.dispatch with an eye towards speed. The net result is that signals are up to 90% faster.
Though some attempts and backwards-compatibility were made, speed trumped compatibility. Thus, as usual, check BackwardsIncompatibleChanges for the complete list of backwards-incompatible changes. Thanks to Jeremy Dunck and Keith Busell for the bulk of the work; some ideas from Brian Herring's previous work (refs #4561) were incorporated. Documentation is, sigh, still forthcoming. Fixes #6814 and #3951 (with the new dispatch_uid argument to connect). git-svn-id: http://code.djangoproject.com/svn/django/trunk@8223 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
"""Multi-consumer multi-producer dispatching mechanism
|
||||
"""
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "Patrick K. O'Brien"
|
||||
__license__ = "BSD-style, see license.txt for details"
|
||||
|
||||
Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
|
||||
See license.txt for original license.
|
||||
|
||||
Heavily modified for Django's purposes.
|
||||
"""
|
||||
|
||||
from django.dispatch.dispatcher import Signal
|
||||
@@ -1,495 +1,243 @@
|
||||
"""Multiple-producer-multiple-consumer signal-dispatching
|
||||
|
||||
dispatcher is the core of the PyDispatcher system,
|
||||
providing the primary API and the core logic for the
|
||||
system.
|
||||
|
||||
Module attributes of note:
|
||||
|
||||
Any -- Singleton used to signal either "Any Sender" or
|
||||
"Any Signal". See documentation of the _Any class.
|
||||
Anonymous -- Singleton used to signal "Anonymous Sender"
|
||||
See documentation of the _Anonymous class.
|
||||
|
||||
Internal attributes:
|
||||
WEAKREF_TYPES -- tuple of types/classes which represent
|
||||
weak references to receivers, and thus must be de-
|
||||
referenced on retrieval to retrieve the callable
|
||||
object
|
||||
connections -- { senderkey (id) : { signal : [receivers...]}}
|
||||
senders -- { senderkey (id) : weakref(sender) }
|
||||
used for cleaning up sender references on sender
|
||||
deletion
|
||||
sendersBack -- { receiverkey (id) : [senderkey (id)...] }
|
||||
used for cleaning up receiver references on receiver
|
||||
deletion, (considerably speeds up the cleanup process
|
||||
vs. the original code.)
|
||||
"""
|
||||
import weakref
|
||||
from django.dispatch import saferef, robustapply, errors
|
||||
import warnings
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set # Python 2.3 fallback
|
||||
|
||||
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
|
||||
__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
|
||||
__version__ = "$Revision: 1.9 $"[11:-2]
|
||||
|
||||
|
||||
class _Parameter:
|
||||
"""Used to represent default parameter values."""
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
class _Any(_Parameter):
|
||||
"""Singleton used to signal either "Any Sender" or "Any Signal"
|
||||
|
||||
The Any object can be used with connect, disconnect,
|
||||
send, or sendExact to signal that the parameter given
|
||||
Any should react to all senders/signals, not just
|
||||
a particular sender/signal.
|
||||
"""
|
||||
Any = _Any()
|
||||
|
||||
class _Anonymous(_Parameter):
|
||||
"""Singleton used to signal "Anonymous Sender"
|
||||
|
||||
The Anonymous object is used to signal that the sender
|
||||
of a message is not specified (as distinct from being
|
||||
"any sender"). Registering callbacks for Anonymous
|
||||
will only receive messages sent without senders. Sending
|
||||
with anonymous will only send messages to those receivers
|
||||
registered for Any or Anonymous.
|
||||
|
||||
Note:
|
||||
The default sender for connect is Any, while the
|
||||
default sender for send is Anonymous. This has
|
||||
the effect that if you do not specify any senders
|
||||
in either function then all messages are routed
|
||||
as though there was a single sender (Anonymous)
|
||||
being used everywhere.
|
||||
"""
|
||||
Anonymous = _Anonymous()
|
||||
from django.dispatch import saferef
|
||||
|
||||
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
||||
|
||||
connections = {}
|
||||
senders = {}
|
||||
sendersBack = {}
|
||||
def _make_id(target):
|
||||
if hasattr(target, 'im_func'):
|
||||
return (id(target.im_self), id(target.im_func))
|
||||
return id(target)
|
||||
|
||||
|
||||
def connect(receiver, signal=Any, sender=Any, weak=True):
|
||||
"""Connect receiver to sender for signal
|
||||
|
||||
receiver -- a callable Python object which is to receive
|
||||
messages/signals/events. 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).
|
||||
class Signal(object):
|
||||
"""Base class for all signals
|
||||
|
||||
Receivers are fairly flexible in their specification,
|
||||
as the machinery in the robustApply module takes care
|
||||
of most of the details regarding figuring out appropriate
|
||||
subsets of the sent arguments to apply to a given
|
||||
receiver.
|
||||
|
||||
Note:
|
||||
if receiver is itself a weak reference (a callable),
|
||||
it will be de-referenced by the system's machinery,
|
||||
so *generally* weak references are not suitable as
|
||||
receivers, though some use might be found for the
|
||||
facility whereby a higher-level library passes in
|
||||
pre-weakrefed receiver references.
|
||||
|
||||
signal -- the signal to which the receiver should respond
|
||||
Internal attributes:
|
||||
receivers -- { receriverkey (id) : weakref(receiver) }
|
||||
"""
|
||||
|
||||
if Any, receiver will receive any signal from the
|
||||
indicated sender (which might also be Any, but is not
|
||||
necessarily Any).
|
||||
|
||||
Otherwise must be a hashable Python object other than
|
||||
None (DispatcherError raised on None).
|
||||
|
||||
sender -- the sender to which the receiver should respond
|
||||
def __init__(self, providing_args=None):
|
||||
"""providing_args -- A list of the arguments this signal can pass along in
|
||||
a send() call.
|
||||
"""
|
||||
self.receivers = []
|
||||
if providing_args is None:
|
||||
providing_args = []
|
||||
self.providing_args = set(providing_args)
|
||||
|
||||
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
|
||||
"""Connect receiver to sender for signal
|
||||
|
||||
if Any, receiver will receive the indicated signals
|
||||
from any sender.
|
||||
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 Anonymous, receiver will only receive indicated
|
||||
signals from send/sendExact which do not specify a
|
||||
sender, or specify Anonymous explicitly as the sender.
|
||||
Receivers must be able to accept keyword arguments.
|
||||
|
||||
Otherwise can be any python object.
|
||||
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.
|
||||
|
||||
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, may raise DispatcherTypeError
|
||||
"""
|
||||
if signal is None:
|
||||
raise errors.DispatcherTypeError(
|
||||
'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
|
||||
)
|
||||
if weak:
|
||||
receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
|
||||
senderkey = id(sender)
|
||||
|
||||
signals = connections.setdefault(senderkey, {})
|
||||
|
||||
# Keep track of senders for cleanup.
|
||||
# Is Anonymous something we want to clean up?
|
||||
if sender not in (None, Anonymous, Any):
|
||||
def remove(object, senderkey=senderkey):
|
||||
_removeSender(senderkey=senderkey)
|
||||
# Skip objects that can not be weakly referenced, which means
|
||||
# they won't be automatically cleaned up, but that's too bad.
|
||||
try:
|
||||
weakSender = weakref.ref(sender, remove)
|
||||
senders[senderkey] = weakSender
|
||||
except:
|
||||
pass
|
||||
returns None
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
receiverID = id(receiver)
|
||||
# get current set, remove any current references to
|
||||
# this receiver in the set, including back-references
|
||||
if signals.has_key(signal):
|
||||
receivers = signals[signal]
|
||||
_removeOldBackRefs(senderkey, signal, receiver, receivers)
|
||||
else:
|
||||
receivers = signals[signal] = []
|
||||
try:
|
||||
current = sendersBack.get(receiverID)
|
||||
if current is None:
|
||||
sendersBack[ receiverID ] = current = []
|
||||
if senderkey not in current:
|
||||
current.append(senderkey)
|
||||
except:
|
||||
pass
|
||||
|
||||
receivers.append(receiver)
|
||||
|
||||
|
||||
|
||||
def disconnect(receiver, signal=Any, sender=Any, weak=True):
|
||||
"""Disconnect receiver from sender for signal
|
||||
|
||||
receiver -- the registered receiver to disconnect
|
||||
signal -- the registered signal to disconnect
|
||||
sender -- the registered sender to disconnect
|
||||
weak -- the weakref state to disconnect
|
||||
|
||||
disconnect reverses the process of connect,
|
||||
the semantics for the individual elements are
|
||||
logically equivalent to a tuple of
|
||||
(receiver, signal, sender, weak) used as a key
|
||||
to be deleted from the internal routing tables.
|
||||
(The actual process is slightly more complex
|
||||
but the semantics are basically the same).
|
||||
|
||||
Note:
|
||||
Using disconnect is not required to cleanup
|
||||
routing when an object is deleted, the framework
|
||||
will remove routes for deleted objects
|
||||
automatically. It's only necessary to disconnect
|
||||
if you want to stop routing to a live object.
|
||||
if settings.DEBUG:
|
||||
import inspect
|
||||
assert inspect.getargspec(receiver)[2] is not None, \
|
||||
"Signal receivers must accept keyword arguments (**kwargs)."
|
||||
|
||||
returns None, may raise DispatcherTypeError or
|
||||
DispatcherKeyError
|
||||
"""
|
||||
if signal is None:
|
||||
raise errors.DispatcherTypeError(
|
||||
'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
|
||||
)
|
||||
if weak: receiver = saferef.safeRef(receiver)
|
||||
senderkey = id(sender)
|
||||
try:
|
||||
signals = connections[senderkey]
|
||||
receivers = signals[signal]
|
||||
except KeyError:
|
||||
raise errors.DispatcherKeyError(
|
||||
"""No receivers found for signal %r from sender %r""" %(
|
||||
signal,
|
||||
sender
|
||||
)
|
||||
)
|
||||
try:
|
||||
# also removes from receivers
|
||||
_removeOldBackRefs(senderkey, signal, receiver, receivers)
|
||||
except ValueError:
|
||||
raise errors.DispatcherKeyError(
|
||||
"""No connection to receiver %s for signal %s from sender %s""" %(
|
||||
receiver,
|
||||
signal,
|
||||
sender
|
||||
)
|
||||
)
|
||||
_cleanupConnections(senderkey, signal)
|
||||
|
||||
def getReceivers(sender=Any, signal=Any):
|
||||
"""Get list of receivers from global tables
|
||||
|
||||
This utility function allows you to retrieve the
|
||||
raw list of receivers from the connections table
|
||||
for the given sender and signal pair.
|
||||
|
||||
Note:
|
||||
there is no guarantee that this is the actual list
|
||||
stored in the connections table, so the value
|
||||
should be treated as a simple iterable/truth value
|
||||
rather than, for instance a list to which you
|
||||
might append new records.
|
||||
|
||||
Normally you would use liveReceivers(getReceivers(...))
|
||||
to retrieve the actual receiver objects as an iterable
|
||||
object.
|
||||
"""
|
||||
existing = connections.get(id(sender))
|
||||
if existing is not None:
|
||||
return existing.get(signal, [])
|
||||
return []
|
||||
|
||||
def liveReceivers(receivers):
|
||||
"""Filter sequence of receivers to get resolved, live receivers
|
||||
|
||||
This is a generator which will iterate over
|
||||
the passed sequence, checking for weak references
|
||||
and resolving them, then returning all live
|
||||
receivers.
|
||||
"""
|
||||
for receiver in receivers:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
# Dereference the weak reference.
|
||||
receiver = receiver()
|
||||
if receiver is not None:
|
||||
yield receiver
|
||||
if dispatch_uid:
|
||||
lookup_key = (dispatch_uid, _make_id(sender))
|
||||
else:
|
||||
yield receiver
|
||||
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||
|
||||
if weak:
|
||||
receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
|
||||
|
||||
|
||||
def getAllReceivers(sender=Any, signal=Any):
|
||||
"""Get list of all receivers from global tables
|
||||
|
||||
This gets all dereferenced receivers which should receive
|
||||
the given signal from sender, each receiver should
|
||||
be produced only once by the resulting generator
|
||||
"""
|
||||
receivers = {}
|
||||
# Get receivers that receive *this* signal from *this* sender.
|
||||
# Add receivers that receive *any* signal from *this* sender.
|
||||
# Add receivers that receive *this* signal from *any* sender.
|
||||
# Add receivers that receive *any* signal from *any* sender.
|
||||
l = []
|
||||
i = id(sender)
|
||||
if i in connections:
|
||||
sender_receivers = connections[i]
|
||||
if signal in sender_receivers:
|
||||
l.extend(sender_receivers[signal])
|
||||
if signal is not Any and Any in sender_receivers:
|
||||
l.extend(sender_receivers[Any])
|
||||
|
||||
if sender is not Any:
|
||||
i = id(Any)
|
||||
if i in connections:
|
||||
sender_receivers = connections[i]
|
||||
if sender_receivers is not None:
|
||||
if signal in sender_receivers:
|
||||
l.extend(sender_receivers[signal])
|
||||
if signal is not Any and Any in sender_receivers:
|
||||
l.extend(sender_receivers[Any])
|
||||
|
||||
for receiver in l:
|
||||
try:
|
||||
if not receiver in receivers:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
receiver = receiver()
|
||||
# this should only (rough guess) be possible if somehow, deref'ing
|
||||
# triggered a wipe.
|
||||
if receiver is None:
|
||||
continue
|
||||
receivers[receiver] = 1
|
||||
yield receiver
|
||||
except TypeError:
|
||||
# dead weakrefs raise TypeError on hash...
|
||||
pass
|
||||
|
||||
def send(signal=Any, sender=Anonymous, *arguments, **named):
|
||||
"""Send signal from sender to all connected receivers.
|
||||
|
||||
signal -- (hashable) signal value, see connect for details
|
||||
|
||||
sender -- the sender of the signal
|
||||
|
||||
if Any, only receivers registered for Any will receive
|
||||
the message.
|
||||
|
||||
if Anonymous, only receivers registered to receive
|
||||
messages from Anonymous or Any will receive the message
|
||||
|
||||
Otherwise can be any python object (normally one
|
||||
registered with a connect if you actually want
|
||||
something to occur).
|
||||
|
||||
arguments -- positional arguments which will be passed to
|
||||
*all* receivers. Note that this may raise TypeErrors
|
||||
if the receivers do not allow the particular arguments.
|
||||
Note also that arguments are applied before named
|
||||
arguments, so they should be used with care.
|
||||
|
||||
named -- named arguments which will be filtered according
|
||||
to the parameters of the receivers to only provide those
|
||||
acceptable to the receiver.
|
||||
|
||||
Return 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.
|
||||
"""
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
responses = []
|
||||
for receiver in getAllReceivers(sender, signal):
|
||||
response = robustapply.robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
|
||||
def sendExact(signal=Any, sender=Anonymous, *arguments, **named ):
|
||||
"""Send signal only to those receivers registered for exact message
|
||||
|
||||
sendExact allows for avoiding Any/Anonymous registered
|
||||
handlers, sending only to those receivers explicitly
|
||||
registered for a particular signal on a particular
|
||||
sender.
|
||||
"""
|
||||
responses = []
|
||||
for receiver in liveReceivers(getReceivers(sender, signal)):
|
||||
response = robustapply.robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
|
||||
def _removeReceiver(receiver):
|
||||
"""Remove receiver from connections."""
|
||||
if not sendersBack:
|
||||
# During module cleanup the mapping will be replaced with None
|
||||
return False
|
||||
backKey = id(receiver)
|
||||
for senderkey in sendersBack.get(backKey,()):
|
||||
try:
|
||||
signals = connections[senderkey].keys()
|
||||
except KeyError,err:
|
||||
pass
|
||||
for r_key, _ in self.receivers:
|
||||
if r_key == lookup_key:
|
||||
break
|
||||
else:
|
||||
for signal in signals:
|
||||
try:
|
||||
receivers = connections[senderkey][signal]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
receivers.remove(receiver)
|
||||
except Exception, err:
|
||||
pass
|
||||
_cleanupConnections(senderkey, signal)
|
||||
try:
|
||||
del sendersBack[ backKey ]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _cleanupConnections(senderkey, signal):
|
||||
"""Delete any empty signals for senderkey. Delete senderkey if empty."""
|
||||
try:
|
||||
receivers = connections[senderkey][signal]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if not receivers:
|
||||
# No more connected receivers. Therefore, remove the signal.
|
||||
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
|
||||
"""
|
||||
|
||||
if dispatch_uid:
|
||||
lookup_key = (dispatch_uid, _make_id(sender))
|
||||
else:
|
||||
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||
|
||||
for idx, (r_key, _) in enumerate(self.receivers):
|
||||
if r_key == lookup_key:
|
||||
del self.receivers[idx]
|
||||
|
||||
def send(self, sender, **named):
|
||||
"""Send signal from sender to all connected receivers.
|
||||
|
||||
sender -- the sender of the signal
|
||||
Either a specific object or None.
|
||||
|
||||
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
|
||||
|
||||
for receiver in self._live_receivers(_make_id(sender)):
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
responses.append((receiver, response))
|
||||
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.
|
||||
"""
|
||||
|
||||
responses = []
|
||||
if not self.receivers:
|
||||
return responses
|
||||
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
for receiver in self._live_receivers(_make_id(sender)):
|
||||
try:
|
||||
signals = connections[senderkey]
|
||||
except KeyError:
|
||||
pass
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
except Exception, err:
|
||||
responses.append((receiver, err))
|
||||
else:
|
||||
del signals[signal]
|
||||
if not signals:
|
||||
# No more signal connections. Therefore, remove the sender.
|
||||
_removeSender(senderkey)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
def _removeSender(senderkey):
|
||||
"""Remove senderkey from connections."""
|
||||
_removeBackrefs(senderkey)
|
||||
def _live_receivers(self, senderkey):
|
||||
"""Filter sequence of receivers to get resolved, live receivers
|
||||
|
||||
connections.pop(senderkey, None)
|
||||
senders.pop(senderkey, None)
|
||||
This checks for weak references
|
||||
and resolves them, then returning only live
|
||||
receivers.
|
||||
"""
|
||||
none_senderkey = _make_id(None)
|
||||
|
||||
for (receiverkey, r_senderkey), receiver in self.receivers:
|
||||
if r_senderkey == none_senderkey or r_senderkey == senderkey:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
# Dereference the weak reference.
|
||||
receiver = receiver()
|
||||
if receiver is not None:
|
||||
yield receiver
|
||||
else:
|
||||
yield receiver
|
||||
|
||||
def _removeBackrefs(senderkey):
|
||||
"""Remove all back-references to this senderkey"""
|
||||
for receiver_list in connections.pop(senderkey, {}).values():
|
||||
for receiver in receiver_list:
|
||||
_killBackref(receiver, senderkey)
|
||||
def _remove_receiver(self, receiver):
|
||||
"""Remove dead receivers from connections."""
|
||||
|
||||
to_remove = []
|
||||
for key, connected_receiver in self.receivers:
|
||||
if connected_receiver == receiver:
|
||||
to_remove.append(key)
|
||||
for key in to_remove:
|
||||
for idx, (r_key, _) in enumerate(self.receivers):
|
||||
if r_key == key:
|
||||
del self.receivers[idx]
|
||||
|
||||
def _removeOldBackRefs(senderkey, signal, receiver, receivers):
|
||||
"""Kill old sendersBack references from receiver
|
||||
|
||||
This guards against multiple registration of the same
|
||||
receiver for a given signal and sender leaking memory
|
||||
as old back reference records build up.
|
||||
|
||||
Also removes old receiver instance from receivers
|
||||
def connect(receiver, signal, sender=None, weak=True):
|
||||
"""
|
||||
try:
|
||||
index = receivers.index(receiver)
|
||||
# need to scan back references here and remove senderkey
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
oldReceiver = receivers[index]
|
||||
del receivers[index]
|
||||
found = 0
|
||||
signals = connections.get(signal)
|
||||
if signals is not None:
|
||||
for sig,recs in connections.get(signal,{}).iteritems():
|
||||
if sig != signal:
|
||||
for rec in recs:
|
||||
if rec is oldReceiver:
|
||||
found = 1
|
||||
break
|
||||
if not found:
|
||||
_killBackref(oldReceiver, senderkey)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _killBackref(receiver, senderkey):
|
||||
"""Do the actual removal of back reference from receiver to senderkey"""
|
||||
receiverkey = id(receiver)
|
||||
receivers_list = sendersBack.get(receiverkey, ())
|
||||
while senderkey in receivers_list:
|
||||
try:
|
||||
receivers_list.remove(senderkey)
|
||||
except:
|
||||
break
|
||||
if not receivers_list:
|
||||
try:
|
||||
del sendersBack[ receiverkey ]
|
||||
except KeyError:
|
||||
pass
|
||||
return True
|
||||
For backward compatibility only. See Signal.connect()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.connect() is deprecated; use Signal.connect() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.connect(receiver, sender, weak)
|
||||
|
||||
def disconnect(receiver, signal, sender=None, weak=True):
|
||||
"""
|
||||
For backward compatibility only. See Signal.disconnect()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.disconnect() is deprecated; use Signal.disconnect() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
signal.disconnect(receiver, sender, weak)
|
||||
|
||||
def send(signal, sender=None, **named):
|
||||
"""
|
||||
For backward compatibility only. See Signal.send()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.send() is deprecated; use Signal.send() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.send(sender=sender, **named)
|
||||
|
||||
def sendExact(signal, sender, **named ):
|
||||
"""
|
||||
This function is deprecated, as it now has the same meaning as send.
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.sendExact() is deprecated; use Signal.send() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.send(sender=sender, **named)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
"""Error types for dispatcher mechanism
|
||||
"""
|
||||
|
||||
class DispatcherError(Exception):
|
||||
"""Base class for all Dispatcher errors"""
|
||||
class DispatcherKeyError(KeyError, DispatcherError):
|
||||
"""Error raised when unknown (sender,signal) set specified"""
|
||||
class DispatcherTypeError(TypeError, DispatcherError):
|
||||
"""Error raised when inappropriate signal-type specified (None)"""
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
PyDispatcher License
|
||||
django.dispatch was originally forked from PyDispatcher.
|
||||
|
||||
PyDispatcher License:
|
||||
|
||||
Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
|
||||
All rights reserved.
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
"""Module implementing error-catching version of send (sendRobust)"""
|
||||
from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
|
||||
from django.dispatch.robustapply import robustApply
|
||||
|
||||
def sendRobust(
|
||||
signal=Any,
|
||||
sender=Anonymous,
|
||||
*arguments, **named
|
||||
):
|
||||
"""Send signal from sender to all connected receivers catching errors
|
||||
|
||||
signal -- (hashable) signal value, see connect for details
|
||||
|
||||
sender -- the sender of the signal
|
||||
|
||||
if Any, only receivers registered for Any will receive
|
||||
the message.
|
||||
|
||||
if Anonymous, only receivers registered to receive
|
||||
messages from Anonymous or Any will receive the message
|
||||
|
||||
Otherwise can be any python object (normally one
|
||||
registered with a connect if you actually want
|
||||
something to occur).
|
||||
|
||||
arguments -- positional arguments which will be passed to
|
||||
*all* receivers. Note that this may raise TypeErrors
|
||||
if the receivers do not allow the particular arguments.
|
||||
Note also that arguments are applied before named
|
||||
arguments, so they should be used with care.
|
||||
|
||||
named -- named arguments which will be filtered according
|
||||
to the parameters of the receivers to only provide those
|
||||
acceptable to the receiver.
|
||||
|
||||
Return a list of tuple pairs [(receiver, response), ... ]
|
||||
|
||||
if any receiver raises an error (specifically any subclass of Exception),
|
||||
the error instance is returned as the result for that receiver.
|
||||
"""
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
responses = []
|
||||
for receiver in liveReceivers(getAllReceivers(sender, signal)):
|
||||
try:
|
||||
response = robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
except Exception, err:
|
||||
responses.append((receiver, err))
|
||||
else:
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
@@ -1,47 +0,0 @@
|
||||
"""Robust apply mechanism
|
||||
|
||||
Provides a function "call", which can sort out
|
||||
what arguments a given callable object can take,
|
||||
and subset the given arguments to match only
|
||||
those which are acceptable.
|
||||
"""
|
||||
|
||||
def function( receiver ):
|
||||
"""Get function-like callable object for given receiver
|
||||
|
||||
returns (function_or_method, codeObject, fromMethod)
|
||||
|
||||
If fromMethod is true, then the callable already
|
||||
has its first argument bound
|
||||
"""
|
||||
if hasattr(receiver, '__call__'):
|
||||
# receiver is a class instance; assume it is callable.
|
||||
# Reassign receiver to the actual method that will be called.
|
||||
if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
|
||||
receiver = receiver.__call__
|
||||
if hasattr( receiver, 'im_func' ):
|
||||
# an instance-method...
|
||||
return receiver, receiver.im_func.func_code, 1
|
||||
elif not hasattr( receiver, 'func_code'):
|
||||
raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
|
||||
return receiver, receiver.func_code, 0
|
||||
|
||||
def robustApply(receiver, *arguments, **named):
|
||||
"""Call receiver with arguments and an appropriate subset of named
|
||||
"""
|
||||
receiver, codeObject, startIndex = function( receiver )
|
||||
acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
|
||||
for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
|
||||
if named.has_key( name ):
|
||||
raise TypeError(
|
||||
"""Argument %r specified both positionally and as a keyword for calling %r"""% (
|
||||
name, receiver,
|
||||
)
|
||||
)
|
||||
if not (codeObject.co_flags & 8):
|
||||
# fc does not have a **kwds type parameter, therefore
|
||||
# remove unacceptable arguments.
|
||||
for arg in named.keys():
|
||||
if arg not in acceptable:
|
||||
del named[arg]
|
||||
return receiver(*arguments, **named)
|
||||
@@ -1,4 +1,10 @@
|
||||
"""Refactored "safe reference" from dispatcher.py"""
|
||||
"""
|
||||
"Safe weakrefs", originally from pyDispatcher.
|
||||
|
||||
Provides a way to safely weakref any function, including bound methods (which
|
||||
aren't handled by the core weakref module).
|
||||
"""
|
||||
|
||||
import weakref, traceback
|
||||
|
||||
def safeRef(target, onDelete = None):
|
||||
@@ -60,7 +66,9 @@ class BoundMethodWeakref(object):
|
||||
same BoundMethodWeakref instance.
|
||||
|
||||
"""
|
||||
|
||||
_allInstances = weakref.WeakValueDictionary()
|
||||
|
||||
def __new__( cls, target, onDelete=None, *arguments,**named ):
|
||||
"""Create new instance or return current instance
|
||||
|
||||
@@ -83,6 +91,7 @@ class BoundMethodWeakref(object):
|
||||
cls._allInstances[key] = base
|
||||
base.__init__( target, onDelete, *arguments,**named)
|
||||
return base
|
||||
|
||||
def __init__(self, target, onDelete=None):
|
||||
"""Return a weak-reference-like instance for a bound method
|
||||
|
||||
@@ -122,6 +131,7 @@ class BoundMethodWeakref(object):
|
||||
self.weakFunc = weakref.ref(target.im_func, remove)
|
||||
self.selfName = str(target.im_self)
|
||||
self.funcName = str(target.im_func.__name__)
|
||||
|
||||
def calculateKey( cls, target ):
|
||||
"""Calculate the reference key for this reference
|
||||
|
||||
@@ -130,6 +140,7 @@ class BoundMethodWeakref(object):
|
||||
"""
|
||||
return (id(target.im_self),id(target.im_func))
|
||||
calculateKey = classmethod( calculateKey )
|
||||
|
||||
def __str__(self):
|
||||
"""Give a friendly representation of the object"""
|
||||
return """%s( %s.%s )"""%(
|
||||
@@ -137,15 +148,19 @@ class BoundMethodWeakref(object):
|
||||
self.selfName,
|
||||
self.funcName,
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __nonzero__( self ):
|
||||
"""Whether we are still a valid reference"""
|
||||
return self() is not None
|
||||
|
||||
def __cmp__( self, other ):
|
||||
"""Compare with another reference"""
|
||||
if not isinstance (other,self.__class__):
|
||||
return cmp( self.__class__, type(other) )
|
||||
return cmp( self.key, other.key)
|
||||
|
||||
def __call__(self):
|
||||
"""Return a strong reference to the bound method
|
||||
|
||||
@@ -224,7 +239,6 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
|
||||
return getattr(target, function.__name__)
|
||||
return None
|
||||
|
||||
|
||||
def get_bound_method_weakref(target, onDelete):
|
||||
"""Instantiates the appropiate BoundMethodWeakRef, depending on the details of
|
||||
the underlying class method implementation"""
|
||||
@@ -234,4 +248,3 @@ def get_bound_method_weakref(target, onDelete):
|
||||
else:
|
||||
# no luck, use the alternative implementation:
|
||||
return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user