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
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
0074
0075
0076
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
0106
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
0187
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
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
0350for name, value in globals().copy().items():
0351 if isinstance(value, type) and issubclass(value, Signal):
0352 __all__.append(name)