[Sqlalchemy-commits] [1307] sqlalchemy/branches/schema/lib/sqlalchemy: some pool/transaction manglin
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-20 23:00:41
|
<!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>[1307] sqlalchemy/branches/schema/lib/sqlalchemy: some pool/transaction mangling</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1307</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-20 18:00:28 -0500 (Thu, 20 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>some pool/transaction mangling</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontenttutorialtxt">sqlalchemy/branches/schema/doc/build/content/tutorial.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemydatabasessqlitepy">sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginedefaultpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginetransactionalpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemypoolpy">sqlalchemy/branches/schema/lib/sqlalchemy/pool.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontenttutorialtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/tutorial.txt (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -144,15 +144,15 @@ </span><span class="cx"> ['Mary', 'secure'] </span><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><span class="cx"> </span><del>- >>> i.execute({'user_name':'Tom'}, {'user_name':'Dick'}, {'user_name':'Harry'}) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </del><ins>+ >>> i.execute({'user_name':'Tom'}, {'user_name':'Fred'}, {'user_name':'Harry'}) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </ins><span class="cx"> INSERT INTO users (user_name) VALUES (?) </span><del>- [['Tom'], ['Dick'], ['Harry']] </del><ins>+ [['Tom'], ['Fred'], ['Harry']] </ins><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><span class="cx"> </span><span class="cx"> </span><span class="cx"> Note that the `VALUES` clause of each `INSERT` statement was automatically adjusted to correspond to the parameters sent to the `execute()` method. This is because the compilation step of a `ClauseElement` takes into account not just the constructed SQL object and the specifics of the type of database being used, but the execution parameters sent along as well. </span><span class="cx"> </span><del>-When constructing clause objects, SQLAlchemy will bind all literal values into bind parameters. On the construction side, bind parameters are always treated as named parameters. At compilation time, SQLAlchemy will convert them their proper format, based on the paramstyle of the underlying DBAPI. This works equally well for all named and positional bind parameter formats described in the DBAPI specification. </del><ins>+When constructing clause objects, SQLAlchemy will bind all literal values into bind parameters. On the construction side, bind parameters are always treated as named parameters. At compilation time, SQLAlchemy will convert them into their proper format, based on the paramstyle of the underlying DBAPI. This works equally well for all named and positional bind parameter formats described in the DBAPI specification. </ins><span class="cx"> </span><span class="cx"> Documentation on inserting: [sql_insert](rel:sql_insert). </span><span class="cx"> </span><span class="lines">@@ -175,15 +175,12 @@ </span><span class="cx"> {python} </span><span class="cx"> >>> r # doctest:+ELLIPSIS </span><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><del>- >>> r.keys - ['user_id', 'user_name', 'password'] - >>> row = r.fetchone() - >>> row['user_name'] - u'Mary' </del><ins>+ >>> r.fetchone() + (1, u'Mary', u'secure') </ins><span class="cx"> >>> r.fetchall() </span><del>- [(2, u'Tom', None), (3, u'Dick', None), (4, u'Harry', None)] </del><ins>+ [(2, u'Tom', None), (3, u'Fred', None), (4, u'Harry', None)] </ins><span class="cx"> </span><del>-Query criterion for the select can also be specified as regular Python expressions, using the column objects in the Table as a base: </del><ins>+Query criterion for the select can also be specified as regular Python expressions, using the `Column` objects in the `Table` as a base. All expressions constructed from `Column` objects are themselves instances of `ClauseElements`, just like the `Select`, `Insert`, and `Table` objects themselves. </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> r = users.select(users.c.user_name=='Harry').execute() </span><span class="lines">@@ -202,11 +199,26 @@ </span><span class="cx"> You can see that when we print out the rows returned by an execution result, it prints the rows as tuples. But in fact these rows are special, and can be used either with a list interface or a dictionary interface. The dictionary interface allows the addressing of columns by string column name, or even the original `Column` object: </span><span class="cx"> </span><span class="cx"> {python} </span><ins>+ >>> row.keys() + ['user_id', 'user_name', 'password'] </ins><span class="cx"> >>> row['user_id'], row[1], row[users.c.password] </span><span class="cx"> (4, u'Harry', None) </span><span class="cx"> </span><span class="cx"> Addressing the columns in a row based on the original `Column` object is especially handy, as it eliminates the need to work with literal column names altogether. </span><span class="cx"> </span><ins>+Result sets also support the regular Python iterator interface. We'll show this with a slightly different form of `select` that allows you to specify the specific columns to be selected: + + {python} + >>> for row in select([users.c.user_id, users.c.user_name]).execute(): # doctest:+NORMALIZE_WHITESPACE + ... print row + SELECT users.user_id, users.user_name + FROM users + [] + (1, u'Mary') + (2, u'Tom') + (3, u'Fred') + (4, u'Harry') + </ins><span class="cx"> ### Table Relationships </span><span class="cx"> </span><span class="cx"> Lets create a second table, `email_addresses`, which references the `users` table. To define the relationship between the two tables, we will use the `ForeignKey` construct. We will also issue the `CREATE` statement for the table in one step: </span><span class="lines">@@ -223,8 +235,10 @@ </span><span class="cx"> ) </span><span class="cx"> ... </span><span class="cx"> </span><del>-Then lets put a few rows in: </del><ins>+Above, the `email_addresses` table is related to the `users` table via the `ForeignKey('users.user_id')`. The `ForeignKey` constructor can take a `Column` object or a string representing the table and column name. When using the string argument, the referenced table must exist within the same `MetaData` object; thats where it looks for the other table! </ins><span class="cx"> </span><ins>+Next, lets put a few rows in: + </ins><span class="cx"> {python} </span><span class="cx"> >>> email_addresses.insert().execute({'email_address':'to...@to...', 'user_id':2},{'email_address':'ma...@ma...', 'user_id':1}) #doctest:+ELLIPSIS </span><span class="cx"> INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) </span><span class="lines">@@ -241,16 +255,16 @@ </span><span class="cx"> >>> print [row for row in r] </span><span class="cx"> [(1, u'Mary', u'secure', 2, u'ma...@ma...', 1), (2, u'Tom', None, 1, u'to...@to...', 2)] </span><span class="cx"> </span><del>-The `join` method is also a standalone function in the `sqlalchemy` namespace. The join condition is figured out from the foreign keys of the Table objects given. They can also be specified explicitly: </del><ins>+The `join` method is also a standalone function in the `sqlalchemy` namespace. The join condition is figured out from the foreign keys of the Table objects given. The condition (also called the "ON clause") can be specified explicitly, such as in this example where we locate all users that used their email address as their password: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> print join(users, email_addresses, users.c.user_id==email_addresses.c.user_id) - users JOIN email_addresses ON users.user_id = email_addresses.user_id </del><ins>+ >>> print join(users, email_addresses, and_(users.c.user_id==email_addresses.c.user_id, users.c.password==email_addresses.c.email_address)) + users JOIN email_addresses ON users.user_id = email_addresses.user_id AND users.password = email_addresses.email_address </ins><span class="cx"> </span><span class="cx"> Working with Object Mappers {@name=orm} </span><span class="cx"> ----------------------------------------------- </span><span class="cx"> </span><del>-Now that we have a little bit of Table and SQL operations covered, lets look into SQLAlchemy's ORM (object relational mapper). With the ORM, you associate Tables (and other *Selectable* units, like queries and table aliases) with Python classes, into units called *Mappers*. Then you can execute queries that return lists of object instances, instead of result sets. The object instances themselves are associated with an object called a *Session*, which automatically tracks changes on each object and supports a "save all at once" operation called a *flush*. </del><ins>+Now that we have a little bit of Table and SQL operations covered, lets look into SQLAlchemy's ORM (object relational mapper). With the ORM, you associate Tables (and other *Selectable* units, like queries and table aliases) with Python classes, into units called *Mappers*. Then you can execute queries that return lists of object instances, instead of result sets. The object instances themselves are associated with an object called a *Session*, which automatically tracks changes on each object and supports a "save all at once" operation called a *flush*. </ins><span class="cx"> </span><span class="cx"> ### Creating a Mapper {@name=mapper} </span><span class="cx"> </span><span class="lines">@@ -261,7 +275,7 @@ </span><span class="cx"> ... def __repr__(self): </span><span class="cx"> ... return "(User %s,password:%s)" % (self.user_name, self.password) </span><span class="cx"> </span><del>-The class is a new style class (i.e. it extends `object`) and does not require a constructor (although one may be provided if desired). We just have one `__repr__` method on it which will display basic information about the User. Note that the `__repr__` method references the instance variables `user_name` and `password` which otherwise aren't defined. While we are free to explicitly define these attributes and treat them normally, this is optional; as SQLAlchemy's `Mapper` construct will manage them for us, as their names correspond to the names of columns in the `users` table. Lets create a mapper, and observe that these attributes are now defined: </del><ins>+The class is a new style class (i.e. it extends `object`) and does not require a constructor (although one may be provided if desired). We just have one `__repr__` method on it which will display basic information about the User. Note that the `__repr__` method references the instance variables `user_name` and `password` which otherwise aren't defined. While we are free to explicitly define these attributes and treat them normally, this is optional; as SQLAlchemy's `Mapper` construct will manage them for us, since their names correspond to the names of columns in the `users` table. Lets create a mapper, and observe that these attributes are now defined: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> usermapper = mapper(User, users) </span><span class="lines">@@ -283,13 +297,13 @@ </span><span class="cx"> FROM users ORDER BY users.oid </span><span class="cx"> [] </span><span class="cx"> >>> l </span><del>- [(User Mary,password:secure), (User Tom,password:None), (User Dick,password:None), (User Harry,password:None)] </del><ins>+ [(User Mary,password:secure), (User Tom,password:None), (User Fred,password:None), (User Harry,password:None)] </ins><span class="cx"> </span><span class="cx"> ### Obtaining a Session {@name=session} </span><span class="cx"> </span><span class="cx"> After you create a Mapper, all operations with that Mapper require the usage of an important object called a `Session`. All objects loaded or saved by the Mapper must be *bound* to a `Session` object, which represents a kind of "workspace" of objects that are loaded into memory. A particular object instance can only be bound to one `Session` at a time. </span><span class="cx"> </span><del>-By default, you have to create a `Session` object explicitly before you can use a `Mapper`, and when loading objects you need to specify the `Session` that will be used to keep track of those objects. But recall that we imported a special *mod* called `threadlocal`, which has made life easier for us by creating a `Session` that is automatically associated with the current thread. Because of that, the `Mapper` was able to use the `Session` that was already associated with the current thread, without us needing to say anything. But now, lets get a handle to that `Session` and deal with it directly. To locate the `Session` corresponding to the current thread, just use `get_session()': </del><ins>+By default, you have to create a `Session` object explicitly before you can use a `Mapper`, and when loading objects you need to specify the `Session` that will be used to keep track of those objects. But recall that we imported a special *mod* called `threadlocal`, which has made life easier for us by creating a `Session` that is automatically associated with the current thread. Because of that, the `Mapper` was able to use the `Session` that was already associated with the current thread, without us needing to say anything. But now, lets get a handle to that `Session` and deal with it directly. To locate the `Session` corresponding to the current thread, just use `get_session()`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> session = get_session() </span><span class="lines">@@ -316,7 +330,7 @@ </span><span class="cx"> {python} </span><span class="cx"> >>> metadata.engine.echo = False </span><span class="cx"> >>> print query.select(User.c.user_id==3) </span><del>- [(User Dick,password:None)] </del><ins>+ [(User Fred,password:None)] </ins><span class="cx"> >>> print query.get(2) </span><span class="cx"> (User Tom,password:None) </span><span class="cx"> >>> print query.get_by(user_name='Mary') </span><span class="lines">@@ -328,6 +342,63 @@ </span><span class="cx"> </span><span class="cx"> Notice that our `User` class has a special attribute `c` attached to it. This 'c' represents the columns on the User's mapper's Table object. Saying `User.c.user_name` is synonymous with saying `users.c.user_name`, recalling that `User` is the Python class and `users` is our `Table` object. </span><span class="cx"> </span><ins>+### Making Changes {@name=changes} + +With a little experience in loading objects, lets see what its like to make changes. First, lets create a new user "Ed". We do this by just constructing the new object. The `Mapper` for the `User` class will be called when we create the object, which will then automatically add it to the current thread's Session, if one is available: + + {python} + >>> ed = User() + >>> ed.user_name = 'ed' + >>> ed.password = 'edspassword' + >>> ed in session + True + +Lets also make a few changes on some of the objects in the database. We will load them with our `Query` object, and then change some things. + + {python} + >>> mary = query.get_by(user_name='Mary') + >>> harry = query.get_by(user_name='Harry') + >>> mary.password = 'marysnewpassword' + >>> harry.password = 'harrysnewpassword' + +At the moment, nothing has been saved to the database; all of our changes are in memory only. What happens if some other part of the application also tries to load 'Mary' from the database and make some changes before we had a chance to save it ? Assuming that the same `Session` is used, loading 'Mary' from the database a second time will issue a second query in order locate the primary key of 'Mary', but will *return the same object instance as the one already loaded*. This behavior is due to an important property of the `Session` known as the **identity map**: + + {python} + >>> mary2 = query.get_by(user_name='Mary') + >>> mary is mary2 + True + +With the identity map, a single `Session` can be relied upon to keep all loaded instances straight, and when a thread-local Session is used, it becomes pretty hard for an application to lose track of the changes made on objects. + +As far as the issue of the same object being modified in two different Sessions, that's an issue of concurrency detection; SQLAlchemy does some basic concurrency checks when saving objects, with the option for a stronger check using version ids. See [adv_datamapping](rel:adv_datamapping) for more details. + +### Saving {@name=saving} + +With a new user "ed" and some changes made on "Mary" and "Harry", lets also mark "Fred" as deleted: + + {python} + >>> fred = query.get_by(user_name='Fred') + >>> session.delete(fred) + +Then to send all of our changes to the database, we `flush()` the Session. Lets turn echo back on to see this happen!: + + {python} + >>> metadata.engine.echo = True + >>> session.flush() + BEGIN + UPDATE users SET password=? WHERE users.user_id = ? + ['marysnewpassword', 1] + UPDATE users SET password=? WHERE users.user_id = ? + ['harrysnewpassword', 4] + INSERT INTO users (user_name, password) VALUES (?, ?) + [None, None] + INSERT INTO users (user_name, password) VALUES (?, ?) + ['ed', 'edspassword'] + DELETE FROM users WHERE users.user_id = ? + [[3]] + COMMIT + + </ins><span class="cx"> ### Transactions </span><span class="cx"> </span><span class="cx"> Main documentation: [unitofwork](rel:unitofwork), [dbengine_transactions](rel:dbengine_transactions). </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemydatabasessqlitepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -227,8 +227,6 @@ </span><span class="cx"> else: </span><span class="cx"> return ansisql.ANSICompiler.binary_operator_string(self, binary) </span><span class="cx"> </span><del>- - </del><span class="cx"> class SQLiteSchemaGenerator(ansisql.ANSISchemaGenerator): </span><span class="cx"> def get_column_specification(self, column, override_pk=False, **kwargs): </span><span class="cx"> colspec = column.name + " " + column.type.engine_impl(self.engine).get_col_spec() </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginedefaultpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -19,10 +19,13 @@ </span><span class="cx"> if pool is None: </span><span class="cx"> kwargs.setdefault('echo', False) </span><span class="cx"> kwargs.setdefault('use_threadlocal',True) </span><del>- if poolclass is not None: - kwargs['poolclass'] = poolclass - self._dbproxy = sqlalchemy.pool.manage(dialect.dbapi(), **kwargs) - self._pool = self._dbproxy.get_pool(*cargs, **cparams) </del><ins>+ if poolclass is None: + poolclass = sqlalchemy.pool.QueuePool + dbapi = dialect.dbapi() + if dbapi is None: + raise exceptions.InvalidRequestException("Cant get DBAPI module for dialect '%s'" % dialect) + + self._pool = poolclass(lambda: dbapi.connect(*cargs, **cparams), **kwargs) </ins><span class="cx"> else: </span><span class="cx"> if isinstance(pool, sqlalchemy.pool.DBProxy): </span><span class="cx"> self._pool = pool.get_pool(*cargs, **cparams) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginetransactionalpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -11,7 +11,10 @@ </span><span class="cx"> try: </span><span class="cx"> base.Transaction.rollback(self) </span><span class="cx"> finally: </span><del>- del self.connection.engine.context.transaction </del><ins>+ try: + del self.connection.engine.context.transaction + except AttributeError: + pass </ins><span class="cx"> def commit(self): </span><span class="cx"> try: </span><span class="cx"> base.Transaction.commit(self) </span><span class="lines">@@ -20,7 +23,10 @@ </span><span class="cx"> if len(stack) == 0: </span><span class="cx"> del self.connection.engine.context.transaction </span><span class="cx"> except: </span><del>- del self.connection.engine.context.transaction </del><ins>+ try: + del self.connection.engine.context.transaction + except AttributeError: + pass </ins><span class="cx"> raise </span><span class="cx"> </span><span class="cx"> class TLConnection(base.Connection): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -13,10 +13,11 @@ </span><span class="cx"> </span><span class="cx"> </span><span class="cx"> class SessionTransaction(object): </span><del>- def __init__(self, session, parent=None): </del><ins>+ def __init__(self, session, parent=None, autoflush=True): </ins><span class="cx"> self.session = session </span><span class="cx"> self.connections = {} </span><span class="cx"> self.parent = parent </span><ins>+ self.autoflush = autoflush </ins><span class="cx"> def connection(self, mapper): </span><span class="cx"> if self.parent is not None: </span><span class="cx"> return self.parent.connection(mapper) </span><span class="lines">@@ -32,6 +33,8 @@ </span><span class="cx"> def commit(self): </span><span class="cx"> if self.parent is not None: </span><span class="cx"> return </span><ins>+ if self.autoflush: + self.session.flush() </ins><span class="cx"> for t in self.connections.values(): </span><span class="cx"> t[1].commit() </span><span class="cx"> self.close() </span><span class="lines">@@ -39,7 +42,7 @@ </span><span class="cx"> if self.parent is not None: </span><span class="cx"> self.parent.rollback() </span><span class="cx"> return </span><del>- for t in self.connections.values(): </del><ins>+ for k, t in self.connections.iteritems(): </ins><span class="cx"> t[1].rollback() </span><span class="cx"> self.close() </span><span class="cx"> def close(self): </span><span class="lines">@@ -67,14 +70,14 @@ </span><span class="cx"> self.hash_key = hash_key </span><span class="cx"> _sessions[self.hash_key] = self </span><span class="cx"> </span><del>- def create_transaction(self): </del><ins>+ def create_transaction(self, **kwargs): </ins><span class="cx"> """returns a new SessionTransaction corresponding to an existing or new transaction. </span><span class="cx"> if the transaction is new, the returned SessionTransaction will have commit control </span><span class="cx"> over the underlying transaction, else will have rollback control only.""" </span><span class="cx"> if self.transaction is not None: </span><span class="cx"> return self.transaction._begin() </span><span class="cx"> else: </span><del>- self.transaction = SessionTransaction(self) </del><ins>+ self.transaction = SessionTransaction(self, **kwargs) </ins><span class="cx"> return self.transaction </span><span class="cx"> def connect(self, mapper=None, **kwargs): </span><span class="cx"> """returns a unique connection corresponding to the given mapper. this connection </span><span class="lines">@@ -254,7 +257,9 @@ </span><span class="cx"> obj._sa_session_id = self.hash_key </span><span class="cx"> def _is_bound(self, obj): </span><span class="cx"> return getattr(obj, '_sa_session_id', None) == self.hash_key </span><del>- </del><ins>+ def __contains__(self, obj): + return self._is_bound(obj) and (obj in self.uow.new or self.uow.has_key(obj._instance_key)) + </ins><span class="cx"> def _get(self, key): </span><span class="cx"> return self.uow._get(key) </span><span class="cx"> def has_key(self, key): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -245,7 +245,7 @@ </span><span class="cx"> continue </span><span class="cx"> flush_context.register_object(obj, isdelete=True) </span><span class="cx"> </span><del>- trans = session.create_transaction() </del><ins>+ trans = session.create_transaction(autoflush=False) </ins><span class="cx"> flush_context.transaction = trans </span><span class="cx"> echo_commit = False </span><span class="cx"> try: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemypoolpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/pool.py (1306 => 1307)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/pool.py 2006-04-20 21:04:03 UTC (rev 1306) +++ sqlalchemy/branches/schema/lib/sqlalchemy/pool.py 2006-04-20 23:00:28 UTC (rev 1307) </span><span class="lines">@@ -73,7 +73,7 @@ </span><span class="cx"> def __init__(self, echo = False, use_threadlocal = True, logger=None, **kwargs): </span><span class="cx"> self._threadconns = {} #weakref.WeakValueDictionary() </span><span class="cx"> self._use_threadlocal = use_threadlocal </span><del>- self._echo = echo </del><ins>+ self.echo = echo </ins><span class="cx"> self._logger = logger or util.Logger(origin='pool') </span><span class="cx"> </span><span class="cx"> def unique_connection(self): </span><span class="lines">@@ -91,8 +91,6 @@ </span><span class="cx"> return agent </span><span class="cx"> </span><span class="cx"> def return_conn(self, agent): </span><del>- if self._echo: - self.log("return connection to pool") </del><span class="cx"> if self._use_threadlocal: </span><span class="cx"> try: </span><span class="cx"> del self._threadconns[thread.get_ident()] </span><span class="lines">@@ -101,15 +99,9 @@ </span><span class="cx"> self.do_return_conn(agent.connection) </span><span class="cx"> </span><span class="cx"> def get(self): </span><del>- if self._echo: - self.log("get connection from pool") - self.log(self.status()) </del><span class="cx"> return self.do_get() </span><span class="cx"> </span><span class="cx"> def return_invalid(self): </span><del>- if self._echo: - self.log("return invalid connection to pool") - self.log(self.status()) </del><span class="cx"> self.do_return_invalid() </span><span class="cx"> </span><span class="cx"> def do_get(self): </span><span class="lines">@@ -140,7 +132,11 @@ </span><span class="cx"> self.connection = None </span><span class="cx"> self.pool.return_invalid() </span><span class="cx"> raise </span><ins>+ if self.pool.echo: + self.pool.log("Connection %s checked out from pool" % repr(self.connection)) </ins><span class="cx"> def invalidate(self): </span><ins>+ if self.pool.echo: + self.pool.log("Invalidate connection %s" % repr(self.connection)) </ins><span class="cx"> self.connection = None </span><span class="cx"> self.pool.return_invalid() </span><span class="cx"> def cursor(self): </span><span class="lines">@@ -160,6 +156,8 @@ </span><span class="cx"> self._close() </span><span class="cx"> def _close(self): </span><span class="cx"> if self.connection is not None: </span><ins>+ if self.pool.echo: + self.pool.log("Connection %s being returned to pool" % repr(self.connection)) </ins><span class="cx"> self.pool.return_conn(self) </span><span class="cx"> self.pool = None </span><span class="cx"> self.connection = None </span><span class="lines">@@ -173,19 +171,18 @@ </span><span class="cx"> </span><span class="cx"> class SingletonThreadPool(Pool): </span><span class="cx"> """Maintains one connection per each thread, never moving to another thread. this is </span><del>- used for SQLite and other databases with a similar restriction.""" </del><ins>+ used for SQLite.""" </ins><span class="cx"> def __init__(self, creator, **params): </span><span class="cx"> Pool.__init__(self, **params) </span><span class="cx"> self._conns = {} </span><span class="cx"> self._creator = creator </span><span class="cx"> </span><span class="cx"> def status(self): </span><del>- return "SingletonThreadPool thread:%d size: %d" % (thread.get_ident(), len(self._conns)) </del><ins>+ return "SingletonThreadPool id:%d thread:%d size: %d" % (id(self), thread.get_ident(), len(self._conns)) </ins><span class="cx"> </span><span class="cx"> def do_return_conn(self, conn): </span><del>- if self._conns.get(thread.get_ident(), None) is None: - self._conns[thread.get_ident()] = conn - </del><ins>+ pass + </ins><span class="cx"> def do_return_invalid(self): </span><span class="cx"> try: </span><span class="cx"> del self._conns[thread.get_ident()] </span><span class="lines">@@ -194,13 +191,11 @@ </span><span class="cx"> </span><span class="cx"> def do_get(self): </span><span class="cx"> try: </span><del>- c = self._conns[thread.get_ident()] - if c is None: - return self._creator() </del><ins>+ return self._conns[thread.get_ident()] </ins><span class="cx"> except KeyError: </span><span class="cx"> c = self._creator() </span><del>- self._conns[thread.get_ident()] = None - return c </del><ins>+ self._conns[thread.get_ident()] = c + return c </ins><span class="cx"> </span><span class="cx"> class QueuePool(Pool): </span><span class="cx"> """uses Queue.Queue to maintain a fixed-size list of connections.""" </span><span class="lines">@@ -212,23 +207,16 @@ </span><span class="cx"> self._max_overflow = max_overflow </span><span class="cx"> </span><span class="cx"> def do_return_conn(self, conn): </span><del>- if self._echo: - self.log("return QP connection to pool") </del><span class="cx"> try: </span><span class="cx"> self._pool.put(conn, False) </span><span class="cx"> except Queue.Full: </span><span class="cx"> self._overflow -= 1 </span><span class="cx"> </span><span class="cx"> def do_return_invalid(self): </span><del>- if self._echo: - self.log("return invalid connection") </del><span class="cx"> if self._pool.full(): </span><span class="cx"> self._overflow -= 1 </span><span class="cx"> </span><span class="cx"> def do_get(self): </span><del>- if self._echo: - self.log("get QP connection from pool") - self.log(self.status()) </del><span class="cx"> try: </span><span class="cx"> return self._pool.get(self._max_overflow > -1 and self._overflow >= self._max_overflow) </span><span class="cx"> except Queue.Empty: </span></span></pre> </div> </div> </body> </html> |