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# Support for statistics.
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    # Keep track of senders for cleanup.
0142    # Is Anonymous something we want to clean up?
0143    if sender not in (None, Anonymous, Any):
0144        def remove(object, senderkey=senderkey):
0145            _remove_sender(senderkey=senderkey)
0146        # Skip objects that can not be weakly referenced, which means
0147        # they won't be automatically cleaned up, but that's too bad.
0148        try:
0149            weak_sender = weakref.ref(sender, remove)
0150            senders[senderkey] = weak_sender
0151        except:
0152            pass
0153    receiver_id = id(receiver)
0154    # get current set, remove any current references to
0155    # this receiver in the set, including back-references
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    # Update stats.
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        # also removes from receivers
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    # Update stats.
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            # Dereference the weak reference.
0261            receiver = receiver()
0262        if receiver is not None:
0263            # Check installed plugins to make sure this receiver is
0264            # live.
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        # Get receivers that receive *this* signal from *this* sender.
0284        get_receivers(sender, signal),
0285        # Add receivers that receive *all* signals from *this* sender.
0286        get_receivers(sender, All),
0287        # Add receivers that receive *this* signal from *any* sender.
0288        get_receivers(Any, signal),
0289        # Add receivers that receive *all* signals from *any* sender.
0290        get_receivers(Any, All),
0291        ):
0292        for receiver in receivers:
0293            if receiver: # filter out dead instance-method weakrefs
0294                try:
0295                    if not receiver in yielded:
0296                        yielded.add(receiver)
0297                        yield receiver
0298                except TypeError:
0299                    # dead weakrefs raise TypeError on hash...
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    # Call each receiver with whatever arguments it can accept.
0336    # Return a list of tuple pairs [(receiver, response), ... ].
0337    responses = []
0338    for receiver in live_receivers(get_all_receivers(sender, signal)):
0339        # Wrap receiver using installed plugins.
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    # Update stats.
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    # Call each receiver with whatever arguments it can accept.
0362    # Return a list of tuple pairs [(receiver, response), ... ].
0363    responses = []
0364    for receiver in live_receivers(get_all_receivers(sender, signal)):
0365        # Wrap receiver using installed plugins.
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    # Update stats.
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        # Wrap receiver using installed plugins.
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    # Call each receiver with whatever arguments it can accept.
0440    # Return a list of tuple pairs [(receiver, response), ... ].
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        # During module cleanup the mapping will be replaced with None.
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            # No more connected receivers. Therefore, remove the signal.
0500            try:
0501                signals = connections[senderkey]
0502            except KeyError:
0503                pass
0504            else:
0505                del signals[signal]
0506                if not signals:
0507                    # No more signal connections. Therefore, remove the sender.
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    # Senderkey will only be in senders dictionary if sender 
0519    # could be weakly referenced.
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        # need to scan back references here and remove senderkey
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