[Checkins] SVN: zope.sqlalchemy/trunk/ Make life-time of sessions configurable. Specify `keep_session=True` when

Christian Theune cvs-admin at zope.org
Sun Jan 27 19:04:22 UTC 2013


Log message for revision 129103:
  Make life-time of sessions configurable. Specify `keep_session=True` when
  setting up the SA extension.
  
  

Changed:
  U   zope.sqlalchemy/trunk/CHANGES.txt
  U   zope.sqlalchemy/trunk/src/zope/sqlalchemy/README.txt
  U   zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py

-=-
Modified: zope.sqlalchemy/trunk/CHANGES.txt
===================================================================
--- zope.sqlalchemy/trunk/CHANGES.txt	2013-01-24 03:42:08 UTC (rev 129102)
+++ zope.sqlalchemy/trunk/CHANGES.txt	2013-01-27 19:04:21 UTC (rev 129103)
@@ -4,7 +4,8 @@
 0.7.2 (unreleased)
 ------------------
 
-* ...
+* Make life-time of sessions configurable. Specify `keep_session=True` when
+  setting up the SA extension.
 
 0.7.1 (2012-05-19)
 ------------------

Modified: zope.sqlalchemy/trunk/src/zope/sqlalchemy/README.txt
===================================================================
--- zope.sqlalchemy/trunk/src/zope/sqlalchemy/README.txt	2013-01-24 03:42:08 UTC (rev 129102)
+++ zope.sqlalchemy/trunk/src/zope/sqlalchemy/README.txt	2013-01-27 19:04:21 UTC (rev 129103)
@@ -104,7 +104,7 @@
     []
 
 We can now create a new user and commit the changes using Zope's transaction
-machinary, just as Zope's publisher would.
+machinery, just as Zope's publisher would.
 
     >>> session.add(User(id=1, name='bob'))
     >>> transaction.commit()
@@ -130,7 +130,7 @@
     [<Address object at ...>]
     >>> str(bob.addresses[0].email)
     'bob at bob.bob'
-    >>> bob.addresses[0].email = 'wrong at wrong'    
+    >>> bob.addresses[0].email = 'wrong at wrong'
 
 To rollback a transaction, use transaction.abort().
 
@@ -153,7 +153,7 @@
     >>> conn = session.connection()
     >>> users = Base.metadata.tables['test_users']
     >>> conn.execute(users.update(users.c.name=='bob'), name='ben')
-    <sqlalchemy.engine.base.ResultProxy object at ...>
+    <sqlalchemy.engine.result.ResultProxy object at ...>
     >>> from zope.sqlalchemy import mark_changed
     >>> mark_changed(session)
     >>> transaction.commit()
@@ -170,13 +170,39 @@
     >>> session = Session()
     >>> conn = session.connection()
     >>> conn.execute(users.update(users.c.name=='ben'), name='bob')
-    <sqlalchemy.engine.base.ResultProxy object at ...>
+    <sqlalchemy.engine.result.ResultProxy object at ...>
     >>> transaction.commit()
     >>> session = Session()
     >>> str(session.query(User).all()[0].name)
     'bob'
     >>> transaction.abort()
 
+Long-lasting session scopes
+---------------------------
+
+The default behaviour of the transaction integration is to close the session
+after a commit. You can tell by trying to access an object after committing:
+
+    >>> bob = session.query(User).all()[0]
+    >>> transaction.commit()
+    >>> bob.name
+    Traceback (most recent call last):
+    DetachedInstanceError: Instance <User at ...> is not bound to a Session; attribute refresh operation cannot proceed
+
+To support cases where a session needs to last longer than a transaction
+(useful in test suites) you can specify to keep a session when creating the
+transaction extension:
+
+    >>> Session = scoped_session(sessionmaker(bind=engine,
+    ... twophase=TEST_TWOPHASE, extension=ZopeTransactionExtension(keep_session=True)))
+
+    >>> session = Session()
+    >>> bob = session.query(User).all()[0]
+    >>> bob.name = 'bobby'
+    >>> transaction.commit()
+    >>> bob.name
+    u'bobby'
+
 Development version
 ===================
 

Modified: zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py
===================================================================
--- zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py	2013-01-24 03:42:08 UTC (rev 129102)
+++ zope.sqlalchemy/trunk/src/zope/sqlalchemy/datamanager.py	2013-01-27 19:04:21 UTC (rev 129103)
@@ -61,12 +61,13 @@
     One phase variant.
     """
     
-    def __init__(self, session, status, transaction_manager):
+    def __init__(self, session, status, transaction_manager, keep_session=False):
         self.transaction_manager = transaction_manager
         self.tx = session.transaction._iterate_parents()[-1]
         self.session = session
         _SESSION_STATE[id(session)] = status
         self.state = 'init'
+        self.keep_session = keep_session
 
     def _finish(self, final_state):
         assert self.tx is not None
@@ -76,7 +77,10 @@
         self.state = final_state
         # closing the session is the last thing we do. If it fails the
         # transactions don't get wedged and the error propagates
-        session.close()
+        if not self.keep_session:
+            session.close()
+        else:
+            session.expire_all()
 
     def abort(self, trans):
         if self.tx is not None: # there may have been no work to do
@@ -176,7 +180,7 @@
         self.transaction.rollback()
 
 
-def join_transaction(session, initial_state=STATUS_ACTIVE, transaction_manager=zope_transaction.manager):
+def join_transaction(session, initial_state=STATUS_ACTIVE, transaction_manager=zope_transaction.manager, keep_session=False):
     """Join a session to a transaction using the appropriate datamanager.
        
     It is safe to call this multiple times, if the session is already joined
@@ -195,7 +199,7 @@
             DataManager = TwoPhaseSessionDataManager
         else:
             DataManager = SessionDataManager
-        transaction_manager.get().join(DataManager(session, initial_state, transaction_manager))
+        transaction_manager.get().join(DataManager(session, initial_state, transaction_manager, keep_session=keep_session))
 
 def mark_changed(session, transaction_manager=zope_transaction.manager):
     """Mark a session as needing to be committed.
@@ -211,17 +215,18 @@
     the DataManager to rollback rather than commit on read only transactions.
     """
     
-    def __init__(self, initial_state=STATUS_ACTIVE, transaction_manager=zope_transaction.manager):
+    def __init__(self, initial_state=STATUS_ACTIVE, transaction_manager=zope_transaction.manager, keep_session=False):
         if initial_state=='invalidated': initial_state = STATUS_CHANGED #BBB
         SessionExtension.__init__(self)
         self.initial_state = initial_state
         self.transaction_manager = transaction_manager
+        self.keep_session = keep_session
     
     def after_begin(self, session, transaction, connection):
-        join_transaction(session, self.initial_state, self.transaction_manager)
+        join_transaction(session, self.initial_state, self.transaction_manager, self.keep_session)
     
     def after_attach(self, session, instance):
-        join_transaction(session, self.initial_state, self.transaction_manager)
+        join_transaction(session, self.initial_state, self.transaction_manager, self.keep_session)
     
     def after_flush(self, session, flush_context):
         mark_changed(session, self.transaction_manager)



More information about the checkins mailing list