From c1a4befc68b27f0b0f0d06e66bea19929d35701e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 13 Aug 2007 20:14:04 +0000 Subject: [PATCH] - a rudimental SessionExtension class has been added, allowing user-defined functionality to take place at flush(), commit(), and rollback() boundaries. --- CHANGES | 2 + lib/sqlalchemy/orm/session.py | 72 ++++++++++++++++++++++++++++++-- lib/sqlalchemy/orm/unitofwork.py | 9 ++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 0fc09c6835..a31ed16377 100644 --- a/CHANGES +++ b/CHANGES @@ -92,6 +92,8 @@ old "objectstore" days. - added new "binds" argument to Session to support configuration of multiple binds with sessionmaker() function. + - a rudimental SessionExtension class has been added, allowing user-defined + functionality to take place at flush(), commit(), and rollback() boundaries. - query-based relation()s available with dynamic_loader(). This is a *writable* collection (supporting append() and remove()) which is also a live Query object diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index dda1357fff..514da6008a 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -11,7 +11,7 @@ from sqlalchemy.orm.mapper import object_mapper as _object_mapper from sqlalchemy.orm.mapper import class_mapper as _class_mapper -__all__ = ['Session', 'SessionTransaction'] +__all__ = ['Session', 'SessionTransaction', 'SessionExtension'] def sessionmaker(bind=None, class_=None, autoflush=True, transactional=True, **kwargs): """Generate a custom-configured [sqlalchemy.orm.session#Session] class. @@ -81,7 +81,50 @@ def sessionmaker(bind=None, class_=None, autoflush=True, transactional=True, **k configure = classmethod(configure) return Sess + +# TODO: add unit test coverage for SessionExtension in test/orm/session.py +class SessionExtension(object): + """an extension hook object for Sessions. Subclasses may be installed into a Session + (or sessionmaker) using the ``extension`` keyword argument. + """ + def before_commit(self, session): + """execute right before commit is called. + + Note that this may not be per-flush if a longer running transaction is ongoing.""" + + def after_commit(self, session): + """execute after a commit has occured. + + Note that this may not be per-flush if a longer running transaction is ongoing.""" + + def after_rollback(self, session): + """execute after a rollback has occured. + + Note that this may not be per-flush if a longer running transaction is ongoing.""" + + def before_flush(self, session, flush_context, objects): + """execute before flush process has started. + + 'objects' is an optional list of objects which were passed to the ``flush()`` + method. + """ + + def after_flush(self, session, flush_context): + """execute after flush has completed, but before commit has been called. + + Note that the session's state is still in pre-flush, i.e. 'new', 'dirty', + and 'deleted' lists still show pre-flush state as well as the history + settings on instance attributes.""" + + def after_flush_postexec(self, session, flush_context): + """execute after flush has completed, and after the post-exec state occurs. + + this will be when the 'new', 'dirty', and 'deleted' lists are in their final + state. An actual commit() may or may not have occured, depending on whether or not + the flush started its own transaction or participated in a larger transaction. + """ + class SessionTransaction(object): """Represents a Session-level Transaction. @@ -89,8 +132,9 @@ class SessionTransaction(object): instances behind the scenes, with one ``Transaction`` per ``Engine`` in use. - Typically, usage of ``SessionTransaction`` is not necessary; use - the ``begin()`` and ``commit()`` methods on ``Session`` itself. + Direct usage of ``SessionTransaction`` is not necessary as of + SQLAlchemy 0.4; use the ``begin()`` and ``commit()`` methods on + ``Session`` itself. The ``SessionTransaction`` object is **not** threadsafe. """ @@ -160,6 +204,10 @@ class SessionTransaction(object): def commit(self): if self.__parent is not None and not self.nested: return self.__parent + + if self.session.extension is not None: + self.session.before_commit(self.session) + if self.autoflush: self.session.flush() @@ -169,14 +217,23 @@ class SessionTransaction(object): for t in util.Set(self.__connections.values()): t[1].commit() + + if self.session.extension is not None: + self.session.after_commit(self.session) + self.close() return self.__parent def rollback(self): if self.__parent is not None and not self.nested: return self.__parent.rollback() + for t in util.Set(self.__connections.values()): t[1].rollback() + + if self.session.extension is not None: + self.session.extension.after_rollback(self.session) + self.close() return self.__parent @@ -261,7 +318,7 @@ class Session(object): a thread-managed Session adapter, provided by the [sqlalchemy.orm#scoped_session()] function. """ - def __init__(self, bind=None, autoflush=True, transactional=False, twophase=False, echo_uow=False, weak_identity_map=False, binds=None): + def __init__(self, bind=None, autoflush=True, transactional=False, twophase=False, echo_uow=False, weak_identity_map=False, binds=None, extension=None): """Construct a new Session. autoflush @@ -300,6 +357,12 @@ class Session(object): When ``True``, configure Python logging to dump all unit-of-work transactions. This is the equivalent of ``logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)``. + + extension + an optional [sqlalchemy.orm.session_SessionExtension] instance, which will receive + pre- and post- commit and flush events, as well as a post-rollback event. User- + defined code may be placed within these hooks using a user-defined subclass + of ``SessionExtension``. transactional Set up this ``Session`` to automatically begin transactions. Setting this @@ -338,6 +401,7 @@ class Session(object): self.autoflush = autoflush self.transactional = transactional self.twophase = twophase + self.extension = extension self._query_cls = query.Query self._mapper_flush_opts = {} diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index bf06896c1b..c0eebe3b0b 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -174,6 +174,9 @@ class UnitOfWork(object): flush_context = UOWTransaction(self, session) + if session.extension is not None: + session.extension.before_flush(session, flush_context, objects) + # create the set of all objects we want to operate upon if objects is not None: # specific list passed in @@ -204,6 +207,9 @@ class UnitOfWork(object): flush_context.transaction = session.transaction try: flush_context.execute() + + if session.extension is not None: + session.extension.after_flush(session, flush_context) except: session.rollback() raise @@ -211,6 +217,9 @@ class UnitOfWork(object): flush_context.post_exec() + if session.extension is not None: + session.extension.after_flush_postexec(session, flush_context) + class UOWTransaction(object): """Handles the details of organizing and executing transaction tasks during a UnitOfWork object's flush() operation. -- 2.47.3