0001"""Multiple-producer-multiple-consumer signal-dispatching.
0002
0003``dispatcher`` is the core of Louie, providing the primary API and the
0004core logic for the system.
0005
0006Internal attributes:
0007
0008- ``WEAKREF_TYPES``: Tuple of types/classes which represent weak
0009 references to receivers, and thus must be dereferenced on retrieval
0010 to retrieve the callable object
0011
0012- ``connections``::
0013
0014 { senderkey (id) : { signal : [receivers...] } }
0015
0016- ``senders``: Used for cleaning up sender references on sender
0017 deletion::
0018
0019 { senderkey (id) : weakref(sender) }
0020
0021- ``senders_back``: Used for cleaning up receiver references on receiver
0022 deletion::
0023
0024 { receiverkey (id) : [senderkey (id)...] }
0025"""
0026
0027import os
0028import weakref
0029
0030try:
0031 set
0032except NameError:
0033 from sets import Set as set, ImmutableSet as frozenset
0034
0035from louie import error
0036from louie import robustapply
0037from louie import saferef
0038from louie.sender import Any, Anonymous
0039from louie.signal import All
0040
0041
0042
0043if __debug__:
0044 connects = 0
0045 disconnects = 0
0046 sends = 0
0047
0048 def print_stats():
0049 print ('\n'
0050 'Louie connects: %i\n'
0051 'Louie disconnects: %i\n'
0052 'Louie sends: %i\n'
0053 '\n') % (connects, disconnects, sends)
0054
0055 if 'PYDISPATCH_STATS' in os.environ:
0056 import atexit
0057 atexit.register(print_stats)
0058
0059
0060
0061WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
0062
0063
0064connections = {}
0065senders = {}
0066senders_back = {}
0067plugins = []
0068
0069def reset():
0070 """Reset the state of Louie.
0071
0072 Useful during unit testing. Should be avoided otherwise.
0073 """
0074 global connections, senders, senders_back, plugins
0075 connections = {}
0076 senders = {}
0077 senders_back = {}
0078 plugins = []
0079
0080
0081def connect(receiver, signal=All, sender=Any, weak=True):
0082 """Connect ``receiver`` to ``sender`` for ``signal``.
0083
0084 - ``receiver``: A callable Python object which is to receive
0085 messages/signals/events. Receivers must be hashable objects.
0086
0087 If weak is ``True``, then receiver must be weak-referencable (more
0088 precisely ``saferef.safe_ref()`` must be able to create a
0089 reference to the receiver).
0090
0091 Receivers are fairly flexible in their specification, as the
0092 machinery in the ``robustapply`` module takes care of most of the
0093 details regarding figuring out appropriate subsets of the sent
0094 arguments to apply to a given receiver.
0095
0096 Note: If ``receiver`` is itself a weak reference (a callable), it
0097 will be de-referenced by the system's machinery, so *generally*
0098 weak references are not suitable as receivers, though some use
0099 might be found for the facility whereby a higher-level library
0100 passes in pre-weakrefed receiver references.
0101
0102 - ``signal``: The signal to which the receiver should respond.
0103
0104 If ``All``, receiver will receive all signals from the indicated
0105 sender (which might also be ``All``, but is not necessarily
0106 ``All``).
0107
0108 Otherwise must be a hashable Python object other than ``None``
0109 (``DispatcherError`` raised on ``None``).
0110
0111 - ``sender``: The sender to which the receiver should respond.
0112
0113 If ``Any``, receiver will receive the indicated signals from any
0114 sender.
0115
0116 If ``Anonymous``, receiver will only receive indicated signals
0117 from ``send``/``send_exact`` which do not specify a sender, or
0118 specify ``Anonymous`` explicitly as the sender.
0119
0120 Otherwise can be any python object.
0121
0122 - ``weak``: Whether to use weak references to the receiver.
0123
0124 By default, the module will attempt to use weak references to
0125 the receiver objects. If this parameter is ``False``, then strong
0126 references will be used.
0127
0128 Returns ``None``, may raise ``DispatcherTypeError``.
0129 """
0130 if signal is None:
0131 raise error.DispatcherTypeError(
0132 'Signal cannot be None (receiver=%r sender=%r)'
0133 % (receiver, sender))
0134 if weak:
0135 receiver = saferef.safe_ref(receiver, on_delete=_remove_receiver)
0136 senderkey = id(sender)
0137 if connections.has_key(senderkey):
0138 signals = connections[senderkey]
0139 else:
0140 connections[senderkey] = signals = {}
0141
0142
0143 if sender not in (None, Anonymous, Any):
0144 def remove(object, senderkey=senderkey):
0145 _remove_sender(senderkey=senderkey)
0146
0147
0148 try:
0149 weak_sender = weakref.ref(sender, remove)
0150 senders[senderkey] = weak_sender
0151 except:
0152 pass
0153 receiver_id = id(receiver)
0154
0155
0156 if signals.has_key(signal):
0157 receivers = signals[signal]
0158 _remove_old_back_refs(senderkey, signal, receiver, receivers)
0159 else:
0160 receivers = signals[signal] = []
0161 try:
0162 current = senders_back.get(receiver_id)
0163 if current is None:
0164 senders_back[receiver_id] = current = []
0165 if senderkey not in current:
0166 current.append(senderkey)
0167 except:
0168 pass
0169 receivers.append(receiver)
0170
0171 if __debug__:
0172 global connects
0173 connects += 1
0174
0175
0176def disconnect(receiver, signal=All, sender=Any, weak=True):
0177 """Disconnect ``receiver`` from ``sender`` for ``signal``.
0178
0179 - ``receiver``: The registered receiver to disconnect.
0180
0181 - ``signal``: The registered signal to disconnect.
0182
0183 - ``sender``: The registered sender to disconnect.
0184
0185 - ``weak``: The weakref state to disconnect.
0186
0187 ``disconnect`` reverses the process of ``connect``, the semantics for
0188 the individual elements are logically equivalent to a tuple of
0189 ``(receiver, signal, sender, weak)`` used as a key to be deleted
0190 from the internal routing tables. (The actual process is slightly
0191 more complex but the semantics are basically the same).
0192
0193 Note: Using ``disconnect`` is not required to cleanup routing when
0194 an object is deleted; the framework will remove routes for deleted
0195 objects automatically. It's only necessary to disconnect if you
0196 want to stop routing to a live object.
0197
0198 Returns ``None``, may raise ``DispatcherTypeError`` or
0199 ``DispatcherKeyError``.
0200 """
0201 if signal is None:
0202 raise error.DispatcherTypeError(
0203 'Signal cannot be None (receiver=%r sender=%r)'
0204 % (receiver, sender))
0205 if weak:
0206 receiver = saferef.safe_ref(receiver)
0207 senderkey = id(sender)
0208 try:
0209 signals = connections[senderkey]
0210 receivers = signals[signal]
0211 except KeyError:
0212 raise error.DispatcherKeyError(
0213 'No receivers found for signal %r from sender %r'
0214 % (signal, sender)
0215 )
0216 try:
0217
0218 _remove_old_back_refs(senderkey, signal, receiver, receivers)
0219 except ValueError:
0220 raise error.DispatcherKeyError(
0221 'No connection to receiver %s for signal %s from sender %s'
0222 % (receiver, signal, sender)
0223 )
0224 _cleanup_connections(senderkey, signal)
0225
0226 if __debug__:
0227 global disconnects
0228 disconnects += 1
0229
0230
0231def get_receivers(sender=Any, signal=All):
0232 """Get list of receivers from global tables.
0233
0234 This function allows you to retrieve the raw list of receivers
0235 from the connections table for the given sender and signal pair.
0236
0237 Note: There is no guarantee that this is the actual list stored in
0238 the connections table, so the value should be treated as a simple
0239 iterable/truth value rather than, for instance a list to which you
0240 might append new records.
0241
0242 Normally you would use ``live_receivers(get_receivers(...))`` to
0243 retrieve the actual receiver objects as an iterable object.
0244 """
0245 try:
0246 return connections[id(sender)][signal]
0247 except KeyError:
0248 return []
0249
0250
0251def live_receivers(receivers):
0252 """Filter sequence of receivers to get resolved, live receivers.
0253
0254 This is a generator which will iterate over the passed sequence,
0255 checking for weak references and resolving them, then returning
0256 all live receivers.
0257 """
0258 for receiver in receivers:
0259 if isinstance(receiver, WEAKREF_TYPES):
0260
0261 receiver = receiver()
0262 if receiver is not None:
0263
0264
0265 live = True
0266 for plugin in plugins:
0267 if not plugin.is_live(receiver):
0268 live = False
0269 break
0270 if live:
0271 yield receiver
0272
0273
0274def get_all_receivers(sender=Any, signal=All):
0275 """Get list of all receivers from global tables.
0276
0277 This gets all receivers which should receive the given signal from
0278 sender, each receiver should be produced only once by the
0279 resulting generator.
0280 """
0281 yielded = set()
0282 for receivers in (
0283
0284 get_receivers(sender, signal),
0285
0286 get_receivers(sender, All),
0287
0288 get_receivers(Any, signal),
0289
0290 get_receivers(Any, All),
0291 ):
0292 for receiver in receivers:
0293 if receiver:
0294 try:
0295 if not receiver in yielded:
0296 yielded.add(receiver)
0297 yield receiver
0298 except TypeError:
0299
0300 pass
0301
0302
0303def send(signal=All, sender=Anonymous, *arguments, **named):
0304 """Send ``signal`` from ``sender`` to all connected receivers.
0305
0306 - ``signal``: (Hashable) signal value; see ``connect`` for details.
0307
0308 - ``sender``: The sender of the signal.
0309
0310 If ``Any``, only receivers registered for ``Any`` will receive the
0311 message.
0312
0313 If ``Anonymous``, only receivers registered to receive messages
0314 from ``Anonymous`` or ``Any`` will receive the message.
0315
0316 Otherwise can be any Python object (normally one registered with
0317 a connect if you actually want something to occur).
0318
0319 - ``arguments``: Positional arguments which will be passed to *all*
0320 receivers. Note that this may raise ``TypeError`` if the receivers
0321 do not allow the particular arguments. Note also that arguments
0322 are applied before named arguments, so they should be used with
0323 care.
0324
0325 - ``named``: Named arguments which will be filtered according to the
0326 parameters of the receivers to only provide those acceptable to
0327 the receiver.
0328
0329 Return a list of tuple pairs ``[(receiver, response), ...]``
0330
0331 If any receiver raises an error, the error propagates back through
0332 send, terminating the dispatch loop, so it is quite possible to
0333 not have all receivers called if a raises an error.
0334 """
0335
0336
0337 responses = []
0338 for receiver in live_receivers(get_all_receivers(sender, signal)):
0339
0340 original = receiver
0341 for plugin in plugins:
0342 receiver = plugin.wrap_receiver(receiver)
0343 response = robustapply.robust_apply(
0344 receiver, original,
0345 signal=signal,
0346 sender=sender,
0347 *arguments,
0348 **named
0349 )
0350 responses.append((receiver, response))
0351
0352 if __debug__:
0353 global sends
0354 sends += 1
0355 return responses
0356
0357
0358def send_minimal(signal=All, sender=Anonymous, *arguments, **named):
0359 """Like ``send``, but does not attach ``signal`` and ``sender``
0360 arguments to the call to the receiver."""
0361
0362
0363 responses = []
0364 for receiver in live_receivers(get_all_receivers(sender, signal)):
0365
0366 original = receiver
0367 for plugin in plugins:
0368 receiver = plugin.wrap_receiver(receiver)
0369 response = robustapply.robust_apply(
0370 receiver, original,
0371 *arguments,
0372 **named
0373 )
0374 responses.append((receiver, response))
0375
0376 if __debug__:
0377 global sends
0378 sends += 1
0379 return responses
0380
0381
0382def send_exact(signal=All, sender=Anonymous, *arguments, **named):
0383 """Send ``signal`` only to receivers registered for exact message.
0384
0385 ``send_exact`` allows for avoiding ``Any``/``Anonymous`` registered
0386 handlers, sending only to those receivers explicitly registered
0387 for a particular signal on a particular sender.
0388 """
0389 responses = []
0390 for receiver in live_receivers(get_receivers(sender, signal)):
0391
0392 original = receiver
0393 for plugin in plugins:
0394 receiver = plugin.wrap_receiver(receiver)
0395 response = robustapply.robust_apply(
0396 receiver, original,
0397 signal=signal,
0398 sender=sender,
0399 *arguments,
0400 **named
0401 )
0402 responses.append((receiver, response))
0403 return responses
0404
0405
0406def send_robust(signal=All, sender=Anonymous, *arguments, **named):
0407 """Send ``signal`` from ``sender`` to all connected receivers catching
0408 errors
0409
0410 - ``signal``: (Hashable) signal value, see connect for details
0411
0412 - ``sender``: The sender of the signal.
0413
0414 If ``Any``, only receivers registered for ``Any`` will receive the
0415 message.
0416
0417 If ``Anonymous``, only receivers registered to receive messages
0418 from ``Anonymous`` or ``Any`` will receive the message.
0419
0420 Otherwise can be any Python object (normally one registered with
0421 a connect if you actually want something to occur).
0422
0423 - ``arguments``: Positional arguments which will be passed to *all*
0424 receivers. Note that this may raise ``TypeError`` if the receivers
0425 do not allow the particular arguments. Note also that arguments
0426 are applied before named arguments, so they should be used with
0427 care.
0428
0429 - ``named``: Named arguments which will be filtered according to the
0430 parameters of the receivers to only provide those acceptable to
0431 the receiver.
0432
0433 Return a list of tuple pairs ``[(receiver, response), ... ]``
0434
0435 If any receiver raises an error (specifically, any subclass of
0436 ``Exception``), the error instance is returned as the result for
0437 that receiver.
0438 """
0439
0440
0441 responses = []
0442 for receiver in live_receivers(get_all_receivers(sender, signal)):
0443 original = receiver
0444 for plugin in plugins:
0445 receiver = plugin.wrap_receiver(receiver)
0446 try:
0447 response = robustapply.robust_apply(
0448 receiver, original,
0449 signal=signal,
0450 sender=sender,
0451 *arguments,
0452 **named
0453 )
0454 except Exception, err:
0455 responses.append((receiver, err))
0456 else:
0457 responses.append((receiver, response))
0458 return responses
0459
0460
0461def _remove_receiver(receiver):
0462 """Remove ``receiver`` from connections."""
0463 if not senders_back:
0464
0465 return False
0466 backKey = id(receiver)
0467 for senderkey in senders_back.get(backKey, ()):
0468 try:
0469 signals = connections[senderkey].keys()
0470 except KeyError:
0471 pass
0472 else:
0473 for signal in signals:
0474 try:
0475 receivers = connections[senderkey][signal]
0476 except KeyError:
0477 pass
0478 else:
0479 try:
0480 receivers.remove(receiver)
0481 except Exception:
0482 pass
0483 _cleanup_connections(senderkey, signal)
0484 try:
0485 del senders_back[backKey]
0486 except KeyError:
0487 pass
0488
0489
0490def _cleanup_connections(senderkey, signal):
0491 """Delete empty signals for ``senderkey``. Delete ``senderkey`` if
0492 empty."""
0493 try:
0494 receivers = connections[senderkey][signal]
0495 except:
0496 pass
0497 else:
0498 if not receivers:
0499
0500 try:
0501 signals = connections[senderkey]
0502 except KeyError:
0503 pass
0504 else:
0505 del signals[signal]
0506 if not signals:
0507
0508 _remove_sender(senderkey)
0509
0510
0511def _remove_sender(senderkey):
0512 """Remove ``senderkey`` from connections."""
0513 _remove_back_refs(senderkey)
0514 try:
0515 del connections[senderkey]
0516 except KeyError:
0517 pass
0518
0519
0520 try:
0521 del senders[senderkey]
0522 except:
0523 pass
0524
0525
0526def _remove_back_refs(senderkey):
0527 """Remove all back-references to this ``senderkey``."""
0528 try:
0529 signals = connections[senderkey]
0530 except KeyError:
0531 signals = None
0532 else:
0533 for signal, receivers in signals.iteritems():
0534 for receiver in receivers:
0535 _kill_back_ref(receiver, senderkey)
0536
0537
0538def _remove_old_back_refs(senderkey, signal, receiver, receivers):
0539 """Kill old ``senders_back`` references from ``receiver``.
0540
0541 This guards against multiple registration of the same receiver for
0542 a given signal and sender leaking memory as old back reference
0543 records build up.
0544
0545 Also removes old receiver instance from receivers.
0546 """
0547 try:
0548 index = receivers.index(receiver)
0549
0550 except ValueError:
0551 return False
0552 else:
0553 old_receiver = receivers[index]
0554 del receivers[index]
0555 found = 0
0556 signals = connections.get(signal)
0557 if signals is not None:
0558 for sig, recs in connections.get(signal, {}).iteritems():
0559 if sig != signal:
0560 for rec in recs:
0561 if rec is old_receiver:
0562 found = 1
0563 break
0564 if not found:
0565 _kill_back_ref(old_receiver, senderkey)
0566 return True
0567 return False
0568
0569
0570def _kill_back_ref(receiver, senderkey):
0571 """Do actual removal of back reference from ``receiver`` to
0572 ``senderkey``."""
0573 receiverkey = id(receiver)
0574 senders = senders_back.get(receiverkey, ())
0575 while senderkey in senders:
0576 try:
0577 senders.remove(senderkey)
0578 except:
0579 break
0580 if not senders:
0581 try:
0582 del senders_back[receiverkey]
0583 except KeyError:
0584 pass
0585 return True