app/django/dispatch/dispatcher.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """Multiple-producer-multiple-consumer signal-dispatching
       
     2 
       
     3 dispatcher is the core of the PyDispatcher system,
       
     4 providing the primary API and the core logic for the
       
     5 system.
       
     6 
       
     7 Module attributes of note:
       
     8 
       
     9     Any -- Singleton used to signal either "Any Sender" or
       
    10         "Any Signal".  See documentation of the _Any class.
       
    11     Anonymous -- Singleton used to signal "Anonymous Sender"
       
    12         See documentation of the _Anonymous class.
       
    13 
       
    14 Internal attributes:
       
    15     WEAKREF_TYPES -- tuple of types/classes which represent
       
    16         weak references to receivers, and thus must be de-
       
    17         referenced on retrieval to retrieve the callable
       
    18         object
       
    19     connections -- { senderkey (id) : { signal : [receivers...]}}
       
    20     senders -- { senderkey (id) : weakref(sender) }
       
    21         used for cleaning up sender references on sender
       
    22         deletion
       
    23     sendersBack -- { receiverkey (id) : [senderkey (id)...] }
       
    24         used for cleaning up receiver references on receiver
       
    25         deletion, (considerably speeds up the cleanup process
       
    26         vs. the original code.)
       
    27 """
       
    28 import types, weakref
       
    29 from django.dispatch import saferef, robustapply, errors
       
    30 
       
    31 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
       
    32 __cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
       
    33 __version__ = "$Revision: 1.9 $"[11:-2]
       
    34 
       
    35 
       
    36 class _Parameter:
       
    37     """Used to represent default parameter values."""
       
    38     def __repr__(self):
       
    39         return self.__class__.__name__
       
    40 
       
    41 class _Any(_Parameter):
       
    42     """Singleton used to signal either "Any Sender" or "Any Signal"
       
    43 
       
    44     The Any object can be used with connect, disconnect,
       
    45     send, or sendExact to signal that the parameter given
       
    46     Any should react to all senders/signals, not just
       
    47     a particular sender/signal.
       
    48     """
       
    49 Any = _Any()
       
    50 
       
    51 class _Anonymous(_Parameter):
       
    52     """Singleton used to signal "Anonymous Sender"
       
    53 
       
    54     The Anonymous object is used to signal that the sender
       
    55     of a message is not specified (as distinct from being
       
    56     "any sender").  Registering callbacks for Anonymous
       
    57     will only receive messages sent without senders.  Sending
       
    58     with anonymous will only send messages to those receivers
       
    59     registered for Any or Anonymous.
       
    60 
       
    61     Note:
       
    62         The default sender for connect is Any, while the
       
    63         default sender for send is Anonymous.  This has
       
    64         the effect that if you do not specify any senders
       
    65         in either function then all messages are routed
       
    66         as though there was a single sender (Anonymous)
       
    67         being used everywhere.
       
    68     """
       
    69 Anonymous = _Anonymous()
       
    70 
       
    71 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
       
    72 
       
    73 connections = {}
       
    74 senders = {}
       
    75 sendersBack = {}
       
    76 
       
    77 
       
    78 def connect(receiver, signal=Any, sender=Any, weak=True):
       
    79     """Connect receiver to sender for signal
       
    80 
       
    81     receiver -- a callable Python object which is to receive
       
    82         messages/signals/events.  Receivers must be hashable
       
    83         objects.
       
    84 
       
    85         if weak is True, then receiver must be weak-referencable
       
    86         (more precisely saferef.safeRef() must be able to create
       
    87         a reference to the receiver).
       
    88     
       
    89         Receivers are fairly flexible in their specification,
       
    90         as the machinery in the robustApply module takes care
       
    91         of most of the details regarding figuring out appropriate
       
    92         subsets of the sent arguments to apply to a given
       
    93         receiver.
       
    94 
       
    95         Note:
       
    96             if receiver is itself a weak reference (a callable),
       
    97             it will be de-referenced by the system's machinery,
       
    98             so *generally* weak references are not suitable as
       
    99             receivers, though some use might be found for the
       
   100             facility whereby a higher-level library passes in
       
   101             pre-weakrefed receiver references.
       
   102 
       
   103     signal -- the signal to which the receiver should respond
       
   104     
       
   105         if Any, receiver will receive any signal from the
       
   106         indicated sender (which might also be Any, but is not
       
   107         necessarily Any).
       
   108         
       
   109         Otherwise must be a hashable Python object other than
       
   110         None (DispatcherError raised on None).
       
   111         
       
   112     sender -- the sender to which the receiver should respond
       
   113     
       
   114         if Any, receiver will receive the indicated signals
       
   115         from any sender.
       
   116         
       
   117         if Anonymous, receiver will only receive indicated
       
   118         signals from send/sendExact which do not specify a
       
   119         sender, or specify Anonymous explicitly as the sender.
       
   120 
       
   121         Otherwise can be any python object.
       
   122         
       
   123     weak -- whether to use weak references to the receiver
       
   124         By default, the module will attempt to use weak
       
   125         references to the receiver objects.  If this parameter
       
   126         is false, then strong references will be used.
       
   127 
       
   128     returns None, may raise DispatcherTypeError
       
   129     """
       
   130     if signal is None:
       
   131         raise errors.DispatcherTypeError(
       
   132             'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
       
   133         )
       
   134     if weak:
       
   135         receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
       
   136     senderkey = id(sender)
       
   137 
       
   138     signals = connections.setdefault(senderkey, {})
       
   139 
       
   140     # Keep track of senders for cleanup.
       
   141     # Is Anonymous something we want to clean up?
       
   142     if sender not in (None, Anonymous, Any):
       
   143         def remove(object, senderkey=senderkey):
       
   144             _removeSender(senderkey=senderkey)
       
   145         # Skip objects that can not be weakly referenced, which means
       
   146         # they won't be automatically cleaned up, but that's too bad.
       
   147         try:
       
   148             weakSender = weakref.ref(sender, remove)
       
   149             senders[senderkey] = weakSender
       
   150         except:
       
   151             pass
       
   152         
       
   153     receiverID = id(receiver)
       
   154     # get current set, remove any current references to
       
   155     # this receiver in the set, including back-references
       
   156     if signals.has_key(signal):
       
   157         receivers = signals[signal]
       
   158         _removeOldBackRefs(senderkey, signal, receiver, receivers)
       
   159     else:
       
   160         receivers = signals[signal] = []
       
   161     try:
       
   162         current = sendersBack.get( receiverID )
       
   163         if current is None:
       
   164             sendersBack[ receiverID ] = current = []
       
   165         if senderkey not in current:
       
   166             current.append(senderkey)
       
   167     except:
       
   168         pass
       
   169 
       
   170     receivers.append(receiver)
       
   171 
       
   172 
       
   173 
       
   174 def disconnect(receiver, signal=Any, sender=Any, weak=True):
       
   175     """Disconnect receiver from sender for signal
       
   176 
       
   177     receiver -- the registered receiver to disconnect
       
   178     signal -- the registered signal to disconnect
       
   179     sender -- the registered sender to disconnect
       
   180     weak -- the weakref state to disconnect
       
   181 
       
   182     disconnect reverses the process of connect,
       
   183     the semantics for the individual elements are
       
   184     logically equivalent to a tuple of
       
   185     (receiver, signal, sender, weak) used as a key
       
   186     to be deleted from the internal routing tables.
       
   187     (The actual process is slightly more complex
       
   188     but the semantics are basically the same).
       
   189 
       
   190     Note:
       
   191         Using disconnect is not required to cleanup
       
   192         routing when an object is deleted, the framework
       
   193         will remove routes for deleted objects
       
   194         automatically.  It's only necessary to disconnect
       
   195         if you want to stop routing to a live object.
       
   196         
       
   197     returns None, may raise DispatcherTypeError or
       
   198         DispatcherKeyError
       
   199     """
       
   200     if signal is None:
       
   201         raise errors.DispatcherTypeError(
       
   202             'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
       
   203         )
       
   204     if weak: receiver = saferef.safeRef(receiver)
       
   205     senderkey = id(sender)
       
   206     try:
       
   207         signals = connections[senderkey]
       
   208         receivers = signals[signal]
       
   209     except KeyError:
       
   210         raise errors.DispatcherKeyError(
       
   211             """No receivers found for signal %r from sender %r""" %(
       
   212                 signal,
       
   213                 sender
       
   214             )
       
   215         )
       
   216     try:
       
   217         # also removes from receivers
       
   218         _removeOldBackRefs(senderkey, signal, receiver, receivers)
       
   219     except ValueError:
       
   220         raise errors.DispatcherKeyError(
       
   221             """No connection to receiver %s for signal %s from sender %s""" %(
       
   222                 receiver,
       
   223                 signal,
       
   224                 sender
       
   225             )
       
   226         )
       
   227     _cleanupConnections(senderkey, signal)
       
   228 
       
   229 def getReceivers( sender = Any, signal = Any ):
       
   230     """Get list of receivers from global tables
       
   231 
       
   232     This utility function allows you to retrieve the
       
   233     raw list of receivers from the connections table
       
   234     for the given sender and signal pair.
       
   235 
       
   236     Note:
       
   237         there is no guarantee that this is the actual list
       
   238         stored in the connections table, so the value
       
   239         should be treated as a simple iterable/truth value
       
   240         rather than, for instance a list to which you
       
   241         might append new records.
       
   242 
       
   243     Normally you would use liveReceivers( getReceivers( ...))
       
   244     to retrieve the actual receiver objects as an iterable
       
   245     object.
       
   246     """
       
   247     existing = connections.get(id(sender))
       
   248     if existing is not None:
       
   249         return existing.get(signal, [])
       
   250     return []
       
   251 
       
   252 def liveReceivers(receivers):
       
   253     """Filter sequence of receivers to get resolved, live receivers
       
   254 
       
   255     This is a generator which will iterate over
       
   256     the passed sequence, checking for weak references
       
   257     and resolving them, then returning all live
       
   258     receivers.
       
   259     """
       
   260     for receiver in receivers:
       
   261         if isinstance( receiver, WEAKREF_TYPES):
       
   262             # Dereference the weak reference.
       
   263             receiver = receiver()
       
   264             if receiver is not None:
       
   265                 yield receiver
       
   266         else:
       
   267             yield receiver
       
   268 
       
   269 
       
   270 
       
   271 def getAllReceivers( sender = Any, signal = Any ):
       
   272     """Get list of all receivers from global tables
       
   273 
       
   274     This gets all dereferenced receivers which should receive
       
   275     the given signal from sender, each receiver should
       
   276     be produced only once by the resulting generator
       
   277     """
       
   278     receivers = {}
       
   279     # Get receivers that receive *this* signal from *this* sender.
       
   280     # Add receivers that receive *any* signal from *this* sender.
       
   281     # Add receivers that receive *this* signal from *any* sender.
       
   282     # Add receivers that receive *any* signal from *any* sender.
       
   283     l = []
       
   284     i = id(sender)
       
   285     if i in connections:
       
   286         sender_receivers = connections[i]
       
   287         if signal in sender_receivers:
       
   288             l.extend(sender_receivers[signal])
       
   289         if signal is not Any and Any in sender_receivers:
       
   290             l.extend(sender_receivers[Any])
       
   291 
       
   292     if sender is not Any:
       
   293         i = id(Any)
       
   294         if i in connections:
       
   295             sender_receivers = connections[i]
       
   296             if sender_receivers is not None:
       
   297                 if signal in sender_receivers:
       
   298                     l.extend(sender_receivers[signal])
       
   299                 if signal is not Any and Any in sender_receivers:
       
   300                     l.extend(sender_receivers[Any])
       
   301 
       
   302     for receiver in l:
       
   303         try:
       
   304             if not receiver in receivers:
       
   305                 if isinstance(receiver, WEAKREF_TYPES):
       
   306                     receiver = receiver()
       
   307                     # this should only (rough guess) be possible if somehow, deref'ing
       
   308                     # triggered a wipe.
       
   309                     if receiver is None:
       
   310                         continue
       
   311                 receivers[receiver] = 1
       
   312                 yield receiver
       
   313         except TypeError:
       
   314             # dead weakrefs raise TypeError on hash...
       
   315             pass
       
   316 
       
   317 def send(signal=Any, sender=Anonymous, *arguments, **named):
       
   318     """Send signal from sender to all connected receivers.
       
   319     
       
   320     signal -- (hashable) signal value, see connect for details
       
   321 
       
   322     sender -- the sender of the signal
       
   323     
       
   324         if Any, only receivers registered for Any will receive
       
   325         the message.
       
   326 
       
   327         if Anonymous, only receivers registered to receive
       
   328         messages from Anonymous or Any will receive the message
       
   329 
       
   330         Otherwise can be any python object (normally one
       
   331         registered with a connect if you actually want
       
   332         something to occur).
       
   333 
       
   334     arguments -- positional arguments which will be passed to
       
   335         *all* receivers. Note that this may raise TypeErrors
       
   336         if the receivers do not allow the particular arguments.
       
   337         Note also that arguments are applied before named
       
   338         arguments, so they should be used with care.
       
   339 
       
   340     named -- named arguments which will be filtered according
       
   341         to the parameters of the receivers to only provide those
       
   342         acceptable to the receiver.
       
   343 
       
   344     Return a list of tuple pairs [(receiver, response), ... ]
       
   345 
       
   346     if any receiver raises an error, the error propagates back
       
   347     through send, terminating the dispatch loop, so it is quite
       
   348     possible to not have all receivers called if a raises an
       
   349     error.
       
   350     """
       
   351     # Call each receiver with whatever arguments it can accept.
       
   352     # Return a list of tuple pairs [(receiver, response), ... ].
       
   353     responses = []
       
   354     for receiver in getAllReceivers(sender, signal):
       
   355         response = robustapply.robustApply(
       
   356             receiver,
       
   357             signal=signal,
       
   358             sender=sender,
       
   359             *arguments,
       
   360             **named
       
   361         )
       
   362         responses.append((receiver, response))
       
   363     return responses
       
   364 
       
   365 
       
   366 def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
       
   367     """Send signal only to those receivers registered for exact message
       
   368 
       
   369     sendExact allows for avoiding Any/Anonymous registered
       
   370     handlers, sending only to those receivers explicitly
       
   371     registered for a particular signal on a particular
       
   372     sender.
       
   373     """
       
   374     responses = []
       
   375     for receiver in liveReceivers(getReceivers(sender, signal)):
       
   376         response = robustapply.robustApply(
       
   377             receiver,
       
   378             signal=signal,
       
   379             sender=sender,
       
   380             *arguments,
       
   381             **named
       
   382         )
       
   383         responses.append((receiver, response))
       
   384     return responses
       
   385     
       
   386 
       
   387 def _removeReceiver(receiver):
       
   388     """Remove receiver from connections."""
       
   389     if not sendersBack:
       
   390         # During module cleanup the mapping will be replaced with None
       
   391         return False
       
   392     backKey = id(receiver)
       
   393     for senderkey in sendersBack.get(backKey,()):
       
   394         try:
       
   395             signals = connections[senderkey].keys()
       
   396         except KeyError,err:
       
   397             pass
       
   398         else:
       
   399             for signal in signals:
       
   400                 try:
       
   401                     receivers = connections[senderkey][signal]
       
   402                 except KeyError:
       
   403                     pass
       
   404                 else:
       
   405                     try:
       
   406                         receivers.remove( receiver )
       
   407                     except Exception, err:
       
   408                         pass
       
   409                 _cleanupConnections(senderkey, signal)
       
   410     try:
       
   411         del sendersBack[ backKey ]
       
   412     except KeyError:
       
   413         pass
       
   414             
       
   415 def _cleanupConnections(senderkey, signal):
       
   416     """Delete any empty signals for senderkey. Delete senderkey if empty."""
       
   417     try:
       
   418         receivers = connections[senderkey][signal]
       
   419     except:
       
   420         pass
       
   421     else:
       
   422         if not receivers:
       
   423             # No more connected receivers. Therefore, remove the signal.
       
   424             try:
       
   425                 signals = connections[senderkey]
       
   426             except KeyError:
       
   427                 pass
       
   428             else:
       
   429                 del signals[signal]
       
   430                 if not signals:
       
   431                     # No more signal connections. Therefore, remove the sender.
       
   432                     _removeSender(senderkey)
       
   433 
       
   434 def _removeSender(senderkey):
       
   435     """Remove senderkey from connections."""
       
   436     _removeBackrefs(senderkey)
       
   437 
       
   438     connections.pop(senderkey, None)
       
   439     senders.pop(senderkey, None)
       
   440 
       
   441 
       
   442 def _removeBackrefs( senderkey):
       
   443     """Remove all back-references to this senderkey"""
       
   444     for receiver_list in connections.pop(senderkey, {}).values():
       
   445         for receiver in receiver_list:
       
   446             _killBackref( receiver, senderkey )
       
   447 
       
   448 
       
   449 def _removeOldBackRefs(senderkey, signal, receiver, receivers):
       
   450     """Kill old sendersBack references from receiver
       
   451 
       
   452     This guards against multiple registration of the same
       
   453     receiver for a given signal and sender leaking memory
       
   454     as old back reference records build up.
       
   455 
       
   456     Also removes old receiver instance from receivers
       
   457     """
       
   458     try:
       
   459         index = receivers.index(receiver)
       
   460         # need to scan back references here and remove senderkey
       
   461     except ValueError:
       
   462         return False
       
   463     else:
       
   464         oldReceiver = receivers[index]
       
   465         del receivers[index]
       
   466         found = 0
       
   467         signals = connections.get(signal)
       
   468         if signals is not None:
       
   469             for sig,recs in connections.get(signal,{}).iteritems():
       
   470                 if sig != signal:
       
   471                     for rec in recs:
       
   472                         if rec is oldReceiver:
       
   473                             found = 1
       
   474                             break
       
   475         if not found:
       
   476             _killBackref( oldReceiver, senderkey )
       
   477             return True
       
   478         return False
       
   479         
       
   480         
       
   481 def _killBackref( receiver, senderkey ):
       
   482     """Do the actual removal of back reference from receiver to senderkey"""
       
   483     receiverkey = id(receiver)
       
   484     receivers_list = sendersBack.get( receiverkey, () )
       
   485     while senderkey in receivers_list:
       
   486         try:
       
   487             receivers_list.remove( senderkey )
       
   488         except:
       
   489             break
       
   490     if not receivers_list:
       
   491         try:
       
   492             del sendersBack[ receiverkey ]
       
   493         except KeyError:
       
   494             pass
       
   495     return True