[Sqlalchemy-commits] [1535] sqlalchemy/trunk/test: TLEngine needed a partial rewrite....
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-28 17:47:01
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1535] sqlalchemy/trunk/test: TLEngine needed a partial rewrite....</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1535</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-28 12:46:45 -0500 (Sun, 28 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>TLEngine needed a partial rewrite....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyenginebasepy">sqlalchemy/trunk/lib/sqlalchemy/engine/base.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyenginestrategiespy">sqlalchemy/trunk/lib/sqlalchemy/engine/strategies.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyenginethreadlocalpy">sqlalchemy/trunk/lib/sqlalchemy/engine/threadlocal.py</a></li> <li><a href="#sqlalchemytrunktesttransactionpy">sqlalchemy/trunk/test/transaction.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyenginebasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/engine/base.py (1534 => 1535)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/engine/base.py 2006-05-28 16:37:22 UTC (rev 1534) +++ sqlalchemy/trunk/lib/sqlalchemy/engine/base.py 2006-05-28 17:46:45 UTC (rev 1535) </span><span class="lines">@@ -194,6 +194,8 @@ </span><span class="cx"> return self.__transaction </span><span class="cx"> else: </span><span class="cx"> return self._create_transaction(self.__transaction) </span><ins>+ def in_transaction(self): + return self.__transaction is not None </ins><span class="cx"> def _begin_impl(self): </span><span class="cx"> if self.__engine.echo: </span><span class="cx"> self.__engine.log("BEGIN") </span><span class="lines">@@ -210,13 +212,13 @@ </span><span class="cx"> """when no Transaction is present, this is called after executions to provide "autocommit" behavior.""" </span><span class="cx"> # TODO: have the dialect determine if autocommit can be set on the connection directly without this </span><span class="cx"> # extra step </span><del>- if self.__transaction is None and re.match(r'UPDATE|INSERT|CREATE|DELETE|DROP', statement.lstrip().upper()): </del><ins>+ if not self.in_transaction() and re.match(r'UPDATE|INSERT|CREATE|DELETE|DROP', statement.lstrip().upper()): </ins><span class="cx"> self._commit_impl() </span><span class="cx"> def close(self): </span><span class="cx"> if self.__connection is not None: </span><span class="cx"> self.__connection.close() </span><span class="cx"> self.__connection = None </span><del>- def scalar(self, object, parameters, **kwargs): </del><ins>+ def scalar(self, object, parameters=None, **kwargs): </ins><span class="cx"> row = self.execute(object, parameters, **kwargs).fetchone() </span><span class="cx"> if row is not None: </span><span class="cx"> return row[0] </span><span class="lines">@@ -406,6 +408,10 @@ </span><span class="cx"> conn.close() </span><span class="cx"> </span><span class="cx"> def transaction(self, callable_, connection=None, *args, **kwargs): </span><ins>+ """executes the given function within a transaction boundary. this is a shortcut for + explicitly calling begin() and commit() and optionally rollback() when execptions are raised. + The given *args and **kwargs will be passed to the function, as well as the Connection used + in the transaction.""" </ins><span class="cx"> if connection is None: </span><span class="cx"> conn = self.contextual_connect() </span><span class="cx"> else: </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyenginestrategiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/engine/strategies.py (1534 => 1535)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/engine/strategies.py 2006-05-28 16:37:22 UTC (rev 1534) +++ sqlalchemy/trunk/lib/sqlalchemy/engine/strategies.py 2006-05-28 17:46:45 UTC (rev 1535) </span><span class="lines">@@ -54,7 +54,7 @@ </span><span class="cx"> dialect = module.dialect(**kwargs) </span><span class="cx"> </span><span class="cx"> poolargs = {} </span><del>- for key in (('echo', 'echo_pool'), ('pool_size', 'pool_size'), ('max_overflow', 'max_overflow'), ('poolclass', 'poolclass'), ('pool_timeout','timeout')): </del><ins>+ for key in (('echo', 'echo_pool'), ('pool_size', 'pool_size'), ('max_overflow', 'max_overflow'), ('poolclass', 'poolclass'), ('pool_timeout','timeout'), ('pool', 'pool')): </ins><span class="cx"> if kwargs.has_key(key[0]): </span><span class="cx"> poolargs[key[1]] = kwargs[key[0]] </span><span class="cx"> poolclass = getattr(module, 'poolclass', None) </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyenginethreadlocalpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/engine/threadlocal.py (1534 => 1535)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/engine/threadlocal.py 2006-05-28 16:37:22 UTC (rev 1534) +++ sqlalchemy/trunk/lib/sqlalchemy/engine/threadlocal.py 2006-05-28 17:46:45 UTC (rev 1535) </span><span class="lines">@@ -6,39 +6,42 @@ </span><span class="cx"> will return the same connection for the same thread. also provides begin/commit methods on the engine itself </span><span class="cx"> which correspond to a thread-local transaction.""" </span><span class="cx"> </span><del>-class TLTransaction(base.Transaction): - def rollback(self): </del><ins>+class TLSession(object): + def __init__(self, engine): + self.engine = engine + self.__tcount = 0 + def get_connection(self, close_with_result=False): </ins><span class="cx"> try: </span><del>- base.Transaction.rollback(self) - finally: </del><ins>+ return self.__transaction + except AttributeError: + return base.Connection(self.engine, close_with_result=close_with_result) + def begin(self): + if self.__tcount == 0: + self.__transaction = self.get_connection() + self.__trans = self.__transaction.begin() + self.__tcount += 1 + def rollback(self): + if self.__tcount > 0: </ins><span class="cx"> try: </span><del>- del self.connection.engine.context.transaction - except AttributeError: - pass </del><ins>+ self.__trans.rollback() + finally: + del self.__transaction + del self.__trans + self.__tcount = 0 </ins><span class="cx"> def commit(self): </span><del>- try: - base.Transaction.commit(self) - stack = self.connection.engine.context.transaction - stack.pop() - if len(stack) == 0: - del self.connection.engine.context.transaction - except: </del><ins>+ if self.__tcount == 1: </ins><span class="cx"> try: </span><del>- del self.connection.engine.context.transaction - except AttributeError: - pass - raise - -class TLConnection(base.Connection): - def _create_transaction(self, parent): - return TLTransaction(self, parent) - def begin(self): - t = base.Connection.begin(self) - if not hasattr(self.engine.context, 'transaction'): - self.engine.context.transaction = [] - self.engine.context.transaction.append(t) - return t - </del><ins>+ self._trans.commit() + finally: + del self.__transaction + del self._trans + self.__tcount = 0 + elif self.__tcount > 1: + self.__tcount -= 1 + def is_begun(self): + return self.__tcount > 0 + + </ins><span class="cx"> class TLEngine(base.ComposedSQLEngine): </span><span class="cx"> """a ComposedSQLEngine that includes support for thread-local managed transactions. This engine </span><span class="cx"> is better suited to be used with threadlocal Pool object.""" </span><span class="lines">@@ -55,29 +58,23 @@ </span><span class="cx"> """returns a Connection that is not thread-locally scoped. this is the equilvalent to calling </span><span class="cx"> "connect()" on a ComposedSQLEngine.""" </span><span class="cx"> return base.Connection(self, self.connection_provider.unique_connection()) </span><ins>+ + def _session(self): + if not hasattr(self.context, 'session'): + self.context.session = TLSession(self) + return self.context.session + session = property(_session, doc="returns the current thread's TLSession") + </ins><span class="cx"> def contextual_connect(self, **kwargs): </span><span class="cx"> """returns a TLConnection which is thread-locally scoped.""" </span><del>- return TLConnection(self, **kwargs) </del><ins>+ return self.session.get_connection(**kwargs) + </ins><span class="cx"> def begin(self): </span><del>- return self.connect().begin() </del><ins>+ return self.session.begin() </ins><span class="cx"> def commit(self): </span><del>- if hasattr(self.context, 'transaction'): - self.context.transaction[-1].commit() </del><ins>+ self.session.commit() </ins><span class="cx"> def rollback(self): </span><del>- if hasattr(self.context, 'transaction'): - self.context.transaction[-1].rollback() - def transaction(self, func, *args, **kwargs): - """executes the given function within a transaction boundary. this is a shortcut for - explicitly calling begin() and commit() and optionally rollback() when execptions are raised. - The given *args and **kwargs will be passed to the function as well, which could be handy - in constructing decorators.""" - trans = self.begin() - try: - func(*args, **kwargs) - except: - trans.rollback() - raise - trans.commit() </del><ins>+ self.session.rollback() </ins><span class="cx"> </span><span class="cx"> class TLocalConnectionProvider(default.PoolConnectionProvider): </span><span class="cx"> def unique_connection(self): </span></span></pre></div> <a id="sqlalchemytrunktesttransactionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/transaction.py (1534 => 1535)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/transaction.py 2006-05-28 16:37:22 UTC (rev 1534) +++ sqlalchemy/trunk/test/transaction.py 2006-05-28 17:46:45 UTC (rev 1535) </span><span class="lines">@@ -5,6 +5,7 @@ </span><span class="cx"> db = testbase.db </span><span class="cx"> from sqlalchemy import * </span><span class="cx"> </span><ins>+ </ins><span class="cx"> class TransactionTest(testbase.PersistTest): </span><span class="cx"> def setUpAll(self): </span><span class="cx"> global users, metadata </span><span class="lines">@@ -34,6 +35,23 @@ </span><span class="cx"> assert len(result.fetchall()) == 0 </span><span class="cx"> connection.close() </span><span class="cx"> </span><ins>+ def testnesting(self): + connection = testbase.db.connect() + transaction = connection.begin() + connection.execute(users.insert(), user_id=1, user_name='user1') + connection.execute(users.insert(), user_id=2, user_name='user2') + connection.execute(users.insert(), user_id=3, user_name='user3') + trans2 = connection.begin() + connection.execute(users.insert(), user_id=4, user_name='user4') + connection.execute(users.insert(), user_id=5, user_name='user5') + trans2.commit() + transaction.rollback() + self.assert_(connection.scalar("select count(1) from query_users") == 0) + + result = connection.execute("select * from query_users") + assert len(result.fetchall()) == 0 + connection.close() + </ins><span class="cx"> class AutoRollbackTest(testbase.PersistTest): </span><span class="cx"> def setUpAll(self): </span><span class="cx"> global metadata </span><span class="lines">@@ -58,6 +76,51 @@ </span><span class="cx"> # comment out the rollback in pool/ConnectionFairy._close() to see ! </span><span class="cx"> users.drop(conn2) </span><span class="cx"> conn2.close() </span><ins>+ +class TLTransactionTest(testbase.PersistTest): + def setUpAll(self): + global users, metadata, tlengine + tlengine = create_engine(testbase.db_uri, strategy='threadlocal', echo=True) + metadata = MetaData() + users = Table('query_users', metadata, + Column('user_id', INT, primary_key = True), + Column('user_name', VARCHAR(20)), + ) + users.create(tlengine) + def tearDown(self): + tlengine.execute(users.delete()) + def tearDownAll(self): + users.drop(tlengine) + tlengine.dispose() </ins><span class="cx"> </span><ins>+ @testbase.unsupported('mysql') + def testrollback(self): + """test a basic rollback""" + tlengine.begin() + tlengine.execute(users.insert(), user_id=1, user_name='user1') + tlengine.execute(users.insert(), user_id=2, user_name='user2') + tlengine.execute(users.insert(), user_id=3, user_name='user3') + tlengine.rollback() + + result = tlengine.execute("select * from query_users") + assert len(result.fetchall()) == 0 + + @testbase.unsupported('mysql', 'sqlite') + def testnesting(self): + """test a basic rollback""" + external_connection = tlengine.connect() + self.assert_(external_connection.connection is not tlengine.contextual_connect().connection) + tlengine.begin() + tlengine.execute(users.insert(), user_id=1, user_name='user1') + tlengine.execute(users.insert(), user_id=2, user_name='user2') + tlengine.execute(users.insert(), user_id=3, user_name='user3') + tlengine.begin() + tlengine.execute(users.insert(), user_id=4, user_name='user4') + tlengine.execute(users.insert(), user_id=5, user_name='user5') + tlengine.commit() + tlengine.rollback() + self.assert_(external_connection.scalar("select count(1) from query_users") == 0) + external_connection.close() + </ins><span class="cx"> if __name__ == "__main__": </span><span class="cx"> testbase.main() </span></span></pre> </div> </div> </body> </html> |