[Sqlalchemy-commits] [1305] sqlalchemy/branches/schema/doc: progress on the tutorial...cleanup of se
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-20 20:38:23
|
<!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>[1305] sqlalchemy/branches/schema/doc: progress on the tutorial...cleanup of session interface</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1305</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-20 15:38:02 -0500 (Thu, 20 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>progress on the tutorial...cleanup of session interface</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcomponentsformattingmyt">sqlalchemy/branches/schema/doc/build/components/formatting.myt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentdatamappingtxt">sqlalchemy/branches/schema/doc/build/content/datamapping.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentsqlconstructiontxt">sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontenttutorialtxt">sqlalchemy/branches/schema/doc/build/content/tutorial.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildtxt2mytpy">sqlalchemy/branches/schema/doc/build/txt2myt.py</a></li> <li><a href="#sqlalchemybranchesschemadocdocscss">sqlalchemy/branches/schema/doc/docs.css</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemy__init__py">sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymodsthreadlocalpy">sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcomponentsformattingmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/components/formatting.myt (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/components/formatting.myt 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/components/formatting.myt 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -261,6 +261,7 @@ </span><span class="cx"> title = None </span><span class="cx"> syntaxtype = 'python' </span><span class="cx"> html_escape = False </span><ins>+ use_sliders = False </ins><span class="cx"> </%args> </span><span class="cx"> </span><span class="cx"> <%init> </span><span class="lines">@@ -289,7 +290,7 @@ </span><span class="cx"> return "<pre>" + highlight.highlight(fix_indent(match.group(1)), html_escape = html_escape, syntaxtype = syntaxtype) + "</pre>" </span><span class="cx"> content = p.sub(hlight, "<pre>" + m.content() + "</pre>") </span><span class="cx"> </%init> </span><del>-<div class="code"> </del><ins>+<div class="<% use_sliders and "sliding_code" or "code" %>"> </ins><span class="cx"> % if title is not None: </span><span class="cx"> <div class="codetitle"><% title %></div> </span><span class="cx"> % </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentdatamappingtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/datamapping.txt (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -1,4 +1,4 @@ </span><del>-Data Mapping </del><ins>+Data Mapping {@name=datamapping} </ins><span class="cx"> ============ </span><span class="cx"> </span><span class="cx"> ### Basic Data Mapping {@name=datamapping} </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentsqlconstructiontxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -1,4 +1,4 @@ </span><del>-Constructing SQL Queries via Python Expressions {@name=sqlconstruction} </del><ins>+Constructing SQL Queries via Python Expressions {@name=sql} </ins><span class="cx"> =============================================== </span><span class="cx"> </span><span class="cx"> *Note:* This section describes how to use SQLAlchemy to construct SQL queries and receive result sets. It does *not* cover the object relational mapping capabilities of SQLAlchemy; that is covered later on in [datamapping](rel:datamapping). However, both areas of functionality work similarly in how selection criterion is constructed, so if you are interested just in ORM, you should probably skim through basic [sql_select_whereclause](rel:sql_select_whereclause) construction before moving on. </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontenttutorialtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/tutorial.txt (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -19,6 +19,10 @@ </span><span class="cx"> [install setuptools]: http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions </span><span class="cx"> [cheese]: http://cheeseshop.python.org/pypi </span><span class="cx"> </span><ins>+Otherwise, you can install from the distribution using the `setup.py` script: + + $ python setup.py install + </ins><span class="cx"> ### Installing a Database API {@name=dbms} </span><span class="cx"> </span><span class="cx"> SQLAlchemy is designed to operate with a [DBAPI][DBAPI] implementation built for a particular database, and includes support for the most popular databases. If you have one of the [supported DBAPI implementations][supported dbms], you can proceed to the following section. Otherwise [SQLite][] is an easy-to-use database to get started with, which works with plain files or in-memory databases. </span><span class="lines">@@ -61,10 +65,10 @@ </span><span class="cx"> Working with Database Objects {@name=schemasql} </span><span class="cx"> ----------------------------------------------- </span><span class="cx"> </span><ins>+A core philosophy of SQLAlchemy is that tables and domain classes are different beasts. For this reason, SQLAlchemy provides constructs that represent tables by themselves (known as *table metadata*). So we will begin by constructing table metadata objects and performing SQL operations with them directly. Later, we will look into SQLAlchemy's Object Relational Mapper (ORM), which provides an additional layer of abstraction onto table metadata, allowing us to load and save objects of any arbitrary Python class. + </ins><span class="cx"> ### Defining Metadata, Binding to Engines {@name=metadata} </span><span class="cx"> </span><del>-A core philosophy of SQLAlchemy is that tables and domain classes are different beasts. For this reason, SQLAlchemy provides constructs that represent tables by themselves (known as *table metadata*). So we will begin by constructing table metadata objects and performing SQL operations with them directly, keeping in mind that there is also an Object Relational Mapper (ORM) which does the same thing except via domain models. - </del><span class="cx"> Firstly, your Tables have to belong to a collection called `MetaData`. We will create a handy form of `MetaData` that automatically connects to our `Engine` (connecting a schema object to an Engine is called *binding*): </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -100,7 +104,7 @@ </span><span class="cx"> ... Column('password', String(80)) </span><span class="cx"> ... ) </span><span class="cx"> </span><del>-As you might have guessed, we have just defined a table named `users` which has three columns: `user_id` (which is a primary key column), `user_name` and `password`. Currently it is just an object that may not correspond to an existing table in your database. To actually create the table, we use the `create()` method. To make it interesting, we will have SQLAlchemy to echo the SQL statements it sends to the database, by setting the `echo` flag on the `Engine` associated with our `BoundMetaData`: </del><ins>+As you might have guessed, we have just defined a table named `users` which has three columns: `user_id` (which is a primary key column), `user_name` and `password`. Currently it is just an object that doesn't necessarily correspond to an existing table in your database. To actually create the table, we use the `create()` method. To make it interesting, we will have SQLAlchemy echo the SQL statements it sends to the database, by setting the `echo` flag on the `Engine` associated with our `BoundMetaData`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> metadata.engine.echo = True </span><span class="lines">@@ -132,31 +136,25 @@ </span><span class="cx"> >>> print i </span><span class="cx"> INSERT INTO users (user_id, user_name, password) VALUES (?, ?, ?) </span><span class="cx"> </span><del>-Since we created this insert statement object from the `users` table which is bound to our `Engine`, the statement itself is also bound to the `Engine`, and supports executing itself. The `execute()` method of the clause object will *compile* the object into a string according to the underlying *dialect* of the Engine to which the statement is bound, and then executes the resulting statement. </del><ins>+Since we created this insert statement object from the `users` table which is bound to our `Engine`, the statement itself is also bound to the `Engine`, and supports executing itself. The `execute()` method of the clause object will *compile* the object into a string according to the underlying *dialect* of the Engine to which the statement is bound, and will then execute the resulting statement. </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> for name in ['Tom', 'Dick', 'Harry']: # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE - ... i.execute(user_name = name) - INSERT INTO users (user_name) VALUES (?) - ['Tom'] </del><ins>+ >>> i.execute(user_name='Mary', password='secure') # doctest:+ELLIPSIS + INSERT INTO users (user_name, password) VALUES (?, ?) + ['Mary', 'secure'] </ins><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><ins>+ + >>> i.execute({'user_name':'Tom'}, {'user_name':'Dick'}, {'user_name':'Harry'}) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </ins><span class="cx"> INSERT INTO users (user_name) VALUES (?) </span><del>- ['Dick'] </del><ins>+ [['Tom'], ['Dick'], ['Harry']] </ins><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><del>- INSERT INTO users (user_name) VALUES (?) - ['Harry'] - <sqlalchemy.engine.base.ResultProxy instance at 0x...> </del><span class="cx"> </span><del>- >>> i.execute(user_name = 'Mary', password = 'secure') # doctest:+ELLIPSIS - INSERT INTO users (user_name, password) VALUES (?, ?) - ['Mary', 'secure'] - <sqlalchemy.engine.base.ResultProxy instance at 0x...> </del><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><span class="cx"> 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. </span><span class="cx"> </span><del>-Documentation on inserting: [sqlconstruction_insert](rel:sqlconstruction_insert). </del><ins>+Documentation on inserting: [sql_insert](rel:sql_insert). </ins><span class="cx"> </span><span class="cx"> ### Selecting </span><span class="cx"> </span><span class="lines">@@ -181,9 +179,9 @@ </span><span class="cx"> ['user_id', 'user_name', 'password'] </span><span class="cx"> >>> row = r.fetchone() </span><span class="cx"> >>> row['user_name'] </span><del>- u'Tom' </del><ins>+ u'Mary' </ins><span class="cx"> >>> r.fetchall() </span><del>- [(2, u'Dick', None), (3, u'Harry', None), (4, u'Mary', u'secure')] </del><ins>+ [(2, u'Tom', None), (3, u'Dick', None), (4, u'Harry', None)] </ins><span class="cx"> </span><span class="cx"> Query criterion for the select can also be specified as regular Python expressions, using the column objects in the Table as a base: </span><span class="cx"> </span><span class="lines">@@ -193,11 +191,22 @@ </span><span class="cx"> FROM users </span><span class="cx"> WHERE users.user_name = ? </span><span class="cx"> ['Harry'] </span><del>- >>> r.fetchall() - [(3, u'Harry', None)] </del><ins>+ >>> row = r.fetchone() + >>> print row + (4, u'Harry', None) </ins><span class="cx"> </span><del>-Pretty much the full range of standard SQL operations are supported as constructed Python expressions, including joins, ordering, grouping, functions, correlated subqueries, unions, etc. Documentation on selecting: [sqlconstruction_select](rel:sqlconstruction_select). </del><ins>+Pretty much the full range of standard SQL operations are supported as constructed Python expressions, including joins, ordering, grouping, functions, correlated subqueries, unions, etc. Documentation on selecting: [sql_select](rel:sql_select). </ins><span class="cx"> </span><ins>+### Working with Rows + +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: + + {python} + >>> row['user_id'], row[1], row[users.c.password] + (4, u'Harry', None) + +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. + </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">@@ -214,6 +223,14 @@ </span><span class="cx"> ) </span><span class="cx"> ... </span><span class="cx"> </span><ins>+Then lets put a few rows in: + + {python} + >>> email_addresses.insert().execute({'email_address':'to...@to...', 'user_id':2},{'email_address':'ma...@ma...', 'user_id':1}) #doctest:+ELLIPSIS + INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) + [['to...@to...', 2], ['ma...@ma...', 1]] + <sqlalchemy.engine.base.ResultProxy instance at 0x...> + </ins><span class="cx"> With two related tables, we can now construct a join amongst them, like this: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -221,12 +238,96 @@ </span><span class="cx"> SELECT users.user_id, users.user_name, users.password, email_addresses.address_id, email_addresses.email_address, email_addresses.user_id </span><span class="cx"> FROM users JOIN email_addresses ON users.user_id = email_addresses.user_id </span><span class="cx"> [] </span><ins>+ >>> print [row for row in r] + [(1, u'Mary', u'secure', 2, u'ma...@ma...', 1), (2, u'Tom', None, 1, u'to...@to...', 2)] </ins><span class="cx"> </span><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. They can also be specified explicitly: + + {python} + >>> 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 + +Working with Object Mappers {@name=orm} +----------------------------------------------- + +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*. + +### Creating a Mapper {@name=mapper} + +A Mapper is usually created once per Python class, and at its core primarily means to say, "objects of this class are to be stored as rows in this table". Lets create a class called `User`, which will represent a user object that is stored in our `users` table: + + {python} + >>> class User(object): + ... def __repr__(self): + ... return "(User %s,password:%s)" % (self.user_name, self.password) + +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: + + {python} + >>> usermapper = mapper(User, users) + >>> u1 = User() + >>> print u1.user_name + None + >>> print u1.password + None </ins><span class="cx"> </span><del>-### Data Mapping {@name=mapping} </del><ins>+When you create a Mapper for a class, that Mapper is now known as the classes' *primary mapper*. SA's ORM can now automatically locate this Mapper when it deals with the class, or instances of that class. </ins><span class="cx"> </span><del>-Main documentation: [datamapping](rel:datamapping), [adv_datamapping](rel:adv_datamapping). </del><ins>+### Querying Objects {@name=querying} </ins><span class="cx"> </span><ins>+We have assigned the new Mapper we created to the instance variable `usermapper`. Using this object, we can issue queries to load objects from the database. For example, to load all of our existing User objects from the database, we just use the method `select()`, which will return a list containing all the objects. Keep in mind that query echoing is still turned on, so we will also see the SQL queries issued: + + {python} + >>> l = usermapper.select() + SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id + FROM users ORDER BY users.oid + [] + >>> l + [(User Mary,password:secure), (User Tom,password:None), (User Dick,password:None), (User Harry,password:None)] + +### Obtaining a Session {@name=session} + +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. + +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()': + + {python} + >>> session = get_session() + >>> session # doctest:+ELLIPSIS + <sqlalchemy.mapping.objectstore.Session object at 0x...> + +### The Query Object {@name=query} + +The Session has all kinds of methods on it to retrieve and store objects, and also to view their current status. The Session also provides an easy interface which can be used to query the database, by giving you an instance to a `Query` object corresponding to a particular Python class: + + {python} + >>> query = session.query(User) + >>> print query.select_by(user_name='Harry') + SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id + FROM users + WHERE users.user_name = ? ORDER BY users.oid + ['Harry'] + [(User Harry,password:None)] + +All querying for objects is performed via an instance of `Query`. The various `select` methods on an instance of `Mapper` also use an underlying `Query` object to perform the operation. A `Query` can be bound to a specific `Session`, or it can also use `get_session()` to locate the session bound to the current thread, if one is available. + +Lets turn off the database echoing for a moment, and try out a few methods on `Query`. Methods that end with the suffix `_by` primarily take keyword arguments which correspond to properties on the object. Other methods take `ClauseElement` objects, which are constructed by using `Column` objects inside of Python expressions, in the same way as we did with our SQL select example in the previous section of this tutorial. Using `ClauseElement` structures to query objects is more verbose but more flexible: + + {python} + >>> metadata.engine.echo = False + >>> print query.select(User.c.user_id==3) + [(User Dick,password:None)] + >>> print query.get(2) + (User Tom,password:None) + >>> print query.get_by(user_name='Mary') + (User Mary,password:secure) + >>> print query.selectfirst(User.c.password==None) + (User Tom,password:None) + >>> print query.count() + 4 + +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. + </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="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -1,15 +1,15 @@ </span><del>-Unit of Work </del><ins>+Unit of Work {@name=unitofwork} </ins><span class="cx"> ============ </span><span class="cx"> </span><span class="cx"> ### Overview {@name=overview} </span><span class="cx"> </span><del>-The concept behind Unit of Work is to track modifications to a field of objects, and then be able to commit those changes to the database in a single operation. Theres a lot of advantages to this, including that your application doesn't need to worry about individual save operations on objects, nor about the required order for those operations, nor about excessive repeated calls to save operations that would be more efficiently aggregated into one step. It also simplifies database transactions, providing a neat package with which to insert into the traditional database begin/commit phase. </del><ins>+The concept behind Unit of Work is to track modifications to a field of objects, and then be able to flush those changes to the database in a single operation. Theres a lot of advantages to this, including that your application doesn't need to worry about individual save operations on objects, nor about the required order for those operations, nor about excessive repeated calls to save operations that would be more efficiently aggregated into one step. It also simplifies database transactions, providing a neat package with which to insert into the traditional database begin/commit phase. </ins><span class="cx"> </span><span class="cx"> SQLAlchemy's unit of work includes these functions: </span><span class="cx"> </span><span class="cx"> * The ability to monitor scalar and list attributes on object instances, as well as object creates. This is handled via the attributes package. </span><span class="cx"> * The ability to maintain and process a list of modified objects, and based on the relationships set up by the mappers for those objects as well as the foreign key relationships of the underlying tables, figure out the proper order of operations so that referential integrity is maintained, and also so that on-the-fly values such as newly created primary keys can be propigated to dependent objects that need them before they are saved. The central algorithm for this is the *topological sort*. </span><del>-* The ability to define custom functionality that occurs within the unit-of-work commit phase, such as "before insert", "after insert", etc. This is accomplished via MapperExtension. </del><ins>+* The ability to define custom functionality that occurs within the unit-of-work flush phase, such as "before insert", "after insert", etc. This is accomplished via MapperExtension. </ins><span class="cx"> * an Identity Map, which is a dictionary storing the one and only instance of an object for a particular table/primary key combination. This allows many parts of an application to get a handle to a particular object without any chance of modifications going to two different places. </span><span class="cx"> * Thread-local operation. the Identity map as well as its enclosing Unit of Work are normally instantiated and accessed in a manner that is local to the current thread, within an object called a Session. Another concurrently executing thread will therefore have its own Session, so unless an application explicitly shares objects between threads, the operation of the object relational mapping is automatically threadsafe. Session objects can also be constructed manually to allow any user-defined scoping. </span><span class="cx"> </span><span class="lines">@@ -21,13 +21,13 @@ </span><span class="cx"> # get the current thread's session </span><span class="cx"> session = objectstore.get_session() </span><span class="cx"> </span><del>-The Session object acts as a proxy to an underlying UnitOfWork object. Common methods include commit(), begin(), clear(), and delete(). Most of these methods are available at the module level in the objectstore module, which operate upon the Session returned by the get_session() function: </del><ins>+The Session object acts as a proxy to an underlying UnitOfWork object. Common methods include flush(), clear(), and delete(). Most of these methods are available at the module level in the objectstore module, which operate upon the Session returned by the get_session() function: </ins><span class="cx"> </span><span class="cx"> {python}# this... </span><del>- objectstore.get_session().commit() </del><ins>+ objectstore.get_session().flush() </ins><span class="cx"> </span><span class="cx"> # is the same as this: </span><del>- objectstore.commit() </del><ins>+ objectstore.flush() </ins><span class="cx"> </span><span class="cx"> A description of the most important methods and concepts follows. </span><span class="cx"> </span><span class="lines">@@ -99,7 +99,7 @@ </span><span class="cx"> </span><span class="cx"> #### Whats Changed ? {@name=changed} </span><span class="cx"> </span><del>-The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores records of all *newly created* objects, records of all objects whose attributes have been *modified*, records of all objects that have been marked as *deleted*, and records of all *modified list-based attributes* where additions or deletions have occurred. These lists are used when a `commit()` call is issued to save all changes. After the commit occurs, these lists are all cleared out. </del><ins>+The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores records of all *newly created* objects, records of all objects whose attributes have been *modified*, records of all objects that have been marked as *deleted*, and records of all *modified list-based attributes* where additions or deletions have occurred. These lists are used when a `flush()` call is issued to save all changes. After the flush occurs, these lists are all cleared out. </ins><span class="cx"> </span><span class="cx"> These records are all tracked by a collection of `Set` objects (which are a SQLAlchemy-specific instance called a `HashSet`) that are also viewable off the Session: </span><span class="cx"> </span><span class="lines">@@ -143,8 +143,8 @@ </span><span class="cx"> >>> ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] </span><span class="cx"> ['sqlalchemy.mapping.unitofwork.UOWListElement 7391872'] </span><span class="cx"> </span><del>- >>> # now commit - >>> session.commit() </del><ins>+ >>> # now flush + >>> session.flush() </ins><span class="cx"> </span><span class="cx"> >>> # the "new" list is now empty </span><span class="cx"> >>> session.new </span><span class="lines">@@ -170,8 +170,8 @@ </span><span class="cx"> >>> session.deleted </span><span class="cx"> [<__main__.Address object at 0x713a70>] </span><span class="cx"> </span><del>- >>> # commit - >>> session.commit() </del><ins>+ >>> # flush + >>> session.flush() </ins><span class="cx"> </span><span class="cx"> >>> # all lists are cleared out </span><span class="cx"> >>> session.new, session.dirty, session.modified_lists, session.deleted </span><span class="lines">@@ -181,38 +181,36 @@ </span><span class="cx"> >>> session.identity_map.values() </span><span class="cx"> [<__main__.Address object at 0x713b30>, <__main__.User object at 0x713630>] </span><span class="cx"> </span><del>-Unlike the identity map, the `new`, `dirty`, `modified_lists`, and `deleted` lists are *not weak referencing.* This means if you abandon all references to new or modified objects within a session, *they are still present* and will be saved on the next commit operation, unless they are removed from the Session explicitly (more on that later). The `new` list may change in a future release to be weak-referencing, however for the `deleted` list, one can see that its quite natural for a an object marked as deleted to have no references in the application, yet a DELETE operation is still required. </del><ins>+Unlike the identity map, the `new`, `dirty`, `modified_lists`, and `deleted` lists are *not weak referencing.* This means if you abandon all references to new or modified objects within a session, *they are still present* and will be saved on the next flush operation, unless they are removed from the Session explicitly (more on that later). The `new` list may change in a future release to be weak-referencing, however for the `deleted` list, one can see that its quite natural for a an object marked as deleted to have no references in the application, yet a DELETE operation is still required. </ins><span class="cx"> </span><del>-#### Commit {@name=commit} </del><ins>+#### Flush {@name=flush} </ins><span class="cx"> </span><del>-This is the main gateway to what the Unit of Work does best, which is save everything ! It should be clear by now that a commit looks like: </del><ins>+This is the main gateway to what the Unit of Work does best, which is save everything ! It should be clear by now that a flush looks like: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- objectstore.get_session().commit() </del><ins>+ objectstore.get_session().flush() </ins><span class="cx"> </span><del>-It also can be called with a list of objects; in this form, the commit operation will be limited only to the objects specified in the list, as well as any child objects within `private` relationships for a delete operation: </del><ins>+It also can be called with a list of objects; in this form, the flush operation will be limited only to the objects specified in the list, as well as any child objects within `private` relationships for a delete operation: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # saves only user1 and address2. all other modified </span><span class="cx"> # objects remain present in the session. </span><del>- objectstore.get_session().commit(user1, address2) </del><ins>+ objectstore.get_session().flush(user1, address2) </ins><span class="cx"> </span><del>-This second form of commit should be used more carefully as it will not necessarily locate other dependent objects within the session, whose database representation may have foreign constraint relationships with the objects being operated upon. </del><ins>+This second form of flush should be used more carefully as it will not necessarily locate other dependent objects within the session, whose database representation may have foreign constraint relationships with the objects being operated upon. </ins><span class="cx"> </span><del>-##### What Commit is, and Isn't {@name=whatis} </del><ins>+##### What Flush is, and Isn't {@name=whatis} </ins><span class="cx"> </span><del>-The purpose of the Commit operation, as defined by the `objectstore` package, is to instruct the Unit of Work to analyze its lists of modified objects, assemble them into a dependency graph, fire off the appopriate INSERT, UPDATE, and DELETE statements via the mappers related to those objects, and to synchronize column-based object attributes that correspond directly to updated/inserted database columns. </del><ins>+The purpose of the flush operation is to instruct the Unit of Work to analyze its lists of modified objects, assemble them into a dependency graph, fire off the appopriate INSERT, UPDATE, and DELETE statements via the mappers related to those objects, and to synchronize column-based object attributes that correspond directly to updated/inserted database columns. </ins><span class="cx"> </span><del>-Its important to note that the *objectstore.get_session().commit() operation is not the same as the commit() operation on SQLEngine.* A `SQLEngine`, described in [database](rel:database), has its own `begin` and `commit` statements which deal directly with transactions opened on DBAPI connections. While the `session.commit()` makes use of these calls in order to issue its own SQL within a database transaction, it is only dealing with "committing" its own in-memory changes and only has an indirect relationship with database connection objects. - -The `session.commit()` operation also does not affect any `relation`-based object attributes, that is attributes that reference other objects or lists of other objects, in any way. A brief list of what will *not* happen includes: </del><ins>+The `session.flush()` operation also does not affect any `relation`-based object attributes, that is attributes that reference other objects or lists of other objects, in any way. A brief list of what will *not* happen includes: </ins><span class="cx"> </span><span class="cx"> * It will not append or delete any object instances to/from any list-based object attributes. Any objects that have been created or marked as deleted will be updated as such in the database, but if a newly deleted object instance is still attached to a parent object's list, the object itself will remain in that list. </span><del>-* It will not set or remove any scalar references to other objects, even if the corresponding database identifier columns have been committed. </del><ins>+* It will not set or remove any scalar references to other objects, even if the corresponding database identifier columns have been flushed. </ins><span class="cx"> </span><del>-This means, if you set `address.user_id` to 5, that integer attribute will be saved, but it will not place an `Address` object in the `addresses` attribute of the corresponding `User` object. In some cases there may be a lazy-loader still attached to an object attribute which when first accesed performs a fresh load from the database and creates the appearance of this behavior, but this behavior should not be relied upon as it is specific to lazy loading and also may disappear in a future release. Similarly, if the `Address` object is marked as deleted and a commit is issued, the correct DELETE statements will be issued, but if the object instance itself is still attached to the `User`, it will remain. </del><ins>+This means, if you set `address.user_id` to 5, that integer attribute will be saved, but it will not place an `Address` object in the `addresses` attribute of the corresponding `User` object. In some cases there may be a lazy-loader still attached to an object attribute which when first accesed performs a fresh load from the database and creates the appearance of this behavior, but this behavior should not be relied upon as it is specific to lazy loading and also may disappear in a future release. Similarly, if the `Address` object is marked as deleted and a flush is issued, the correct DELETE statements will be issued, but if the object instance itself is still attached to the `User`, it will remain. </ins><span class="cx"> </span><del>-So the primary guideline for dealing with commit() is, *the developer is responsible for maintaining in-memory objects and their relationships to each other, the unit of work is responsible for maintaining the database representation of the in-memory objects.* The typical pattern is that the manipulation of objects *is* the way that changes get communicated to the unit of work, so that when the commit occurs, the objects are already in their correct in-memory representation and problems dont arise. The manipulation of identifier attributes like integer key values as well as deletes in particular are a frequent source of confusion. </del><ins>+So the primary guideline for dealing with flush() is, *the developer is responsible for maintaining in-memory objects and their relationships to each other, the unit of work is responsible for maintaining the database representation of the in-memory objects.* The typical pattern is that the manipulation of objects *is* the way that changes get communicated to the unit of work, so that when the flush occurs, the objects are already in their correct in-memory representation and problems dont arise. The manipulation of identifier attributes like integer key values as well as deletes in particular are a frequent source of confusion. </ins><span class="cx"> </span><span class="cx"> A terrific feature of SQLAlchemy which is also a supreme source of confusion is the backreference feature, described in [datamapping_relations_backreferences](rel:datamapping_relations_backreferences). This feature allows two types of objects to maintain attributes that reference each other, typically one object maintaining a list of elements of the other side, which contains a scalar reference to the list-holding object. When you append an element to the list, the element gets a "backreference" back to the object which has the list. When you attach the list-holding element to the child element, the child element gets attached to the list. *This feature has nothing to do whatsoever with the Unit of Work.*`*` It is strictly a small convenience feature intended to support the developer's manual manipulation of in-memory objects, and the backreference operation happens at the moment objects are attached or removed to/from each other, inde! pendent of any kind of database operation. It does not change the golden rule, that the developer is reponsible for maintaining in-memory object relationships. </span><span class="cx"> </span><span class="lines">@@ -226,8 +224,8 @@ </span><span class="cx"> # mark three objects to be deleted </span><span class="cx"> objectstore.get_session().delete(obj1, obj2, obj3) </span><span class="cx"> </span><del>- # commit - objectstore.get_session().commit() </del><ins>+ # flush + objectstore.get_session().flush() </ins><span class="cx"> </span><span class="cx"> When objects which contain references to other objects are deleted, the mappers for those related objects will issue UPDATE statements for those objects that should no longer contain references to the deleted object, setting foreign key identifiers to NULL. Similarly, when a mapper contains relations with the `private=True` option, DELETE statements will be issued for objects within that relationship in addition to that of the primary deleted object; this is called a *cascading delete*. </span><span class="cx"> </span><span class="lines">@@ -265,7 +263,7 @@ </span><span class="cx"> {python} </span><span class="cx"> session.expunge(obj1) </span><span class="cx"> </span><del>-Use `expunge` when youd like to remove an object altogether from memory, such as before calling `del` on it, which will prevent any "ghost" operations occuring when the session is committed. </del><ins>+Use `expunge` when youd like to remove an object altogether from memory, such as before calling `del` on it, which will prevent any "ghost" operations occuring when the session is flushed. </ins><span class="cx"> </span><span class="cx"> #### Import Instance {@name=import} </span><span class="cx"> </span><span class="lines">@@ -281,31 +279,11 @@ </span><span class="cx"> </span><span class="cx"> Note that the import_instance() function will either mark the deserialized object as the official copy in the current identity map, which includes updating its _instance_key with the current application's class instance, or it will discard it and return the corresponding object that was already present. Thats why its important to receive the return results from the method and use the result as the official object instance. </span><span class="cx"> </span><del>-#### Begin {@name=begin} - -The "scope" of the unit of work commit can be controlled further by issuing a begin(). A begin operation constructs a new UnitOfWork object and sets it as the currently used UOW. It maintains a reference to the original UnitOfWork as its "parent", and shares the same identity map of objects that have been loaded from the database within the scope of the parent UnitOfWork. However, the "new", "dirty", and "deleted" lists are empty. This has the effect that only changes that take place after the begin() operation get logged to the current UnitOfWork, and therefore those are the only changes that get commit()ted. When the commit is complete, the "begun" UnitOfWork removes itself and places the parent UnitOfWork as the current one again. -The begin() method returns a transactional object, upon which you can call commit() or rollback(). *Only this transactional object controls the transaction* - commit() upon the Session will do nothing until commit() or rollback() is called upon the transactional object. - - {python} - # modify an object - myobj1.foo = "something new" - - # begin - trans = session.begin() - - # modify another object - myobj2.lala = "something new" - - # only 'myobj2' is saved - trans.commit() - -begin/commit supports the same "nesting" behavior as the SQLEngine (note this behavior is not the original "nested" behavior), meaning that many begin() calls can be made, but only the outermost transactional object will actually perform a commit(). Similarly, calls to the commit() method on the Session, which might occur in function calls within the transaction, will not do anything; this allows an external function caller to control the scope of transactions used within the functions. - </del><span class="cx"> ### Advanced UnitOfWork Management {@name=advscope} </span><span class="cx"> </span><span class="cx"> #### Nesting UnitOfWork in a Database Transaction {@name=transactionnesting} </span><span class="cx"> </span><del>-The UOW commit operation places its INSERT/UPDATE/DELETE operations within the scope of a database transaction controlled by a SQLEngine: </del><ins>+The UOW flush operation places its INSERT/UPDATE/DELETE operations within the scope of a database transaction controlled by a SQLEngine: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> engine.begin() </span><span class="lines">@@ -314,7 +292,7 @@ </span><span class="cx"> except: </span><span class="cx"> engine.rollback() </span><span class="cx"> raise </span><del>- engine.commit() </del><ins>+ engine.flush() </ins><span class="cx"> </span><span class="cx"> If you recall from the [dbengine_transactions](rel:dbengine_transactions) section, the engine's begin()/commit() methods support reentrant behavior. This means you can nest begin and commits and only have the outermost begin/commit pair actually take effect (rollbacks however, abort the whole operation at any stage). From this it follows that the UnitOfWork commit operation can be nested within a transaction as well: </span><span class="cx"> </span><span class="lines">@@ -322,7 +300,7 @@ </span><span class="cx"> engine.begin() </span><span class="cx"> try: </span><span class="cx"> # perform custom SQL operations </span><del>- objectstore.commit() </del><ins>+ objectstore.flush() </ins><span class="cx"> # perform custom SQL operations </span><span class="cx"> except: </span><span class="cx"> engine.rollback() </span><span class="lines">@@ -347,15 +325,15 @@ </span><span class="cx"> # get the session that corresponds to an instance </span><span class="cx"> s = objectstore.get_session(x) </span><span class="cx"> </span><del>- # commit - s.commit() </del><ins>+ # flush + s.flush() </ins><span class="cx"> </span><span class="cx"> # perform a block of operations with this session set within the current scope </span><span class="cx"> objectstore.push_session(s) </span><span class="cx"> try: </span><span class="cx"> r = mapper.select_by(id=12) </span><span class="cx"> x = new MyObj() </span><del>- objectstore.commit() </del><ins>+ objectstore.flush() </ins><span class="cx"> finally: </span><span class="cx"> objectstore.pop_session() </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildtxt2mytpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/txt2myt.py (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/txt2myt.py 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/build/txt2myt.py 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -91,20 +91,30 @@ </span><span class="cx"> text = re.compile(r'^(?!<&)', re.M).sub(' ', text) </span><span class="cx"> </span><span class="cx"> sqlre = re.compile(r'{sql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S) </span><ins>+ if sqlre.search(text) is not None: + use_sliders = False + else: + use_sliders = True + </ins><span class="cx"> text = sqlre.sub(r"<&formatting.myt:poplink&>\1\n<&|formatting.myt:codepopper, link='sql'&>\2</&>\n\n", text) </span><span class="cx"> </span><span class="cx"> sqlre2 = re.compile(r'{opensql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S) </span><span class="cx"> text = sqlre2.sub(r"<&|formatting.myt:poppedcode &>\1\n\2</&>\n\n", text) </span><span class="cx"> </span><span class="cx"> pre_parent = parent[pre] </span><ins>+ opts = {} </ins><span class="cx"> if type == 'python': </span><del>- syntype = 'python' </del><ins>+ opts['syntaxtype'] = 'python' </ins><span class="cx"> else: </span><del>- syntype = None </del><ins>+ opts['syntaxtype'] = None + </ins><span class="cx"> if title is not None: </span><del>- tag = MyghtyTag(CODE_BLOCK, {'title':title, 'syntaxtype':syntype}) - else: - tag = MyghtyTag(CODE_BLOCK, {'syntaxtype':syntype}) </del><ins>+ opts['title'] = title + + if use_sliders: + opts['use_sliders'] = True + + tag = MyghtyTag(CODE_BLOCK, opts) </ins><span class="cx"> tag.text = text </span><span class="cx"> tag.tail = pre.tail </span><span class="cx"> pre_parent[index(pre_parent, pre)] = tag </span></span></pre></div> <a id="sqlalchemybranchesschemadocdocscss"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/docs.css (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/docs.css 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/doc/docs.css 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -197,6 +197,16 @@ </span><span class="cx"> line-height:1.2em; </span><span class="cx"> } </span><span class="cx"> </span><ins>+.sliding_code { + font-family:courier, serif; + font-size:12px; + background-color: #E2E2EB; + padding:2px 2px 2px 10px; + margin: 5px 5px 5px 5px; + line-height:1.2em; + overflow:auto; +} + </ins><span class="cx"> .codepop { </span><span class="cx"> font-weight:bold; </span><span class="cx"> font-family: verdana, sans-serif; </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemy__init__py"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -14,7 +14,7 @@ </span><span class="cx"> from sqlalchemy.mapping import * </span><span class="cx"> import sqlalchemy.ext.proxy </span><span class="cx"> </span><del>-from sqlalchemy.mapping.objectstore import Session </del><ins>+from sqlalchemy.mapping.objectstore import Session, get_session </ins><span class="cx"> </span><span class="cx"> create_engine = sqlalchemy.engine.create_engine </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -355,26 +355,32 @@ </span><span class="cx"> to any Session, then an error is raised (or None is returned if raiseerror=False). This behavior can be changed </span><span class="cx"> using the "threadlocal" mod, which will add an additional step to return a Session that is bound to the current </span><span class="cx"> thread.""" </span><ins>+ if obj is not None: + # does it have a hash key ? + hashkey = getattr(obj, '_sa_session_id', None) + if hashkey is not None: + # ok, return that + try: + return _sessions[hashkey] + except KeyError: + if raiseerror: + raise InvalidRequestError("Session '%s' referenced by object '%s' no longer exists" % (hashkey, repr(obj))) + else: + return None + + return _default_session(obj=obj, raiseerror=raiseerror) + +def _default_session(obj=None, raiseerror=True): </ins><span class="cx"> if obj is None: </span><span class="cx"> if raiseerror: </span><span class="cx"> raise InvalidRequestError("Thread-local Sessions are disabled by default. Use 'import sqlalchemy.mods.threadlocal' to enable.") </span><span class="cx"> else: </span><span class="cx"> return None </span><del>- # does it have a hash key ? - hashkey = getattr(obj, '_sa_session_id', None) - if hashkey is not None: - # ok, return that - try: - return _sessions[hashkey] - except KeyError: - if raiseerror: - raise InvalidRequestError("Session '%s' referenced by object '%s' no longer exists" % (hashkey, repr(obj))) - else: - return None </del><span class="cx"> else: </span><span class="cx"> if raiseerror: </span><span class="cx"> raise InvalidRequestError("Object '%s' not bound to any Session" % (repr(obj))) </span><span class="cx"> else: </span><span class="cx"> return None </span><ins>+ </ins><span class="cx"> unitofwork.get_session = get_session </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymodsthreadlocalpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py (1304 => 1305)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py 2006-04-20 18:04:16 UTC (rev 1304) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py 2006-04-20 20:38:02 UTC (rev 1305) </span><span class="lines">@@ -19,23 +19,8 @@ </span><span class="cx"> explicit Session objects when creating instances and creating queries. </span><span class="cx"> """ </span><span class="cx"> </span><del>-def get_session(obj=None, raiseerror=True): - # object-specific session ? - if obj is not None: - # does it have a hash key ? - hashkey = getattr(obj, '_sa_session_id', None) - if hashkey is not None: - # ok, return that - try: - return objectstore._sessions[hashkey] - except KeyError: - if raiseerror: - raise InvalidRequestError("Session '%s' referenced by object '%s' no longer exists" % (hashkey, repr(obj))) - else: - return None </del><ins>+get_session = objectstore.get_session </ins><span class="cx"> </span><del>- return objectstore.session_registry() - </del><span class="cx"> def begin(*obj): </span><span class="cx"> return get_session().begin(*obj) </span><span class="cx"> def commit(*obj): </span><span class="lines">@@ -95,11 +80,10 @@ </span><span class="cx"> return get_session().import_instance(instance) </span><span class="cx"> </span><span class="cx"> def install_plugin(): </span><del>- unitofwork.get_session = get_session </del><span class="cx"> mod = sys.modules[__name__] </span><del>- for name in ['get_session', 'import_instance', 'instance_key', 'has_instance', 'is_dirty', 'has_key', 'delete', 'expunge', 'expire', 'refresh', 'clear', 'flush', 'begin', 'commit']: </del><ins>+ for name in ['import_instance', 'instance_key', 'has_instance', 'is_dirty', 'has_key', 'delete', 'expunge', 'expire', 'refresh', 'clear', 'flush', 'begin', 'commit']: </ins><span class="cx"> setattr(objectstore, name, getattr(mod, name)) </span><del>- - objectstore.session_registry = util.ScopedRegistry(objectstore.Session) # Default session registry </del><ins>+ reg = util.ScopedRegistry(objectstore.Session) + objectstore._default_session = lambda *args, **kwargs: reg() </ins><span class="cx"> engine.default_strategy = 'threadlocal' </span><span class="cx"> install_plugin() </span></span></pre> </div> </div> </body> </html> |