From 80d1aaa66113ba3770cb9b2ec2c97fed28fa465c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 14 Nov 2010 15:54:37 -0500 Subject: [PATCH] - event documentation bonanza --- doc/build/core/event.rst | 25 +- doc/build/core/events.rst | 8 +- doc/build/orm/events.rst | 24 +- doc/build/orm/interfaces.rst | 73 ---- lib/sqlalchemy/events.py | 56 ++- lib/sqlalchemy/orm/deprecated_interfaces.py | 95 ++++- lib/sqlalchemy/orm/events.py | 449 +++++++++++++++----- lib/sqlalchemy/orm/interfaces.py | 12 + 8 files changed, 528 insertions(+), 214 deletions(-) diff --git a/doc/build/core/event.rst b/doc/build/core/event.rst index 946aa7901f..aae7a727e2 100644 --- a/doc/build/core/event.rst +++ b/doc/build/core/event.rst @@ -8,8 +8,6 @@ the internals of both SQLAlchemy Core and ORM. The system is all new as of version 0.7 and supercedes the previous system of "extension", "proxy", and "listener" classes. -Core events are described in :ref:`core_event_toplevel` and ORM events in :ref:`orm_event_toplevel`. - Event Registration ------------------ @@ -21,7 +19,7 @@ instructions regarding secondary event targets based on the given target. The name of an event and the argument signature of a corresponding listener function is derived from a class bound specification method, which exists bound to a marker class that's described in the documentation. -For example, the documentation for :ref:`.PoolEvents.on_connect` indicates that the event name is ``"on_connect"`` +For example, the documentation for :meth:`.PoolEvents.on_connect` indicates that the event name is ``"on_connect"`` and that a user-defined listener function should receive two positional arguments:: from sqlalchemy.event import listen @@ -82,6 +80,25 @@ which modifies the subsequent handling. By default, no listener ever requires # it to use the return value listen(validate_phone, 'on_set', UserContact.phone, retval=True) - +Event Reference +---------------- + +Both SQLAlchemy Core and SQLAlchemy ORM feature a wide variety of event hooks: + +* **Core Events** - these are described in + :ref:`core_event_toplevel` and include event hooks specific to + connection pool lifecycle, SQL statement execution, + transaction lifecycle, and schema creation and teardown. + +* **ORM Events** - these are described in + :ref:`orm_event_toplevel`, and include event hooks specific to + class and attribute instrumentation, object initialization + hooks, attribute on-change hooks, session state, flush, and + commit hooks, mapper initialization, object/result population, + and per-instance persistence hooks. + +API Reference +------------- + .. autofunction:: sqlalchemy.event.listen diff --git a/doc/build/core/events.rst b/doc/build/core/events.rst index 7f0d2d530f..6c8b4d064a 100644 --- a/doc/build/core/events.rst +++ b/doc/build/core/events.rst @@ -3,8 +3,12 @@ Core Events ============ -This section describes the event interfaces provided in SQLAlchemy Core. For an introduction -to the event listening API, see :ref:`event_toplevel`. ORM events are described in :ref:`orm_event_toplevel`. +This section describes the event interfaces provided in +SQLAlchemy Core. The event system in 0.7 is all new and +supercedes the previous system of "extension", "listener", and +"proxy" classes. For an introduction to the event listening API, +see :ref:`event_toplevel`. ORM events are described in +:ref:`orm_event_toplevel`. Connection Pool Events ----------------------- diff --git a/doc/build/orm/events.rst b/doc/build/orm/events.rst index 87dbe5d7d6..fdbe3d3393 100644 --- a/doc/build/orm/events.rst +++ b/doc/build/orm/events.rst @@ -5,7 +5,9 @@ ORM Events The ORM includes a wide variety of hooks available for subscription. The event system in 0.7 is all new and supercedes the previous system of "extension" classes. -For an introduction to the event API, see :ref:`core_event_toplevel`. +For an introduction to the event API, see :ref:`event_toplevel`. Non-ORM events +such as those regarding connections and low-level statement execution are described in +:ref:`core_event_toplevel`. Attribute Events ---------------- @@ -13,12 +15,6 @@ Attribute Events .. autoclass:: sqlalchemy.orm.events.AttributeEvents :members: -Instrumentation Events ------------------------ - -.. autoclass:: sqlalchemy.orm.events.InstrumentationEvents - :members: - Mapper Events --------------- @@ -36,3 +32,17 @@ Session Events TODO +Instrumentation Events +----------------------- + +.. autoclass:: sqlalchemy.orm.events.InstrumentationEvents + :members: + +Alternate Class Instrumentation +------------------------------- + +.. autoclass:: sqlalchemy.orm.interfaces.InstrumentationManager + :members: + :undoc-members: + + diff --git a/doc/build/orm/interfaces.rst b/doc/build/orm/interfaces.rst index 3d19f0f40a..0df54cfdac 100644 --- a/doc/build/orm/interfaces.rst +++ b/doc/build/orm/interfaces.rst @@ -16,91 +16,18 @@ a consistent interface to all events without the need for subclassing. Mapper Events ----------------- -To use :class:`.MapperExtension`, make your own subclass of it and just send it off to a mapper:: - - from sqlalchemy.orm.interfaces import MapperExtension - - class MyExtension(MapperExtension): - def before_insert(self, mapper, connection, instance): - print "instance %s before insert !" % instance - - m = mapper(User, users_table, extension=MyExtension()) - -Multiple extensions will be chained together and processed in order; they are specified as a list:: - - m = mapper(User, users_table, extension=[ext1, ext2, ext3]) - .. autoclass:: MapperExtension :members: Session Events ----------------- -The :class:`.SessionExtension` applies plugin points for :class:`.Session` objects:: - - from sqlalchemy.orm.interfaces import SessionExtension - - class MySessionExtension(SessionExtension): - def before_commit(self, session): - print "before commit!" - - Session = sessionmaker(extension=MySessionExtension()) - -The same :class:`~sqlalchemy.orm.interfaces.SessionExtension` instance can be -used with any number of sessions. - .. autoclass:: SessionExtension :members: Attribute Events -------------------- -:class:`.AttributeExtension` is used to listen for set, remove, and append -events on individual mapped attributes. It is established on an individual -mapped attribute using the `extension` argument, available on -:func:`.column_property`, :func:`.relationship`, and others:: - - from sqlalchemy.orm.interfaces import AttributeExtension - from sqlalchemy.orm import mapper, relationship, column_property - - class MyAttrExt(AttributeExtension): - def append(self, state, value, initiator): - print "append event !" - return value - - def set(self, state, value, oldvalue, initiator): - print "set event !" - return value - - mapper(SomeClass, sometable, properties={ - 'foo':column_property(sometable.c.foo, extension=MyAttrExt()), - 'bar':relationship(Bar, extension=MyAttrExt()) - }) - -Note that the :class:`AttributeExtension` methods -:meth:`~.AttributeExtension.append` and :meth:`~.AttributeExtension.set` need -to return the ``value`` parameter. The returned value is used as the effective -value, and allows the extension to change what is ultimately persisted. - .. autoclass:: AttributeExtension :members: -Instrumentation Events and Re-implementation ---------------------------------------------- - -:class:`.InstrumentationManager` can be subclassed in order to receive class -instrumentation events as well as to change how class instrumentation -proceeds. This class exists for the purposes of integration with other object -management frameworks which would like to entirely modify the instrumentation -methodology of the ORM, and is not intended for regular usage. One possible -exception is the :meth:`.InstrumentationManager.post_configure_attribute` -method, which can be useful for adding extensions to all mapped attributes, -though a much better way to do this will be available in a future release of -SQLAlchemy. - -For an example of :class:`.InstrumentationManager`, see the example -:ref:`examples_instrumentation`. - -.. autoclass:: InstrumentationManager - :members: - :undoc-members: diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index ca4959e61e..a1313de632 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -52,16 +52,64 @@ class DDLEvents(event.Events): """ def on_before_create(self, target, connection, **kw): - """ """ + """Called before CREATE statments are emitted. + + :param target: the :class:`.MetaData` or :class:`.Table` + object which is the target of the event. + :param connection: the :class:`.Connection` where the + CREATE statement or statements will be emitted. + :param \**kw: additional keyword arguments relevant + to the event. Currently this includes the ``tables`` + argument in the case of a :class:`.MetaData` object, + which is the list of :class:`.Table` objects for which + CREATE will be emitted. + + """ def on_after_create(self, target, connection, **kw): - """ """ + """Called after CREATE statments are emitted. + + :param target: the :class:`.MetaData` or :class:`.Table` + object which is the target of the event. + :param connection: the :class:`.Connection` where the + CREATE statement or statements have been emitted. + :param \**kw: additional keyword arguments relevant + to the event. Currently this includes the ``tables`` + argument in the case of a :class:`.MetaData` object, + which is the list of :class:`.Table` objects for which + CREATE has been emitted. + + """ def on_before_drop(self, target, connection, **kw): - """ """ + """Called before DROP statments are emitted. + + :param target: the :class:`.MetaData` or :class:`.Table` + object which is the target of the event. + :param connection: the :class:`.Connection` where the + DROP statement or statements will be emitted. + :param \**kw: additional keyword arguments relevant + to the event. Currently this includes the ``tables`` + argument in the case of a :class:`.MetaData` object, + which is the list of :class:`.Table` objects for which + DROP will be emitted. + + """ def on_after_drop(self, target, connection, **kw): - """ """ + """Called after DROP statments are emitted. + + :param target: the :class:`.MetaData` or :class:`.Table` + object which is the target of the event. + :param connection: the :class:`.Connection` where the + DROP statement or statements have been emitted. + :param \**kw: additional keyword arguments relevant + to the event. Currently this includes the ``tables`` + argument in the case of a :class:`.MetaData` object, + which is the list of :class:`.Table` objects for which + DROP has been emitted. + + """ class PoolEvents(event.Events): diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py index 76e023701e..8aa57c6875 100644 --- a/lib/sqlalchemy/orm/deprecated_interfaces.py +++ b/lib/sqlalchemy/orm/deprecated_interfaces.py @@ -3,15 +3,31 @@ from interfaces import EXT_CONTINUE class MapperExtension(object): - """Base implementation for customizing ``Mapper`` behavior. + """Base implementation for :class:`.Mapper` event hooks. + + .. note:: :class:`.MapperExtension` is deprecated. Please + refer to :func:`.event.listen` as well as + :class:`.MapperEvents`. - New extension classes subclass ``MapperExtension`` and are specified + New extension classes subclass :class:`.MapperExtension` and are specified using the ``extension`` mapper() argument, which is a single - ``MapperExtension`` or a list of such. A single mapper - can maintain a chain of ``MapperExtension`` objects. When a - particular mapping event occurs, the corresponding method - on each ``MapperExtension`` is invoked serially, and each method - has the ability to halt the chain from proceeding further. + :class:`.MapperExtension` or a list of such:: + + from sqlalchemy.orm.interfaces import MapperExtension + + class MyExtension(MapperExtension): + def before_insert(self, mapper, connection, instance): + print "instance %s before insert !" % instance + + m = mapper(User, users_table, extension=MyExtension()) + + A single mapper can maintain a chain of ``MapperExtension`` + objects. When a particular mapping event occurs, the + corresponding method on each ``MapperExtension`` is invoked + serially, and each method has the ability to halt the chain + from proceeding further:: + + m = mapper(User, users_table, extension=[ext1, ext2, ext3]) Each ``MapperExtension`` method returns the symbol EXT_CONTINUE by default. This symbol generally means "move @@ -354,9 +370,28 @@ class MapperExtension(object): class SessionExtension(object): - """An extension hook object for Sessions. Subclasses may be - installed into a Session (or sessionmaker) using the ``extension`` - keyword argument. """ + """Base implementation for :class:`.Session` event hooks. + + .. note:: :class:`.SessionExtension` is deprecated. Please + refer to :func:`.event.listen` as well as + :class:`.SessionEvents`. + + Subclasses may be installed into a :class:`.Session` (or + :func:`.sessionmaker`) using the ``extension`` keyword + argument:: + + from sqlalchemy.orm.interfaces import SessionExtension + + class MySessionExtension(SessionExtension): + def before_commit(self, session): + print "before commit!" + + Session = sessionmaker(extension=MySessionExtension()) + + The same :class:`.SessionExtension` instance can be used + with any number of sessions. + + """ def before_commit(self, session): """Execute right before commit is called. @@ -432,11 +467,43 @@ class SessionExtension(object): class AttributeExtension(object): - """An event handler for individual attribute change events. + """Base implementation for :class:`.AttributeImpl` event hooks, events + that fire upon attribute mutations in user code. + + .. note:: :class:`.AttributeExtension` is deprecated. Please + refer to :func:`.event.listen` as well as + :class:`.AttributeEvents`. + + :class:`.AttributeExtension` is used to listen for set, + remove, and append events on individual mapped attributes. + It is established on an individual mapped attribute using + the `extension` argument, available on + :func:`.column_property`, :func:`.relationship`, and + others:: + + from sqlalchemy.orm.interfaces import AttributeExtension + from sqlalchemy.orm import mapper, relationship, column_property - .. note:: :class:`AttributeExtension` is deprecated. Please - refer to :func:`event.listen` as well as - :attr:`AttributeImpl.events`. + class MyAttrExt(AttributeExtension): + def append(self, state, value, initiator): + print "append event !" + return value + + def set(self, state, value, oldvalue, initiator): + print "set event !" + return value + + mapper(SomeClass, sometable, properties={ + 'foo':column_property(sometable.c.foo, extension=MyAttrExt()), + 'bar':relationship(Bar, extension=MyAttrExt()) + }) + + Note that the :class:`AttributeExtension` methods + :meth:`~.AttributeExtension.append` and + :meth:`~.AttributeExtension.set` need to return the + ``value`` parameter. The returned value is used as the + effective value, and allows the extension to change what is + ultimately persisted. AttributeExtension is assembled within the descriptors associated with a mapped class. diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 2ad7b806c8..66d3834521 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -133,45 +133,79 @@ class InstanceEvents(event.Events): """ def on_resurrect(self, target): - """""" + """Receive an object instance as it is 'resurrected' from + garbage collection, which occurs when a "dirty" state falls + out of scope.""" class MapperEvents(event.Events): """Define events specific to mappings. - + e.g.:: from sqlalchemy import event - from sqlalchemy.orm import mapper - # attach to a class + def my_before_insert_listener(mapper, connection, target): + # execute a stored procedure upon INSERT, + # apply the value to the row to be inserted + target.calculated_value = connection.scalar( + "select my_special_function(%d)" + % target.special_number) + + # associate the listener function with SomeMappedClass, + # to execute during the "on_before_insert" hook event.listen(my_before_insert_listener, 'on_before_insert', SomeMappedClass) + Available targets include mapped classes, instances of + :class:`.Mapper` (i.e. returned by :func:`.mapper`, + :func:`.class_mapper` and similar), as well as the + :class:`.Mapper` class and :func:`.mapper` function itself + for global event reception:: + + from sqlalchemy.orm import mapper + + def some_listener(mapper, connection, target): + log.debug("Instance %s being inserted" % target) + # attach to all mappers event.listen(some_listener, 'on_before_insert', mapper) - Mapper event listeners are propagated to subclass (inheriting) - mappers unconditionally. + Mapper events provide hooks into critical sections of the + mapper, including those related to object instrumentation, + object loading, and object persistence. In particular, the + persistence methods :meth:`~.MapperEvents.on_before_insert`, + and :meth:`~.MapperEvents.on_before_update` are popular + places to augment the state being persisted - however, these + methods operate with several significant restrictions. The + user is encouraged to evaluate the + :meth:`.SessionEvents.on_before_flush` and + :meth:`.SessionEvents.on_after_flush` methods as more + flexible and user-friendly hooks in which to apply + additional database state during a flush. - Several modifiers are available to the listen() function. + When using :class:`.MapperEvents`, several modifiers are + available to the :func:`.event.listen` function. :param propagate=False: When True, the event listener should - be applied to all inheriting mappers as well. - :param raw=False: When True, the "target" argument to the - event, if applicable will be the :class:`.InstanceState` management + be applied to all inheriting mappers as well as the + mapper which is the target of this listener. + :param raw=False: When True, the "target" argument passed + to applicable event listener functions will be the + instance's :class:`.InstanceState` management object, rather than the mapped instance itself. :param retval=False: when True, the user-defined event function must have a return value, the purpose of which is either to control subsequent event propagation, or to otherwise alter - the operation in progress by the mapper. Possible values - here are: + the operation in progress by the mapper. Possible return + values are: * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event processing normally. * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent event handlers in the chain. * other values - the return value specified by specific listeners, - such as "translate_row" or "create_instance". + such as :meth:`~.MapperEvents.on_translate_row` or + :meth:`~.MapperEvents.on_create_instance`. """ @@ -226,6 +260,10 @@ class MapperEvents(event.Events): This listener can generally only be applied to the :class:`.Mapper` class overall. + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param class\_: the mapped class. + """ def on_translate_row(self, mapper, context, row): @@ -236,13 +274,23 @@ class MapperEvents(event.Events): It is called when the mapper first receives a row, before the object identity or the instance itself has been derived from that row. The given row may or may not be a - ``RowProxy`` object - it will always be a dictionary-like + :class:`.RowProxy` object - it will always be a dictionary-like object which contains mapped columns as keys. The returned object should also be a dictionary-like object which recognizes mapped columns as keys. - If the ultimate return value is EXT_CONTINUE, the row - is not translated. + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param context: the :class:`.QueryContext`, which includes + a handle to the current :class:`.Query` in progress as well + as additional state information. + :param row: the result row being handled. This may be + an actual :class:`.RowProxy` or may be a dictionary containing + :class:`.Column` objects as keys. + :return: When configured with ``retval=True``, the function + should return a dictionary-like row object, or ``EXT_CONTINUE``, + indicating the original row should be used. + """ @@ -254,20 +302,19 @@ class MapperEvents(event.Events): EXT_CONTINUE to indicate normal object creation should take place. This listener is typically registered with ``retval=True``. - mapper - The mapper doing the operation - - context - The QueryContext generated from the Query. - - row - The result row from the database - - class\_ - The class we are mapping. - - return value - A new object instance, or EXT_CONTINUE + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param context: the :class:`.QueryContext`, which includes + a handle to the current :class:`.Query` in progress as well + as additional state information. + :param row: the result row being handled. This may be + an actual :class:`.RowProxy` or may be a dictionary containing + :class:`.Column` objects as keys. + :param class\_: the mapped class. + :return: When configured with ``retval=True``, the return value + should be a newly created instance of the mapped class, + or ``EXT_CONTINUE`` indicating that default object construction + should take place. """ @@ -275,33 +322,32 @@ class MapperEvents(event.Events): result, **flags): """Receive an object instance before that instance is appended to a result list. + + This is a rarely used hook which can be used to alter + the construction of a result list returned by :class:`.Query`. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param context: the :class:`.QueryContext`, which includes + a handle to the current :class:`.Query` in progress as well + as additional state information. + :param row: the result row being handled. This may be + an actual :class:`.RowProxy` or may be a dictionary containing + :class:`.Column` objects as keys. + :param target: the mapped instance being populated. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :param result: a list-like object where results are being + appended. + :param \**flags: Additional state information about the + current handling of the row. + :return: If this method is registered with ``retval=True``, + a return value of ``EXT_STOP`` will prevent the instance + from being appended to the given result list, whereas a + return value of ``EXT_CONTINUE`` will result in the default + behavior of appending the value to the result list. - If this method is registered with ``retval=True``, - the append operation can be replaced. If any value other than - EXT_CONTINUE is returned, result appending will not proceed for - this instance, giving this extension an opportunity to do the - appending itself, if desired. - - mapper - The mapper doing the operation. - - selectcontext - The QueryContext generated from the Query. - - row - The result row from the database. - - target - The object instance to be appended to the result, or - the InstanceState if registered with ``raw=True``. - - result - List to which results are being appended. - - \**flags - extra information about the row, same as criterion in - ``create_row_processor()`` method of - :class:`~sqlalchemy.orm.interfaces.MapperProperty` """ @@ -315,86 +361,265 @@ class MapperEvents(event.Events): unloaded attributes to be populated. The method may be called many times for a single instance, as multiple result rows are used to populate eagerly loaded collections. - - If this listener is registered with ``retval=True`` and - returns EXT_CONTINUE, instance population will - proceed normally. If any other value or None is returned, - instance population will not proceed, giving this extension an - opportunity to populate the instance itself, if desired. - - As of 0.5, most usages of this hook are obsolete. For a + + Most usages of this hook are obsolete. For a generic "object has been newly created from a row" hook, use - ``reconstruct_instance()``, or the ``@orm.reconstructor`` - decorator. + :meth:`.InstanceEvents.on_load`. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param context: the :class:`.QueryContext`, which includes + a handle to the current :class:`.Query` in progress as well + as additional state information. + :param row: the result row being handled. This may be + an actual :class:`.RowProxy` or may be a dictionary containing + :class:`.Column` objects as keys. + :param class\_: the mapped class. + :return: When configured with ``retval=True``, a return + value of ``EXT_STOP`` will bypass instance population by + the mapper. A value of ``EXT_CONTINUE`` indicates that + default instance population should take place. """ def on_before_insert(self, mapper, connection, target): - """Receive an object instance before that instance is inserted - into its table. - - This is a good place to set up primary key values and such - that aren't handled otherwise. - - Column-based attributes can be modified within this method - which will result in the new value being inserted. However - *no* changes to the overall flush plan can be made, and - manipulation of the ``Session`` will not have the desired effect. - To manipulate the ``Session`` within an extension, use - ``SessionExtension``. + """Receive an object instance before an INSERT statement + is emitted corresponding to that instance. + + This event is used to modify local, non-object related + attributes on the instance before an INSERT occurs, as well + as to emit additional SQL statements on the given + connection. + + The event is often called for a batch of objects of the + same class before their INSERT statements are emitted at + once in a later step. In the extremely rare case that + this is not desirable, the :func:`.mapper` can be + configured with ``batch=False``, which will cause + batches of instances to be broken up into individual + (and more poorly performing) event->persist->event + steps. + + Handlers should **not** modify any attributes which are + mapped by :func:`.relationship`, nor should they attempt + to make any modifications to the :class:`.Session` in + this hook (including :meth:`.Session.add`, + :meth:`.Session.delete`, etc.) - such changes will not + take effect. For overall changes to the "flush plan", + use :meth:`.SessionEvents.before_flush`. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit INSERT statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being persisted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. """ def on_after_insert(self, mapper, connection, target): - """Receive an object instance after that instance is inserted. + """Receive an object instance after an INSERT statement + is emitted corresponding to that instance. + + This event is used to modify in-Python-only + state on the instance after an INSERT occurs, as well + as to emit additional SQL statements on the given + connection. + + The event is often called for a batch of objects of the + same class after their INSERT statements have been + emitted at once in a previous step. In the extremely + rare case that this is not desirable, the + :func:`.mapper` can be configured with ``batch=False``, + which will cause batches of instances to be broken up + into individual (and more poorly performing) + event->persist->event steps. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit INSERT statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being persisted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. """ def on_before_update(self, mapper, connection, target): - """Receive an object instance before that instance is updated. - - Note that this method is called for all instances that are marked as - "dirty", even those which have no net changes to their column-based - attributes. An object is marked as dirty when any of its column-based - attributes have a "set attribute" operation called or when any of its - collections are modified. If, at update time, no column-based - attributes have any net changes, no UPDATE statement will be issued. - This means that an instance being sent to before_update is *not* a - guarantee that an UPDATE statement will be issued (although you can - affect the outcome here). + """Receive an object instance before an UPDATE statement + is emitted corresponding to that instance. + + This event is used to modify local, non-object related + attributes on the instance before an UPDATE occurs, as well + as to emit additional SQL statements on the given + connection. + + This method is called for all instances that are + marked as "dirty", *even those which have no net changes + to their column-based attributes*. An object is marked + as dirty when any of its column-based attributes have a + "set attribute" operation called or when any of its + collections are modified. If, at update time, no + column-based attributes have any net changes, no UPDATE + statement will be issued. This means that an instance + being sent to :meth:`~.MapperEvents.on_before_update` is + *not* a guarantee that an UPDATE statement will be + issued, although you can affect the outcome here by + modifying attributes so that a net change in value does + exist. To detect if the column-based attributes on the object have net changes, and will therefore generate an UPDATE statement, use ``object_session(instance).is_modified(instance, include_collections=False)``. - Column-based attributes can be modified within this method - which will result in the new value being updated. However - *no* changes to the overall flush plan can be made, and - manipulation of the ``Session`` will not have the desired effect. - To manipulate the ``Session`` within an extension, use - ``SessionExtension``. - + The event is often called for a batch of objects of the + same class before their UPDATE statements are emitted at + once in a later step. In the extremely rare case that + this is not desirable, the :func:`.mapper` can be + configured with ``batch=False``, which will cause + batches of instances to be broken up into individual + (and more poorly performing) event->persist->event + steps. + + Handlers should **not** modify any attributes which are + mapped by :func:`.relationship`, nor should they attempt + to make any modifications to the :class:`.Session` in + this hook (including :meth:`.Session.add`, + :meth:`.Session.delete`, etc.) - such changes will not + take effect. For overall changes to the "flush plan", + use :meth:`.SessionEvents.before_flush`. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit UPDATE statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being persisted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. """ def on_after_update(self, mapper, connection, target): - """Receive an object instance after that instance is updated. + """Receive an object instance after an UPDATE statement + is emitted corresponding to that instance. + + This event is used to modify in-Python-only + state on the instance after an UPDATE occurs, as well + as to emit additional SQL statements on the given + connection. + + This method is called for all instances that are + marked as "dirty", *even those which have no net changes + to their column-based attributes*, and for which + no UPDATE statement has proceeded. An object is marked + as dirty when any of its column-based attributes have a + "set attribute" operation called or when any of its + collections are modified. If, at update time, no + column-based attributes have any net changes, no UPDATE + statement will be issued. This means that an instance + being sent to :meth:`~.MapperEvents.on_after_update` is + *not* a guarantee that an UPDATE statement has been + issued. + + To detect if the column-based attributes on the object have net + changes, and therefore resulted in an UPDATE statement, use + ``object_session(instance).is_modified(instance, + include_collections=False)``. + + The event is often called for a batch of objects of the + same class after their UPDATE statements have been emitted at + once in a previous step. In the extremely rare case that + this is not desirable, the :func:`.mapper` can be + configured with ``batch=False``, which will cause + batches of instances to be broken up into individual + (and more poorly performing) event->persist->event + steps. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit UPDATE statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being persisted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. """ def on_before_delete(self, mapper, connection, target): - """Receive an object instance before that instance is deleted. - - Note that *no* changes to the overall flush plan can be made - here; and manipulation of the ``Session`` will not have the - desired effect. To manipulate the ``Session`` within an - extension, use ``SessionExtension``. - + """Receive an object instance before a DELETE statement + is emitted corresponding to that instance. + + This event is used to emit additional SQL statements on + the given connection as well as to perform application + specific bookkeeping related to a deletion event. + + The event is often called for a batch of objects of the + same class before their DELETE statements are emitted at + once in a later step. + + Handlers should **not** modify any attributes which are + mapped by :func:`.relationship`, nor should they attempt + to make any modifications to the :class:`.Session` in + this hook (including :meth:`.Session.add`, + :meth:`.Session.delete`, etc.) - such changes will not + take effect. For overall changes to the "flush plan", + use :meth:`.SessionEvents.before_flush`. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit DELETE statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being deleted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. + """ def on_after_delete(self, mapper, connection, target): - """Receive an object instance after that instance is deleted. - + """Receive an object instance after a DELETE statement + has been emitted corresponding to that instance. + + This event is used to emit additional SQL statements on + the given connection as well as to perform application + specific bookkeeping related to a deletion event. + + The event is often called for a batch of objects of the + same class after their DELETE statements have been emitted at + once in a previous step. + + :param mapper: the :class:`.Mapper` which is the target + of this event. + :param connection: the :class:`.Connection` being used to + emit DELETE statements for this instance. This + provides a handle into the current transaction on the + target database specific to this instance. + :param target: the mapped instance being deleted. If + the event is configured with ``raw=True``, this will + instead be the :class:`.InstanceState` state-management + object associated with the instance. + :return: No return value is supported by this event. + """ @classmethod @@ -417,7 +642,7 @@ class AttributeEvents(event.Events): event.listen(my_set_listener, 'on_set', MyClass.somescalar, retval=True) - Several modifiers are available to the listen() function. + Several modifiers are available to the :func:`~event.listen` function. :param active_history=False: When True, indicates that the "on_set" event would like to receive the "old" value @@ -488,7 +713,9 @@ class AttributeEvents(event.Events): replaces it. :param initiator: the attribute implementation object which initiated this event. - + :return: if the event was registered with ``retval=True``, + the given value, or a new effective value, should be returned. + """ def on_remove(self, target, value, initiator): @@ -500,7 +727,7 @@ class AttributeEvents(event.Events): :param value: the value being removed. :param initiator: the attribute implementation object which initiated this event. - + :return: No return value is defined for this event. """ def on_set(self, target, value, oldvalue, initiator): @@ -521,6 +748,8 @@ class AttributeEvents(event.Events): or expired. :param initiator: the attribute implementation object which initiated this event. + :return: if the event was registered with ``retval=True``, + the given value, or a new effective value, should be returned. """ diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 3c49b15431..6bcdc6f0f9 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -616,6 +616,18 @@ class LoaderStrategy(object): class InstrumentationManager(object): """User-defined class instrumentation extension. + :class:`.InstrumentationManager` can be subclassed in order + to change + how class instrumentation proceeds. This class exists for + the purposes of integration with other object management + frameworks which would like to entirely modify the + instrumentation methodology of the ORM, and is not intended + for regular usage. For interception of class instrumentation + events, see :class:`.InstrumentationEvents`. + + For an example of :class:`.InstrumentationManager`, see the + example :ref:`examples_instrumentation`. + The API for this class should be considered as semi-stable, and may change slightly with new releases. -- 2.47.2