1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

magic-removal: Upgraded django.dispatch to pydispatcher 1.0.3 (which hasn't been released yet; got it from latest CVS)

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2636 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-04-09 22:59:25 +00:00
parent c229739f2f
commit 25b712819d
7 changed files with 817 additions and 741 deletions

View File

@ -1,6 +1,6 @@
"""Multi-consumer multi-producer dispatching mechanism """Multi-consumer multi-producer dispatching mechanism
""" """
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "Patrick K. O'Brien" __author__ = "Patrick K. O'Brien"
__license__ = "BSD-style, see license.txt for details" __license__ = "BSD-style, see license.txt for details"

View File

@ -1,485 +1,497 @@
"""Multiple-producer-multiple-consumer signal-dispatching """Multiple-producer-multiple-consumer signal-dispatching
dispatcher is the core of the PyDispatcher system, dispatcher is the core of the PyDispatcher system,
providing the primary API and the core logic for the providing the primary API and the core logic for the
system. system.
Module attributes of note: Module attributes of note:
Any -- Singleton used to signal either "Any Sender" or Any -- Singleton used to signal either "Any Sender" or
"Any Signal". See documentation of the _Any class. "Any Signal". See documentation of the _Any class.
Anonymous -- Singleton used to signal "Anonymous Sender" Anonymous -- Singleton used to signal "Anonymous Sender"
See documentation of the _Anonymous class. See documentation of the _Anonymous class.
Internal attributes: Internal attributes:
WEAKREF_TYPES -- tuple of types/classes which represent WEAKREF_TYPES -- tuple of types/classes which represent
weak references to receivers, and thus must be de- weak references to receivers, and thus must be de-
referenced on retrieval to retrieve the callable referenced on retrieval to retrieve the callable
object object
connections -- { senderkey (id) : { signal : [receivers...]}} connections -- { senderkey (id) : { signal : [receivers...]}}
senders -- { senderkey (id) : weakref(sender) } senders -- { senderkey (id) : weakref(sender) }
used for cleaning up sender references on sender used for cleaning up sender references on sender
deletion deletion
sendersBack -- { receiverkey (id) : [senderkey (id)...] } sendersBack -- { receiverkey (id) : [senderkey (id)...] }
used for cleaning up receiver references on receiver used for cleaning up receiver references on receiver
deletion, (considerably speeds up the cleanup process deletion, (considerably speeds up the cleanup process
vs. the original code.) vs. the original code.)
""" """
import types, weakref from __future__ import generators
from django.dispatch import saferef, robustapply, errors import types, weakref
from django.dispatch import saferef, robustapply, errors
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
__cvsid__ = "$Id: dispatcher.py,v 1.8 2004/11/26 06:37:33 mcfletch Exp $" __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
__version__ = "$Revision: 1.8 $"[11:-2] __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.""" try:
def __repr__(self): True
return self.__class__.__name__ except NameError:
True = 1==1
class _Any(_Parameter): False = 1==0
"""Singleton used to signal either "Any Sender" or "Any Signal"
class _Parameter:
The Any object can be used with connect, disconnect, """Used to represent default parameter values."""
send, or sendExact to signal that the parameter given def __repr__(self):
Any should react to all senders/signals, not just return self.__class__.__name__
a particular sender/signal.
""" class _Any(_Parameter):
Any = _Any() """Singleton used to signal either "Any Sender" or "Any Signal"
class _Anonymous(_Parameter): The Any object can be used with connect, disconnect,
"""Singleton used to signal "Anonymous Sender" send, or sendExact to signal that the parameter given
Any should react to all senders/signals, not just
The Anonymous object is used to signal that the sender a particular sender/signal.
of a message is not specified (as distinct from being """
"any sender"). Registering callbacks for Anonymous Any = _Any()
will only receive messages sent without senders. Sending
with anonymous will only send messages to those receivers class _Anonymous(_Parameter):
registered for Any or Anonymous. """Singleton used to signal "Anonymous Sender"
Note: The Anonymous object is used to signal that the sender
The default sender for connect is Any, while the of a message is not specified (as distinct from being
default sender for send is Anonymous. This has "any sender"). Registering callbacks for Anonymous
the effect that if you do not specify any senders will only receive messages sent without senders. Sending
in either function then all messages are routed with anonymous will only send messages to those receivers
as though there was a single sender (Anonymous) registered for Any or Anonymous.
being used everywhere.
""" Note:
Anonymous = _Anonymous() The default sender for connect is Any, while the
default sender for send is Anonymous. This has
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) the effect that if you do not specify any senders
in either function then all messages are routed
connections = {} as though there was a single sender (Anonymous)
senders = {} being used everywhere.
sendersBack = {} """
Anonymous = _Anonymous()
def connect(receiver, signal=Any, sender=Any, weak=True): WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
"""Connect receiver to sender for signal
connections = {}
receiver -- a callable Python object which is to receive senders = {}
messages/signals/events. Receivers must be hashable sendersBack = {}
objects.
if weak is True, then receiver must be weak-referencable def connect(receiver, signal=Any, sender=Any, weak=True):
(more precisely saferef.safeRef() must be able to create """Connect receiver to sender for signal
a reference to the receiver).
receiver -- a callable Python object which is to receive
Receivers are fairly flexible in their specification, messages/signals/events. Receivers must be hashable
as the machinery in the robustApply module takes care objects.
of most of the details regarding figuring out appropriate
subsets of the sent arguments to apply to a given if weak is True, then receiver must be weak-referencable
receiver. (more precisely saferef.safeRef() must be able to create
a reference to the receiver).
Note:
if receiver is itself a weak reference (a callable), Receivers are fairly flexible in their specification,
it will be de-referenced by the system's machinery, as the machinery in the robustApply module takes care
so *generally* weak references are not suitable as of most of the details regarding figuring out appropriate
receivers, though some use might be found for the subsets of the sent arguments to apply to a given
facility whereby a higher-level library passes in receiver.
pre-weakrefed receiver references.
Note:
signal -- the signal to which the receiver should respond if receiver is itself a weak reference (a callable),
it will be de-referenced by the system's machinery,
if Any, receiver will receive any signal from the so *generally* weak references are not suitable as
indicated sender (which might also be Any, but is not receivers, though some use might be found for the
necessarily Any). facility whereby a higher-level library passes in
pre-weakrefed receiver references.
Otherwise must be a hashable Python object other than
None (DispatcherError raised on None). signal -- the signal to which the receiver should respond
sender -- the sender to which the receiver should respond if Any, receiver will receive any signal from the
indicated sender (which might also be Any, but is not
if Any, receiver will receive the indicated signals necessarily Any).
from any sender.
Otherwise must be a hashable Python object other than
if Anonymous, receiver will only receive indicated None (DispatcherError raised on None).
signals from send/sendExact which do not specify a
sender, or specify Anonymous explicitly as the sender. sender -- the sender to which the receiver should respond
Otherwise can be any python object. if Any, receiver will receive the indicated signals
from any sender.
weak -- whether to use weak references to the receiver
By default, the module will attempt to use weak if Anonymous, receiver will only receive indicated
references to the receiver objects. If this parameter signals from send/sendExact which do not specify a
is false, then strong references will be used. sender, or specify Anonymous explicitly as the sender.
returns None, may raise DispatcherTypeError Otherwise can be any python object.
"""
if signal is None: weak -- whether to use weak references to the receiver
raise errors.DispatcherTypeError( By default, the module will attempt to use weak
'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) references to the receiver objects. If this parameter
) is false, then strong references will be used.
if weak:
receiver = saferef.safeRef(receiver, onDelete=_removeReceiver) returns None, may raise DispatcherTypeError
senderkey = id(sender) """
if connections.has_key(senderkey): if signal is None:
signals = connections[senderkey] raise errors.DispatcherTypeError(
else: 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
connections[senderkey] = signals = {} )
# Keep track of senders for cleanup. if weak:
# Is Anonymous something we want to clean up? receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
if sender not in (None, Anonymous, Any): senderkey = id(sender)
def remove(object, senderkey=senderkey): if connections.has_key(senderkey):
_removeSender(senderkey=senderkey) signals = connections[senderkey]
# Skip objects that can not be weakly referenced, which means else:
# they won't be automatically cleaned up, but that's too bad. connections[senderkey] = signals = {}
try: # Keep track of senders for cleanup.
weakSender = weakref.ref(sender, remove) # Is Anonymous something we want to clean up?
senders[senderkey] = weakSender if sender not in (None, Anonymous, Any):
except: def remove(object, senderkey=senderkey):
pass _removeSender(senderkey=senderkey)
# Skip objects that can not be weakly referenced, which means
receiverID = id(receiver) # they won't be automatically cleaned up, but that's too bad.
# get current set, remove any current references to try:
# this receiver in the set, including back-references weakSender = weakref.ref(sender, remove)
if signals.has_key(signal): senders[senderkey] = weakSender
receivers = signals[signal] except:
_removeOldBackRefs(senderkey, signal, receiver, receivers) pass
else:
receivers = signals[signal] = [] receiverID = id(receiver)
try: # get current set, remove any current references to
current = sendersBack.get( receiverID ) # this receiver in the set, including back-references
if current is None: if signals.has_key(signal):
sendersBack[ receiverID ] = current = [] receivers = signals[signal]
if senderkey not in current: _removeOldBackRefs(senderkey, signal, receiver, receivers)
current.append(senderkey) else:
except: receivers = signals[signal] = []
pass try:
current = sendersBack.get( receiverID )
receivers.append(receiver) if current is None:
sendersBack[ receiverID ] = current = []
if senderkey not in current:
current.append(senderkey)
def disconnect(receiver, signal=Any, sender=Any, weak=True): except:
"""Disconnect receiver from sender for signal pass
receiver -- the registered receiver to disconnect receivers.append(receiver)
signal -- the registered signal to disconnect
sender -- the registered sender to disconnect
weak -- the weakref state to disconnect
def disconnect(receiver, signal=Any, sender=Any, weak=True):
disconnect reverses the process of connect, """Disconnect receiver from sender for signal
the semantics for the individual elements are
logically equivalent to a tuple of receiver -- the registered receiver to disconnect
(receiver, signal, sender, weak) used as a key signal -- the registered signal to disconnect
to be deleted from the internal routing tables. sender -- the registered sender to disconnect
(The actual process is slightly more complex weak -- the weakref state to disconnect
but the semantics are basically the same).
disconnect reverses the process of connect,
Note: the semantics for the individual elements are
Using disconnect is not required to cleanup logically equivalent to a tuple of
routing when an object is deleted, the framework (receiver, signal, sender, weak) used as a key
will remove routes for deleted objects to be deleted from the internal routing tables.
automatically. It's only necessary to disconnect (The actual process is slightly more complex
if you want to stop routing to a live object. but the semantics are basically the same).
returns None, may raise DispatcherTypeError or Note:
DispatcherKeyError Using disconnect is not required to cleanup
""" routing when an object is deleted, the framework
if signal is None: will remove routes for deleted objects
raise errors.DispatcherTypeError( automatically. It's only necessary to disconnect
'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) if you want to stop routing to a live object.
)
if weak: receiver = saferef.safeRef(receiver) returns None, may raise DispatcherTypeError or
senderkey = id(sender) DispatcherKeyError
try: """
signals = connections[senderkey] if signal is None:
receivers = signals[signal] raise errors.DispatcherTypeError(
except KeyError: 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
raise errors.DispatcherKeyError( )
"""No receivers found for signal %r from sender %r""" %( if weak: receiver = saferef.safeRef(receiver)
signal, senderkey = id(sender)
sender try:
) signals = connections[senderkey]
) receivers = signals[signal]
try: except KeyError:
# also removes from receivers raise errors.DispatcherKeyError(
_removeOldBackRefs(senderkey, signal, receiver, receivers) """No receivers found for signal %r from sender %r""" %(
except ValueError: signal,
raise errors.DispatcherKeyError( sender
"""No connection to receiver %s for signal %s from sender %s""" %( )
receiver, )
signal, try:
sender # also removes from receivers
) _removeOldBackRefs(senderkey, signal, receiver, receivers)
) except ValueError:
_cleanupConnections(senderkey, signal) raise errors.DispatcherKeyError(
"""No connection to receiver %s for signal %s from sender %s""" %(
def getReceivers( sender = Any, signal = Any ): receiver,
"""Get list of receivers from global tables signal,
sender
This utility function allows you to retrieve the )
raw list of receivers from the connections table )
for the given sender and signal pair. _cleanupConnections(senderkey, signal)
Note: def getReceivers( sender = Any, signal = Any ):
there is no guarantee that this is the actual list """Get list of receivers from global tables
stored in the connections table, so the value
should be treated as a simple iterable/truth value This utility function allows you to retrieve the
rather than, for instance a list to which you raw list of receivers from the connections table
might append new records. for the given sender and signal pair.
Normally you would use liveReceivers( getReceivers( ...)) Note:
to retrieve the actual receiver objects as an iterable there is no guarantee that this is the actual list
object. stored in the connections table, so the value
""" should be treated as a simple iterable/truth value
try: rather than, for instance a list to which you
return connections[id(sender)][signal] might append new records.
except KeyError:
return [] Normally you would use liveReceivers( getReceivers( ...))
to retrieve the actual receiver objects as an iterable
def liveReceivers(receivers): object.
"""Filter sequence of receivers to get resolved, live receivers """
try:
This is a generator which will iterate over return connections[id(sender)][signal]
the passed sequence, checking for weak references except KeyError:
and resolving them, then returning all live return []
receivers.
""" def liveReceivers(receivers):
for receiver in receivers: """Filter sequence of receivers to get resolved, live receivers
if isinstance( receiver, WEAKREF_TYPES):
# Dereference the weak reference. This is a generator which will iterate over
receiver = receiver() the passed sequence, checking for weak references
if receiver is not None: and resolving them, then returning all live
yield receiver receivers.
else: """
yield receiver for receiver in receivers:
if isinstance( receiver, WEAKREF_TYPES):
# Dereference the weak reference.
receiver = receiver()
def getAllReceivers( sender = Any, signal = Any ): if receiver is not None:
"""Get list of all receivers from global tables yield receiver
else:
This gets all receivers which should receive yield receiver
the given signal from sender, each receiver should
be produced only once by the resulting generator
"""
receivers = {} def getAllReceivers( sender = Any, signal = Any ):
for set in ( """Get list of all receivers from global tables
# Get receivers that receive *this* signal from *this* sender.
getReceivers( sender, signal ), This gets all receivers which should receive
# Add receivers that receive *any* signal from *this* sender. the given signal from sender, each receiver should
getReceivers( sender, Any ), be produced only once by the resulting generator
# Add receivers that receive *this* signal from *any* sender. """
getReceivers( Any, signal ), receivers = {}
# Add receivers that receive *any* signal from *any* sender. for set in (
getReceivers( Any, Any ), # Get receivers that receive *this* signal from *this* sender.
): getReceivers( sender, signal ),
for receiver in set: # Add receivers that receive *any* signal from *this* sender.
if receiver: # filter out dead instance-method weakrefs getReceivers( sender, Any ),
try: # Add receivers that receive *this* signal from *any* sender.
if not receivers.has_key( receiver ): getReceivers( Any, signal ),
receivers[receiver] = 1 # Add receivers that receive *any* signal from *any* sender.
yield receiver getReceivers( Any, Any ),
except TypeError: ):
# dead weakrefs raise TypeError on hash... for receiver in set:
pass if receiver: # filter out dead instance-method weakrefs
try:
def send(signal=Any, sender=Anonymous, *arguments, **named): if not receivers.has_key( receiver ):
"""Send signal from sender to all connected receivers. receivers[receiver] = 1
yield receiver
signal -- (hashable) signal value, see connect for details except TypeError:
# dead weakrefs raise TypeError on hash...
sender -- the sender of the signal pass
if Any, only receivers registered for Any will receive def send(signal=Any, sender=Anonymous, *arguments, **named):
the message. """Send signal from sender to all connected receivers.
if Anonymous, only receivers registered to receive signal -- (hashable) signal value, see connect for details
messages from Anonymous or Any will receive the message
sender -- the sender of the signal
Otherwise can be any python object (normally one
registered with a connect if you actually want if Any, only receivers registered for Any will receive
something to occur). the message.
arguments -- positional arguments which will be passed to if Anonymous, only receivers registered to receive
*all* receivers. Note that this may raise TypeErrors messages from Anonymous or Any will receive the message
if the receivers do not allow the particular arguments.
Note also that arguments are applied before named Otherwise can be any python object (normally one
arguments, so they should be used with care. registered with a connect if you actually want
something to occur).
named -- named arguments which will be filtered according
to the parameters of the receivers to only provide those arguments -- positional arguments which will be passed to
acceptable to the receiver. *all* receivers. Note that this may raise TypeErrors
if the receivers do not allow the particular arguments.
Return a list of tuple pairs [(receiver, response), ... ] Note also that arguments are applied before named
arguments, so they should be used with care.
if any receiver raises an error, the error propagates back
through send, terminating the dispatch loop, so it is quite named -- named arguments which will be filtered according
possible to not have all receivers called if a raises an to the parameters of the receivers to only provide those
error. acceptable to the receiver.
"""
# Call each receiver with whatever arguments it can accept. Return a list of tuple pairs [(receiver, response), ... ]
# Return a list of tuple pairs [(receiver, response), ... ].
responses = [] if any receiver raises an error, the error propagates back
for receiver in liveReceivers(getAllReceivers(sender, signal)): through send, terminating the dispatch loop, so it is quite
response = robustapply.robustApply( possible to not have all receivers called if a raises an
receiver, error.
signal=signal, """
sender=sender, # Call each receiver with whatever arguments it can accept.
*arguments, # Return a list of tuple pairs [(receiver, response), ... ].
**named responses = []
) for receiver in liveReceivers(getAllReceivers(sender, signal)):
responses.append((receiver, response)) response = robustapply.robustApply(
return responses receiver,
def sendExact( signal=Any, sender=Anonymous, *arguments, **named ): signal=signal,
"""Send signal only to those receivers registered for exact message sender=sender,
*arguments,
sendExact allows for avoiding Any/Anonymous registered **named
handlers, sending only to those receivers explicitly )
registered for a particular signal on a particular responses.append((receiver, response))
sender. return responses
""" def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
responses = [] """Send signal only to those receivers registered for exact message
for receiver in liveReceivers(getReceivers(sender, signal)):
response = robustapply.robustApply( sendExact allows for avoiding Any/Anonymous registered
receiver, handlers, sending only to those receivers explicitly
signal=signal, registered for a particular signal on a particular
sender=sender, sender.
*arguments, """
**named responses = []
) for receiver in liveReceivers(getReceivers(sender, signal)):
responses.append((receiver, response)) response = robustapply.robustApply(
return responses receiver,
signal=signal,
sender=sender,
def _removeReceiver(receiver): *arguments,
"""Remove receiver from connections.""" **named
backKey = id(receiver) )
for senderkey in sendersBack.get(backKey,()): responses.append((receiver, response))
try: return responses
signals = connections[senderkey].keys()
except KeyError,err:
pass def _removeReceiver(receiver):
else: """Remove receiver from connections."""
for signal in signals: if not sendersBack:
try: # During module cleanup the mapping will be replaced with None
receivers = connections[senderkey][signal] return False
except KeyError: backKey = id(receiver)
pass for senderkey in sendersBack.get(backKey,()):
else: try:
try: signals = connections[senderkey].keys()
receivers.remove( receiver ) except KeyError,err:
except Exception, err: pass
pass else:
_cleanupConnections(senderkey, signal) for signal in signals:
try: try:
del sendersBack[ backKey ] receivers = connections[senderkey][signal]
except KeyError: except KeyError:
pass pass
else:
def _cleanupConnections(senderkey, signal): try:
"""Delete any empty signals for senderkey. Delete senderkey if empty.""" receivers.remove( receiver )
try: except Exception, err:
receivers = connections[senderkey][signal] pass
except: _cleanupConnections(senderkey, signal)
pass try:
else: del sendersBack[ backKey ]
if not receivers: except KeyError:
# No more connected receivers. Therefore, remove the signal. pass
try:
signals = connections[senderkey] def _cleanupConnections(senderkey, signal):
except KeyError: """Delete any empty signals for senderkey. Delete senderkey if empty."""
pass try:
else: receivers = connections[senderkey][signal]
del signals[signal] except:
if not signals: pass
# No more signal connections. Therefore, remove the sender. else:
_removeSender(senderkey) if not receivers:
# No more connected receivers. Therefore, remove the signal.
def _removeSender(senderkey): try:
"""Remove senderkey from connections.""" signals = connections[senderkey]
_removeBackrefs(senderkey) except KeyError:
try: pass
del connections[senderkey] else:
except KeyError: del signals[signal]
pass if not signals:
# Senderkey will only be in senders dictionary if sender # No more signal connections. Therefore, remove the sender.
# could be weakly referenced. _removeSender(senderkey)
try: del senders[senderkey]
except: pass def _removeSender(senderkey):
"""Remove senderkey from connections."""
_removeBackrefs(senderkey)
def _removeBackrefs( senderkey): try:
"""Remove all back-references to this senderkey""" del connections[senderkey]
try: except KeyError:
signals = connections[senderkey] pass
except KeyError: # Senderkey will only be in senders dictionary if sender
signals = None # could be weakly referenced.
else: try:
items = signals.items() del senders[senderkey]
def allReceivers( ): except:
for signal,set in items: pass
for item in set:
yield item
for receiver in allReceivers(): def _removeBackrefs( senderkey):
_killBackref( receiver, senderkey ) """Remove all back-references to this senderkey"""
try:
def _removeOldBackRefs(senderkey, signal, receiver, receivers): signals = connections[senderkey]
"""Kill old sendersBack references from receiver except KeyError:
signals = None
This guards against multiple registration of the same else:
receiver for a given signal and sender leaking memory items = signals.items()
as old back reference records build up. def allReceivers( ):
for signal,set in items:
Also removes old receiver instance from receivers for item in set:
""" yield item
try: for receiver in allReceivers():
index = receivers.index(receiver) _killBackref( receiver, senderkey )
# need to scan back references here and remove senderkey
except ValueError: def _removeOldBackRefs(senderkey, signal, receiver, receivers):
return False """Kill old sendersBack references from receiver
else:
oldReceiver = receivers[index] This guards against multiple registration of the same
del receivers[index] receiver for a given signal and sender leaking memory
found = 0 as old back reference records build up.
signals = connections.get(signal)
if signals is not None: Also removes old receiver instance from receivers
for sig,recs in connections.get(signal,{}).iteritems(): """
if sig != signal: try:
for rec in recs: index = receivers.index(receiver)
if rec is oldReceiver: # need to scan back references here and remove senderkey
found = 1 except ValueError:
break return False
if not found: else:
_killBackref( oldReceiver, senderkey ) oldReceiver = receivers[index]
return True del receivers[index]
return False found = 0
signals = connections.get(signal)
if signals is not None:
def _killBackref( receiver, senderkey ): for sig,recs in connections.get(signal,{}).iteritems():
"""Do the actual removal of back reference from receiver to senderkey""" if sig != signal:
receiverkey = id(receiver) for rec in recs:
set = sendersBack.get( receiverkey, () ) if rec is oldReceiver:
while senderkey in set: found = 1
try: break
set.remove( senderkey ) if not found:
except: _killBackref( oldReceiver, senderkey )
break return True
if not set: return False
try:
del sendersBack[ receiverkey ]
except KeyError: def _killBackref( receiver, senderkey ):
pass """Do the actual removal of back reference from receiver to senderkey"""
return True receiverkey = id(receiver)
set = sendersBack.get( receiverkey, () )
while senderkey in set:
try:
set.remove( senderkey )
except:
break
if not set:
try:
del sendersBack[ receiverkey ]
except KeyError:
pass
return True

View File

@ -1,10 +1,10 @@
"""Error types for dispatcher mechanism """Error types for dispatcher mechanism
""" """
class DispatcherError(Exception): class DispatcherError(Exception):
"""Base class for all Dispatcher errors""" """Base class for all Dispatcher errors"""
class DispatcherKeyError(KeyError, DispatcherError): class DispatcherKeyError(KeyError, DispatcherError):
"""Error raised when unknown (sender,signal) set specified""" """Error raised when unknown (sender,signal) set specified"""
class DispatcherTypeError(TypeError, DispatcherError): class DispatcherTypeError(TypeError, DispatcherError):
"""Error raised when inappropriate signal-type specified (None)""" """Error raised when inappropriate signal-type specified (None)"""

View File

@ -1,34 +1,34 @@
PyDispatcher License PyDispatcher License
Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions modification, are permitted provided that the following conditions
are met: are met:
Redistributions of source code must retain the above copyright Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials disclaimer in the documentation and/or other materials
provided with the distribution. provided with the distribution.
The name of Patrick K. O'Brien, or the name of any Contributor, The name of Patrick K. O'Brien, or the name of any Contributor,
may not be used to endorse or promote products derived from this may not be used to endorse or promote products derived from this
software without specific prior written permission. software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE. OF THE POSSIBILITY OF SUCH DAMAGE.

57
django/dispatch/robust.py Normal file
View File

@ -0,0 +1,57 @@
"""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

View File

@ -1,49 +1,49 @@
"""Robust apply mechanism """Robust apply mechanism
Provides a function "call", which can sort out Provides a function "call", which can sort out
what arguments a given callable object can take, what arguments a given callable object can take,
and subset the given arguments to match only and subset the given arguments to match only
those which are acceptable. those which are acceptable.
""" """
def function( receiver ): def function( receiver ):
"""Get function-like callable object for given receiver """Get function-like callable object for given receiver
returns (function_or_method, codeObject, fromMethod) returns (function_or_method, codeObject, fromMethod)
If fromMethod is true, then the callable already If fromMethod is true, then the callable already
has its first argument bound has its first argument bound
""" """
if hasattr(receiver, '__call__'): if hasattr(receiver, '__call__'):
# receiver is a class instance; assume it is callable. # receiver is a class instance; assume it is callable.
# Reassign receiver to the actual method that will be called. # Reassign receiver to the actual method that will be called.
if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'): if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
receiver = receiver.__call__ receiver = receiver.__call__
if hasattr( receiver, 'im_func' ): if hasattr( receiver, 'im_func' ):
# an instance-method... # an instance-method...
return receiver, receiver.im_func.func_code, 1 return receiver, receiver.im_func.func_code, 1
elif not hasattr( receiver, 'func_code'): elif not hasattr( receiver, 'func_code'):
raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver))) raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
return receiver, receiver.func_code, 0 return receiver, receiver.func_code, 0
def robustApply(receiver, *arguments, **named): def robustApply(receiver, *arguments, **named):
"""Call receiver with arguments and an appropriate subset of named """Call receiver with arguments and an appropriate subset of named
""" """
receiver, codeObject, startIndex = function( receiver ) receiver, codeObject, startIndex = function( receiver )
acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount] acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]: for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
if named.has_key( name ): if named.has_key( name ):
raise TypeError( raise TypeError(
"""Argument %r specified both positionally and as a keyword for calling %r"""% ( """Argument %r specified both positionally and as a keyword for calling %r"""% (
name, receiver, name, receiver,
) )
) )
if not (codeObject.co_flags & 8): if not (codeObject.co_flags & 8):
# fc does not have a **kwds type parameter, therefore # fc does not have a **kwds type parameter, therefore
# remove unacceptable arguments. # remove unacceptable arguments.
for arg in named.keys(): for arg in named.keys():
if arg not in acceptable: if arg not in acceptable:
del named[arg] del named[arg]
return receiver(*arguments, **named) return receiver(*arguments, **named)

View File

@ -1,158 +1,165 @@
"""Refactored "safe reference" from dispatcher.py""" """Refactored "safe reference" from dispatcher.py"""
import weakref, traceback import weakref, traceback
def safeRef(target, onDelete = None): def safeRef(target, onDelete = None):
"""Return a *safe* weak reference to a callable target """Return a *safe* weak reference to a callable target
target -- the object to be weakly referenced, if it's a target -- the object to be weakly referenced, if it's a
bound method reference, will create a BoundMethodWeakref, bound method reference, will create a BoundMethodWeakref,
otherwise creates a simple weakref. otherwise creates a simple weakref.
onDelete -- if provided, will have a hard reference stored onDelete -- if provided, will have a hard reference stored
to the callable to be called after the safe reference to the callable to be called after the safe reference
goes out of scope with the reference object, (either a goes out of scope with the reference object, (either a
weakref or a BoundMethodWeakref) as argument. weakref or a BoundMethodWeakref) as argument.
""" """
if hasattr(target, 'im_self'): if hasattr(target, 'im_self'):
if target.im_self is not None: if target.im_self is not None:
# Turn a bound method into a BoundMethodWeakref instance. # Turn a bound method into a BoundMethodWeakref instance.
# Keep track of these instances for lookup by disconnect(). # Keep track of these instances for lookup by disconnect().
assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,) assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,)
reference = BoundMethodWeakref( reference = BoundMethodWeakref(
target=target, target=target,
onDelete=onDelete onDelete=onDelete
) )
return reference return reference
if callable(onDelete): if callable(onDelete):
return weakref.ref(target, onDelete) return weakref.ref(target, onDelete)
else: else:
return weakref.ref( target ) return weakref.ref( target )
class BoundMethodWeakref(object): class BoundMethodWeakref(object):
"""'Safe' and reusable weak references to instance methods """'Safe' and reusable weak references to instance methods
BoundMethodWeakref objects provide a mechanism for BoundMethodWeakref objects provide a mechanism for
referencing a bound method without requiring that the referencing a bound method without requiring that the
method object itself (which is normally a transient method object itself (which is normally a transient
object) is kept alive. Instead, the BoundMethodWeakref object) is kept alive. Instead, the BoundMethodWeakref
object keeps weak references to both the object and the object keeps weak references to both the object and the
function which together define the instance method. function which together define the instance method.
Attributes: Attributes:
key -- the identity key for the reference, calculated key -- the identity key for the reference, calculated
by the class's calculateKey method applied to the by the class's calculateKey method applied to the
target instance method target instance method
deletionMethods -- sequence of callable objects taking deletionMethods -- sequence of callable objects taking
single argument, a reference to this object which single argument, a reference to this object which
will be called when *either* the target object or will be called when *either* the target object or
target function is garbage collected (i.e. when target function is garbage collected (i.e. when
this object becomes invalid). These are specified this object becomes invalid). These are specified
as the onDelete parameters of safeRef calls. as the onDelete parameters of safeRef calls.
weakSelf -- weak reference to the target object weakSelf -- weak reference to the target object
weakFunc -- weak reference to the target function weakFunc -- weak reference to the target function
Class Attributes: Class Attributes:
_allInstances -- class attribute pointing to all live _allInstances -- class attribute pointing to all live
BoundMethodWeakref objects indexed by the class's BoundMethodWeakref objects indexed by the class's
calculateKey(target) method applied to the target calculateKey(target) method applied to the target
objects. This weak value dictionary is used to objects. This weak value dictionary is used to
short-circuit creation so that multiple references short-circuit creation so that multiple references
to the same (object, function) pair produce the to the same (object, function) pair produce the
same BoundMethodWeakref instance. same BoundMethodWeakref instance.
""" """
_allInstances = weakref.WeakValueDictionary() _allInstances = weakref.WeakValueDictionary()
def __new__( cls, target, onDelete=None, *arguments,**named ): def __new__( cls, target, onDelete=None, *arguments,**named ):
"""Create new instance or return current instance """Create new instance or return current instance
Basically this method of construction allows us to Basically this method of construction allows us to
short-circuit creation of references to already- short-circuit creation of references to already-
referenced instance methods. The key corresponding referenced instance methods. The key corresponding
to the target is calculated, and if there is already to the target is calculated, and if there is already
an existing reference, that is returned, with its an existing reference, that is returned, with its
deletionMethods attribute updated. Otherwise the deletionMethods attribute updated. Otherwise the
new instance is created and registered in the table new instance is created and registered in the table
of already-referenced methods. of already-referenced methods.
""" """
key = cls.calculateKey(target) key = cls.calculateKey(target)
current =cls._allInstances.get(key) current =cls._allInstances.get(key)
if current is not None: if current is not None:
current.deletionMethods.append( onDelete) current.deletionMethods.append( onDelete)
return current return current
else: else:
base = super( BoundMethodWeakref, cls).__new__( cls ) base = super( BoundMethodWeakref, cls).__new__( cls )
cls._allInstances[key] = base cls._allInstances[key] = base
base.__init__( target, onDelete, *arguments,**named) base.__init__( target, onDelete, *arguments,**named)
return base return base
def __init__(self, target, onDelete=None): def __init__(self, target, onDelete=None):
"""Return a weak-reference-like instance for a bound method """Return a weak-reference-like instance for a bound method
target -- the instance-method target for the weak target -- the instance-method target for the weak
reference, must have im_self and im_func attributes reference, must have im_self and im_func attributes
and be reconstructable via: and be reconstructable via:
target.im_func.__get__( target.im_self ) target.im_func.__get__( target.im_self )
which is true of built-in instance methods. which is true of built-in instance methods.
onDelete -- optional callback which will be called onDelete -- optional callback which will be called
when this weak reference ceases to be valid when this weak reference ceases to be valid
(i.e. either the object or the function is garbage (i.e. either the object or the function is garbage
collected). Should take a single argument, collected). Should take a single argument,
which will be passed a pointer to this object. which will be passed a pointer to this object.
""" """
def remove(weak, self=self): def remove(weak, self=self):
"""Set self.isDead to true when method or instance is destroyed""" """Set self.isDead to true when method or instance is destroyed"""
methods = self.deletionMethods[:] methods = self.deletionMethods[:]
del self.deletionMethods[:] del self.deletionMethods[:]
try: try:
del self.__class__._allInstances[ self.key ] del self.__class__._allInstances[ self.key ]
except KeyError: except KeyError:
pass pass
for function in methods: for function in methods:
try: try:
if callable( function ): if callable( function ):
function( self ) function( self )
except Exception: except Exception, e:
traceback.print_exc() try:
self.deletionMethods = [onDelete] traceback.print_exc()
self.key = self.calculateKey( target ) except AttributeError, err:
self.weakSelf = weakref.ref(target.im_self, remove) print '''Exception during saferef %s cleanup function %s: %s'''%(
self.weakFunc = weakref.ref(target.im_func, remove) self, function, e
def calculateKey( cls, target ): )
"""Calculate the reference key for this reference self.deletionMethods = [onDelete]
self.key = self.calculateKey( target )
Currently this is a two-tuple of the id()'s of the self.weakSelf = weakref.ref(target.im_self, remove)
target object and the target function respectively. self.weakFunc = weakref.ref(target.im_func, remove)
""" self.selfName = str(target.im_self)
return (id(target.im_self),id(target.im_func)) self.funcName = str(target.im_func.__name__)
calculateKey = classmethod( calculateKey ) def calculateKey( cls, target ):
def __str__(self): """Calculate the reference key for this reference
"""Give a friendly representation of the object"""
return """%s( %s.%s )"""%( Currently this is a two-tuple of the id()'s of the
self.__class__.__name__, target object and the target function respectively.
self.weakSelf(), """
self.weakFunc().__name__, return (id(target.im_self),id(target.im_func))
) calculateKey = classmethod( calculateKey )
__repr__ = __str__ def __str__(self):
def __nonzero__( self ): """Give a friendly representation of the object"""
"""Whether we are still a valid reference""" return """%s( %s.%s )"""%(
return self() is not None self.__class__.__name__,
def __cmp__( self, other ): self.selfName,
"""Compare with another reference""" self.funcName,
if not isinstance (other,self.__class__): )
return cmp( self.__class__, type(other) ) __repr__ = __str__
return cmp( self.key, other.key) def __nonzero__( self ):
def __call__(self): """Whether we are still a valid reference"""
"""Return a strong reference to the bound method return self() is not None
def __cmp__( self, other ):
If the target cannot be retrieved, then will """Compare with another reference"""
return None, otherwise returns a bound instance if not isinstance (other,self.__class__):
method for our object and function. return cmp( self.__class__, type(other) )
return cmp( self.key, other.key)
Note: def __call__(self):
You may call this method any number of times, """Return a strong reference to the bound method
as it does not invalidate the reference.
""" If the target cannot be retrieved, then will
target = self.weakSelf() return None, otherwise returns a bound instance
if target is not None: method for our object and function.
function = self.weakFunc()
if function is not None: Note:
return function.__get__(target) You may call this method any number of times,
return None as it does not invalidate the reference.
"""
target = self.weakSelf()
if target is not None:
function = self.weakFunc()
if function is not None:
return function.__get__(target)
return None