[Sqlalchemy-commits] [1181] sqlalchemy/trunk/test: added "nest_on" option for Session, so nested tra
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-03-22 01:16:31
|
<!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>[1181] sqlalchemy/trunk/test: added "nest_on" option for Session, so nested transactions can occur mostly at the Session level, </title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1181</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-03-21 19:16:16 -0600 (Tue, 21 Mar 2006)</dd> </dl> <h3>Log Message</h3> <pre>added "nest_on" option for Session, so nested transactions can occur mostly at the Session level, fixes [ticket:113]</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyenginepy">sqlalchemy/trunk/lib/sqlalchemy/engine.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingobjectstorepy">sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemytrunktestobjectstorepy">sqlalchemy/trunk/test/objectstore.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyenginepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/engine.py (1180 => 1181)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/engine.py 2006-03-22 00:22:26 UTC (rev 1180) +++ sqlalchemy/trunk/lib/sqlalchemy/engine.py 2006-03-22 01:16:16 UTC (rev 1181) </span><span class="lines">@@ -180,6 +180,8 @@ </span><span class="cx"> if parent is not None: </span><span class="cx"> self.__connection = self.engine._pool.unique_connection() </span><span class="cx"> self.__tcount = 0 </span><ins>+ def pop(self): + self.engine.pop_session(self) </ins><span class="cx"> def _connection(self): </span><span class="cx"> try: </span><span class="cx"> return self.__transaction </span><span class="lines">@@ -448,11 +450,13 @@ </span><span class="cx"> sess = SQLSession(self, self.context.session) </span><span class="cx"> self.context.session = sess </span><span class="cx"> return sess </span><del>- def pop_session(self): </del><ins>+ def pop_session(self, s = None): </ins><span class="cx"> """restores the current thread's SQLSession to that before the last push_session. Returns the restored SQLSession object. Raises an exception if there is no SQLSession pushed onto the stack.""" </span><span class="cx"> sess = self.context.session.parent </span><span class="cx"> if sess is None: </span><del>- raise InvalidRequestError("No SQLSession is pushed onto the stack.") </del><ins>+ raise exceptions.InvalidRequestError("No SQLSession is pushed onto the stack.") + elif s is not None and s is not self.context.session: + raise exceptions.InvalidRequestError("Given SQLSession is not the current session on the stack") </ins><span class="cx"> self.context.session = sess </span><span class="cx"> return sess </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1180 => 1181)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-22 00:22:26 UTC (rev 1180) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-22 01:16:16 UTC (rev 1181) </span><span class="lines">@@ -350,6 +350,8 @@ </span><span class="cx"> """returns a proxying object to this mapper, which will execute methods on the mapper </span><span class="cx"> within the context of the given session. The session is placed as the "current" session </span><span class="cx"> via the push_session/pop_session methods in the objectstore module.""" </span><ins>+ if objectstore.get_session() is session: + return self </ins><span class="cx"> mapper = self </span><span class="cx"> class Proxy(object): </span><span class="cx"> def __getattr__(self, key): </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py (1180 => 1181)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-03-22 00:22:26 UTC (rev 1180) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-03-22 01:16:16 UTC (rev 1181) </span><span class="lines">@@ -17,7 +17,7 @@ </span><span class="cx"> class Session(object): </span><span class="cx"> """Maintains a UnitOfWork instance, including transaction state.""" </span><span class="cx"> </span><del>- def __init__(self, nest_transactions=False, hash_key=None): </del><ins>+ def __init__(self, nest_on=None, hash_key=None): </ins><span class="cx"> """Initialize the objectstore with a UnitOfWork registry. If called </span><span class="cx"> with no arguments, creates a single UnitOfWork for all operations. </span><span class="cx"> </span><span class="lines">@@ -29,13 +29,28 @@ </span><span class="cx"> self.uow = unitofwork.UnitOfWork() </span><span class="cx"> self.parent_uow = None </span><span class="cx"> self.begin_count = 0 </span><del>- self.nest_transactions = nest_transactions </del><ins>+ self.nest_on = util.to_list(nest_on) + self.__pushed_count = 0 </ins><span class="cx"> if hash_key is None: </span><span class="cx"> self.hash_key = id(self) </span><span class="cx"> else: </span><span class="cx"> self.hash_key = hash_key </span><span class="cx"> _sessions[self.hash_key] = self </span><del>- </del><ins>+ + def was_pushed(self): + if self.nest_on is None: + return + self.__pushed_count += 1 + if self.__pushed_count == 1: + for n in self.nest_on: + n.push_session() + def was_popped(self): + if self.nest_on is None or self.__pushed_count == 0: + return + self.__pushed_count -= 1 + if self.__pushed_count == 0: + for n in self.nest_on: + n.pop_session() </ins><span class="cx"> def get_id_key(ident, class_): </span><span class="cx"> """returns an identity-map key for use in storing/retrieving an item from the identity </span><span class="cx"> map, given a tuple of the object's primary key values. </span><span class="lines">@@ -108,7 +123,7 @@ </span><span class="cx"> def _trans_commit(self, trans): </span><span class="cx"> if trans.uow is self.uow and trans.isactive: </span><span class="cx"> try: </span><del>- self.uow.commit() </del><ins>+ self._commit_uow() </ins><span class="cx"> finally: </span><span class="cx"> self.uow = self.parent_uow </span><span class="cx"> self.parent_uow = None </span><span class="lines">@@ -116,6 +131,13 @@ </span><span class="cx"> if trans.uow is self.uow: </span><span class="cx"> self.uow = self.parent_uow </span><span class="cx"> self.parent_uow = None </span><ins>+ + def _commit_uow(self, *obj): + self.was_pushed() + try: + self.uow.commit(*obj) + finally: + self.was_popped() </ins><span class="cx"> </span><span class="cx"> def commit(self, *objects): </span><span class="cx"> """commits the current UnitOfWork transaction. called with </span><span class="lines">@@ -126,11 +148,12 @@ </span><span class="cx"> # if an object list is given, commit just those but dont </span><span class="cx"> # change begin/commit status </span><span class="cx"> if len(objects): </span><ins>+ self._commit_uow(*objects) </ins><span class="cx"> self.uow.commit(*objects) </span><span class="cx"> return </span><span class="cx"> if self.parent_uow is None: </span><del>- self.uow.commit() - </del><ins>+ self._commit_uow() + </ins><span class="cx"> def refresh(self, *obj): </span><span class="cx"> """reloads the attributes for the given objects from the database, clears </span><span class="cx"> any changes made.""" </span><span class="lines">@@ -287,14 +310,18 @@ </span><span class="cx"> </span><span class="cx"> def push_session(sess): </span><span class="cx"> old = get_session() </span><ins>+ if getattr(sess, '_previous', None) is not None: + raise InvalidRequestError("Given Session is already pushed onto some thread's stack") </ins><span class="cx"> sess._previous = old </span><span class="cx"> session_registry.set(sess) </span><ins>+ sess.was_pushed() </ins><span class="cx"> </span><span class="cx"> def pop_session(): </span><span class="cx"> sess = get_session() </span><span class="cx"> old = sess._previous </span><span class="cx"> sess._previous = None </span><span class="cx"> session_registry.set(old) </span><ins>+ sess.was_popped() </ins><span class="cx"> return old </span><span class="cx"> </span><span class="cx"> def using_session(sess, func): </span></span></pre></div> <a id="sqlalchemytrunktestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/objectstore.py (1180 => 1181)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/objectstore.py 2006-03-22 00:22:26 UTC (rev 1180) +++ sqlalchemy/trunk/test/objectstore.py 2006-03-22 01:16:16 UTC (rev 1181) </span><span class="lines">@@ -89,7 +89,8 @@ </span><span class="cx"> tables.delete_user_data() </span><span class="cx"> </span><span class="cx"> def test_nested_begin_commit(self): </span><del>- """test nested session.begin/commit""" </del><ins>+ """tests that nesting objectstore transactions with multiple commits + affects only the outermost transaction""" </ins><span class="cx"> class User(object):pass </span><span class="cx"> m = mapper(User, users) </span><span class="cx"> def name_of(id): </span><span class="lines">@@ -117,6 +118,8 @@ </span><span class="cx"> self.assert_(name_of(8) == name2, msg="user_name should be %s" % name2) </span><span class="cx"> </span><span class="cx"> def test_nested_rollback(self): </span><ins>+ """tests that nesting objectstore transactions with a rollback inside + affects only the outermost transaction""" </ins><span class="cx"> class User(object):pass </span><span class="cx"> m = mapper(User, users) </span><span class="cx"> def name_of(id): </span><span class="lines">@@ -141,6 +144,32 @@ </span><span class="cx"> self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1) </span><span class="cx"> self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2) </span><span class="cx"> </span><ins>+ def test_true_nested(self): + """tests creating a new Session inside a database transaction, in + conjunction with an engine-level nested transaction, which uses + a second connection in order to achieve a nested transaction that commits, inside + of another engine session that rolls back.""" +# testbase.db.echo='debug' + class User(object): + pass + testbase.db.begin() + try: + m = mapper(User, users) + name1 = "Oliver Twist" + name2 = 'Mr. Bumble' + m.get(7).user_name = name1 + s = objectstore.Session(nest_on=testbase.db) + m.using(s).get(8).user_name = name2 + s.commit() + objectstore.commit() + testbase.db.rollback() + except: + testbase.db.rollback() + raise + objectstore.clear() + self.assert_(m.get(8).user_name == name2) + self.assert_(m.get(7).user_name != name1) + </ins><span class="cx"> class UnicodeTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><span class="cx"> global uni_table </span></span></pre> </div> </div> </body> </html> |