diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-08-06 15:32:46 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-08-06 15:32:46 +0000 |
| commit | 34a3bd52255a2253696b74b2d76133aace839fd2 (patch) | |
| tree | 73bc39cc2e0a93925b1e8796b18224192e19e4a3 /django/dispatch | |
| parent | d06b474251c979c512a966a7fb41cd8a06487c14 (diff) | |
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
Diffstat (limited to 'django/dispatch')
| -rw-r--r-- | django/dispatch/__init__.py | 9 | ||||
| -rw-r--r-- | django/dispatch/dispatcher.py | 638 | ||||
| -rw-r--r-- | django/dispatch/errors.py | 10 | ||||
| -rw-r--r-- | django/dispatch/license.txt | 4 | ||||
| -rw-r--r-- | django/dispatch/robust.py | 57 | ||||
| -rw-r--r-- | django/dispatch/robustapply.py | 47 | ||||
| -rw-r--r-- | django/dispatch/saferef.py | 19 |
7 files changed, 218 insertions, 566 deletions
diff --git a/django/dispatch/__init__.py b/django/dispatch/__init__.py index bccae2a2da..0798acc7db 100644 --- a/django/dispatch/__init__.py +++ b/django/dispatch/__init__.py @@ -1,6 +1,9 @@ """Multi-consumer multi-producer dispatching mechanism + +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. """ -__version__ = "1.0.0" -__author__ = "Patrick K. O'Brien" -__license__ = "BSD-style, see license.txt for details" +from django.dispatch.dispatcher import Signal
\ No newline at end of file diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 725544446e..ad54fa42bb 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -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 - -__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" +import warnings +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback - 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 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. +def _make_id(target): + if hasattr(target, 'im_func'): + return (id(target.im_self), id(target.im_func)) + return id(target) - 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. - - if Anonymous, receiver will only receive indicated - signals from send/sendExact which do not specify a - sender, or specify Anonymous explicitly as the sender. + receiver -- a function or an instance method which is to + receive signals. Receivers must be + hashable objects. - Otherwise can be any python object. + if weak is True, then receiver must be weak-referencable + (more precisely saferef.safeRef() must be able to create + a reference to the receiver). - 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. + Receivers must be able to accept keyword arguments. - 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) + If receivers have a dispatch_uid attribute, the receiver will + not be added if another receiver already exists with that + dispatch_uid. - signals = connections.setdefault(senderkey, {}) + sender -- the sender to which the receiver should respond + Must either be of type Signal, or None to receive events + from any sender. - # 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 + 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. - 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 + 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. - 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. + returns None + """ + from django.conf import settings - 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 [] + if settings.DEBUG: + import inspect + assert inspect.getargspec(receiver)[2] is not None, \ + "Signal receivers must accept keyword arguments (**kwargs)." + + if dispatch_uid: + lookup_key = (dispatch_uid, _make_id(sender)) + else: + lookup_key = (_make_id(receiver), _make_id(sender)) -def liveReceivers(receivers): - """Filter sequence of receivers to get resolved, live receivers + if weak: + receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) - 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 + for r_key, _ in self.receivers: + if r_key == lookup_key: + break else: - yield receiver + 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. -def getAllReceivers(sender=Any, signal=Any): - """Get list of all receivers from global tables + returns None + """ - 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 dispatch_uid: + lookup_key = (dispatch_uid, _make_id(sender)) + else: + lookup_key = (_make_id(receiver), _make_id(sender)) - 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 idx, (r_key, _) in enumerate(self.receivers): + if r_key == lookup_key: + del self.receivers[idx] - 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(self, sender, **named): + """Send signal from sender to all connected receivers. -def send(signal=Any, sender=Anonymous, *arguments, **named): - """Send signal from sender to all connected receivers. + sender -- the sender of the signal + Either a specific object or None. - signal -- (hashable) signal value, see connect for details + named -- named arguments which will be passed to receivers. - sender -- the sender of the signal - - if Any, only receivers registered for Any will receive - the message. + Returns a list of tuple pairs [(receiver, response), ... ]. - if Anonymous, only receivers registered to receive - messages from Anonymous or Any will receive the message + 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. + """ - Otherwise can be any python object (normally one - registered with a connect if you actually want - something to occur). + responses = [] + if not self.receivers: + return responses - 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. + for receiver in self._live_receivers(_make_id(sender)): + response = receiver(signal=self, sender=sender, **named) + responses.append((receiver, response)) + return responses - named -- named arguments which will be filtered according - to the parameters of the receivers to only provide those - acceptable to the receiver. + def send_robust(self, sender, **named): + """Send signal from sender to all connected receivers catching errors - Return a list of tuple pairs [(receiver, response), ... ] + sender -- the sender of the signal + Can be any python object (normally one registered with + a connect if you actually want something to occur). - 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 + 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 -def sendExact(signal=Any, sender=Anonymous, *arguments, **named ): - """Send signal only to those receivers registered for exact message + if any receiver raises an error (specifically any subclass of Exception), + the error instance is returned as the result for that receiver. + """ - 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 - + responses = [] + if not self.receivers: + 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 - 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. + # 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 _live_receivers(self, senderkey): + """Filter sequence of receivers to get resolved, live receivers -def _removeSender(senderkey): - """Remove senderkey from connections.""" - _removeBackrefs(senderkey) + This checks for weak references + and resolves them, then returning only live + receivers. + """ + none_senderkey = _make_id(None) - connections.pop(senderkey, None) - senders.pop(senderkey, 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 _remove_receiver(self, receiver): + """Remove dead receivers from connections.""" -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) + 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 connect(receiver, signal, sender=None, weak=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 _removeOldBackRefs(senderkey, signal, receiver, receivers): - """Kill old sendersBack references from receiver +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) - This guards against multiple registration of the same - receiver for a given signal and sender leaking memory - as old back reference records build up. +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) - Also removes old receiver instance from receivers +def sendExact(signal, sender, **named ): """ - 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 + 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) diff --git a/django/dispatch/errors.py b/django/dispatch/errors.py deleted file mode 100644 index a4c924dbe9..0000000000 --- a/django/dispatch/errors.py +++ /dev/null @@ -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)""" - diff --git a/django/dispatch/license.txt b/django/dispatch/license.txt index 8ff5d28613..505090dff9 100644 --- a/django/dispatch/license.txt +++ b/django/dispatch/license.txt @@ -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. diff --git a/django/dispatch/robust.py b/django/dispatch/robust.py deleted file mode 100644 index 8b1590de35..0000000000 --- a/django/dispatch/robust.py +++ /dev/null @@ -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 diff --git a/django/dispatch/robustapply.py b/django/dispatch/robustapply.py deleted file mode 100644 index 14ba2b51ac..0000000000 --- a/django/dispatch/robustapply.py +++ /dev/null @@ -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) diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 2abe3300da..8bcfd8a140 100644 --- a/django/dispatch/saferef.py +++ b/django/dispatch/saferef.py @@ -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) - |
