Commits

Mike Bayer  committed d4bde26

- a rudimental SessionExtension class has been added, allowing user-defined
functionality to take place at flush(), commit(), and rollback() boundaries.

  • Participants
  • Parent commits 9094506

Comments (0)

Files changed (3)

           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

File lib/sqlalchemy/orm/session.py

 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.
         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.
 
     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.
     """
     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()
 
 
         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
         
     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
                 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
         self.autoflush = autoflush
         self.transactional = transactional
         self.twophase = twophase
+        self.extension = extension
         self._query_cls = query.Query
         self._mapper_flush_opts = {}
         

File lib/sqlalchemy/orm/unitofwork.py

 
         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
         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
 
         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.