0001from __future__ import print_function
0002import sys
0003import types
0004from pydispatch import dispatcher
0005from weakref import ref
0006from .compat import class_types
0007
0008
0009subclassClones = {}
0010
0011
0012def listen(receiver, soClass, signal, alsoSubclasses=True, weak=True):
0013    """
0014    Listen for the given ``signal`` on the SQLObject subclass
0015    ``soClass``, calling ``receiver()`` when ``send(soClass, signal,
0016    ...)`` is called.
0017
0018    If ``alsoSubclasses`` is true, receiver will also be called when
0019    an event is fired on any subclass.
0020    """
0021    dispatcher.connect(receiver, signal=signal, sender=soClass, weak=weak)
0022    weakReceiver = ref(receiver)
0023    subclassClones.setdefault(soClass, []).append((weakReceiver, signal))
0024
0025# We export this function:
0026send = dispatcher.send
0027
0028
0029class Signal(object):
0030    """
0031    Base event for all SQLObject events.
0032
0033    In general the sender for these methods is the class, not the
0034    instance.
0035    """
0036
0037
0038class ClassCreateSignal(Signal):
0039    """
0040    Signal raised after class creation.  The sender is the superclass
0041    (in case of multiple superclasses, the first superclass).  The
0042    arguments are ``(new_class_name, bases, new_attrs, post_funcs,
0043    early_funcs)``.  ``new_attrs`` is a dictionary and may be modified
0044    (but ``new_class_name`` and ``bases`` are immutable).
0045    ``post_funcs`` is an initially-empty list that can have callbacks
0046    appended to it.
0047
0048    Note: at the time this event is called, the new class has not yet
0049    been created.  The functions in ``post_funcs`` will be called
0050    after the class is created, with the single arguments of
0051    ``(new_class)``.  Also, ``early_funcs`` will be called at the
0052    soonest possible time after class creation (``post_funcs`` is
0053    called after the class's ``__classinit__``).
0054    """
0055
0056
0057def _makeSubclassConnections(new_class_name, bases, new_attrs,
0058                             post_funcs, early_funcs):
0059    early_funcs.insert(0, _makeSubclassConnectionsPost)
0060
0061
0062def _makeSubclassConnectionsPost(new_class):
0063    for cls in new_class.__bases__:
0064        for weakReceiver, signal in subclassClones.get(cls, []):
0065            receiver = weakReceiver()
0066            if not receiver:
0067                continue
0068            listen(receiver, new_class, signal)
0069
0070dispatcher.connect(_makeSubclassConnections, signal=ClassCreateSignal)
0071
0072
0073# @@: Should there be a class reload event?  This would allow modules
0074# to be reloaded, possibly.  Or it could even be folded into
0075# ClassCreateSignal, since anything that listens to that needs to pay
0076# attention to reloads (or else it is probably buggy).
0077
0078
0079class RowCreateSignal(Signal):
0080    """
0081    Called before an instance is created, with the class as the
0082    sender.  Called with the arguments ``(instance, kwargs, post_funcs)``.
0083    There may be a ``connection`` argument.  ``kwargs``may be usefully
0084    modified.  ``post_funcs`` is a list of callbacks, intended to have
0085    functions appended to it, and are called with the arguments
0086    ``(new_instance)``.
0087
0088    Note: this is not called when an instance is created from an
0089    existing database row.
0090    """
0091
0092
0093class RowCreatedSignal(Signal):
0094    """
0095    Called after an instance is created, with the class as the
0096    sender.  Called with the arguments ``(instance, kwargs, post_funcs)``.
0097    There may be a ``connection`` argument.  ``kwargs``may be usefully
0098    modified.  ``post_funcs`` is a list of callbacks, intended to have
0099    functions appended to it, and are called with the arguments
0100    ``(new_instance)``.
0101
0102    Note: this is not called when an instance is created from an
0103    existing database row.
0104    """
0105# @@: An event for getting a row?  But for each row, when doing a
0106# select?  For .sync, .syncUpdate, .expire?
0107
0108
0109class RowDestroySignal(Signal):
0110    """
0111    Called before an instance is deleted.  Sender is the instance's
0112    class.  Arguments are ``(instance, post_funcs)``.
0113
0114    ``post_funcs`` is a list of callbacks, intended to have
0115    functions appended to it, and are called with arguments ``(instance)``.
0116    If any of the post_funcs raises an exception, the deletion is only
0117    affected if this will prevent a commit.
0118
0119    You cannot cancel the delete, but you can raise an exception (which will
0120    probably cancel the delete, but also cause an uncaught exception if not
0121    expected).
0122
0123    Note: this is not called when an instance is destroyed through
0124    garbage collection.
0125
0126    @@: Should this allow ``instance`` to be a primary key, so that a
0127    row can be deleted without first fetching it?
0128    """
0129
0130
0131class RowDestroyedSignal(Signal):
0132    """
0133    Called after an instance is deleted.  Sender is the instance's
0134    class.  Arguments are ``(instance)``.
0135
0136    This is called before the post_funcs of RowDestroySignal
0137
0138    Note: this is not called when an instance is destroyed through
0139    garbage collection.
0140    """
0141
0142
0143class RowUpdateSignal(Signal):
0144    """
0145    Called when an instance is updated through a call to ``.set()``
0146    (or a column attribute assignment).  The arguments are
0147    ``(instance, kwargs)``.  ``kwargs`` can be modified.  This is run
0148    *before* the instance is updated; if you want to look at the
0149    current values, simply look at ``instance``.
0150    """
0151
0152
0153class RowUpdatedSignal(Signal):
0154    """
0155    Called when an instance is updated through a call to ``.set()``
0156    (or a column attribute assignment).  The arguments are
0157    ``(instance, post_funcs)``. ``post_funcs`` is a list of callbacks,
0158    intended to have functions appended to it, and are called with the
0159    arguments ``(new_instance)``. This is run *after* the instance is
0160    updated; Works better with lazyUpdate = True.
0161    """
0162
0163
0164class AddColumnSignal(Signal):
0165    """
0166    Called when a column is added to a class, with arguments ``(cls,
0167    connection, column_name, column_definition, changeSchema,
0168    post_funcs)``.  This is called *after* the column has been added,
0169    and is called for each column after class creation.
0170
0171    post_funcs are called with ``(cls, so_column_obj)``
0172    """
0173
0174
0175class DeleteColumnSignal(Signal):
0176    """
0177    Called when a column is removed from a class, with the arguments
0178    ``(cls, connection, column_name, so_column_obj, post_funcs)``.
0179    Like ``AddColumnSignal`` this is called after the action has been
0180    performed, and is called for subclassing (when a column is
0181    implicitly removed by setting it to ``None``).
0182
0183    post_funcs are called with ``(cls, so_column_obj)``
0184    """
0185
0186# @@: Signals for indexes and joins?  These are mostly event consumers,
0187# though.
0188
0189
0190class CreateTableSignal(Signal):
0191    """
0192    Called when a table is created.  If ``ifNotExists==True`` and the
0193    table exists, this event is not called.
0194
0195    Called with ``(cls, connection, extra_sql, post_funcs)``.
0196    ``extra_sql`` is a list (which can be appended to) of extra SQL
0197    statements to be run after the table is created.  ``post_funcs``
0198    functions are called with ``(cls, connection)`` after the table
0199    has been created.  Those functions are *not* called simply when
0200    constructing the SQL.
0201    """
0202
0203
0204class DropTableSignal(Signal):
0205    """
0206    Called when a table is dropped.  If ``ifExists==True`` and the
0207    table doesn't exist, this event is not called.
0208
0209    Called with ``(cls, connection, extra_sql, post_funcs)``.
0210    ``post_funcs`` functions are called with ``(cls, connection)``
0211    after the table has been dropped.
0212    """
0213
0214############################################################
0215# Event Debugging
0216############################################################
0217
0218
0219def summarize_events_by_sender(sender=None, output=None, indent=0):
0220    """
0221    Prints out a summary of the senders and listeners in the system,
0222    for debugging purposes.
0223    """
0224    if output is None:
0225        output = sys.stdout
0226    leader = ' ' * indent
0227    if sender is None:
0228        send_list = [
0229            (deref(dispatcher.senders.get(sid)), listeners)
0230            for sid, listeners in dispatcher.connections.items()
0231            if deref(dispatcher.senders.get(sid))]
0232        for sender, listeners in sorted_items(send_list):
0233            real_sender = deref(sender)
0234            if not real_sender:
0235                continue
0236            header = 'Sender: %r' % real_sender
0237            print(leader + header, file=output)
0238            print(leader + ('=' * len(header)), file=output)
0239            summarize_events_by_sender(real_sender, output=output,
0240                                       indent=indent + 2)
0241    else:
0242        for signal, receivers in                   sorted_items(dispatcher.connections.get(id(sender), [])):
0244            receivers = [deref(r) for r in receivers if deref(r)]
0245            header = 'Signal: %s (%i receivers)' % (sort_name(signal),
0246                                                    len(receivers))
0247            print(leader + header, file=output)
0248            print(leader + ('-' * len(header)), file=output)
0249            for receiver in sorted(receivers, key=sort_name):
0250                print(leader + '  ' + nice_repr(receiver), file=output)
0251
0252
0253def deref(value):
0254    if isinstance(value, dispatcher.WEAKREF_TYPES):
0255        return value()
0256    else:
0257        return value
0258
0259
0260def sorted_items(a_dict):
0261    if isinstance(a_dict, dict):
0262        a_dict = a_dict.items()
0263    return sorted(a_dict, key=lambda t: sort_name(t[0]))
0264
0265
0266def sort_name(value):
0267    if isinstance(value, type):
0268        return value.__name__
0269    elif isinstance(value, types.FunctionType):
0270        return value.__name__
0271    else:
0272        return str(value)
0273
0274_real_dispatcher_send = dispatcher.send
0275_real_dispatcher_sendExact = dispatcher.sendExact
0276_real_dispatcher_connect = dispatcher.connect
0277_real_dispatcher_disconnect = dispatcher.disconnect
0278_debug_enabled = False
0279
0280
0281def debug_events():
0282    global _debug_enabled, send
0283    if _debug_enabled:
0284        return
0285    _debug_enabled = True
0286    dispatcher.send = send = _debug_send
0287    dispatcher.sendExact = _debug_sendExact
0288    dispatcher.disconnect = _debug_disconnect
0289    dispatcher.connect = _debug_connect
0290
0291
0292def _debug_send(signal=dispatcher.Any, sender=dispatcher.Anonymous,
0293                *arguments, **named):
0294    print("send %s from %s: %s" % (
0295          nice_repr(signal), nice_repr(sender),
0296          fmt_args(*arguments, **named)))
0297    return _real_dispatcher_send(signal, sender, *arguments, **named)
0298
0299
0300def _debug_sendExact(signal=dispatcher.Any, sender=dispatcher.Anonymous,
0301                     *arguments, **named):
0302    print("sendExact %s from %s: %s" % (
0303          nice_repr(signal), nice_repr(sender), fmt_args(*arguments, **name)))
0304    return _real_dispatcher_sendExact(signal, sender, *arguments, **named)
0305
0306
0307def _debug_connect(receiver, signal=dispatcher.Any, sender=dispatcher.Any,
0308                   weak=True):
0309    print("connect %s to %s signal %s" % (
0310          nice_repr(receiver), nice_repr(signal), nice_repr(sender)))
0311    return _real_dispatcher_connect(receiver, signal, sender, weak)
0312
0313
0314def _debug_disconnect(receiver, signal=dispatcher.Any, sender=dispatcher.Any,
0315                      weak=True):
0316    print("disconnecting %s from %s signal %s" % (
0317          nice_repr(receiver), nice_repr(signal), nice_repr(sender)))
0318    return _real_dispatcher_disconnect(receiver, signal, sender, weak)
0319
0320
0321def fmt_args(*arguments, **name):
0322    args = [repr(a) for a in arguments]
0323    args.extend([
0324        '%s=%r' % (n, v) for n, v in sorted(name.items())])
0325    return ', '.join(args)
0326
0327
0328def nice_repr(v):
0329    """
0330    Like repr(), but nicer for debugging here.
0331    """
0332    if isinstance(v, class_types):
0333        return v.__module__ + '.' + v.__name__
0334    elif isinstance(v, types.FunctionType):
0335        if '__name__' in v.__globals__:
0336            if getattr(sys.modules[v.__globals__['__name__']],
0337                       v.__name__, None) is v:
0338                return '%s.%s' % (v.__globals__['__name__'], v.__name__)
0339        return repr(v)
0340    elif isinstance(v, types.MethodType):
0341        return '%s.%s of %s' % (
0342            nice_repr(v.__self__.__class__), v.__func__.__name__,
0343            nice_repr(v.__self__))
0344    else:
0345        return repr(v)
0346
0347
0348__all__ = ['listen', 'send']
0349# Use copy() to avoid 'dictionary changed' issues on python 3
0350for name, value in globals().copy().items():
0351    if isinstance(value, type) and issubclass(value, Signal):
0352        __all__.append(name)