sqlalchemy-commits Mailing List for SQLAlchemy (Page 363)
Brought to you by:
zzzeek
You can subscribe to this list here.
2006 |
Jan
|
Feb
(74) |
Mar
(167) |
Apr
(127) |
May
(190) |
Jun
(119) |
Jul
(77) |
Aug
(82) |
Sep
(84) |
Oct
(153) |
Nov
(45) |
Dec
(54) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2007 |
Jan
(109) |
Feb
(80) |
Mar
(110) |
Apr
(106) |
May
(92) |
Jun
(147) |
Jul
(288) |
Aug
(307) |
Sep
(108) |
Oct
(156) |
Nov
(147) |
Dec
(134) |
2008 |
Jan
(126) |
Feb
(91) |
Mar
(184) |
Apr
(208) |
May
(212) |
Jun
(54) |
Jul
(106) |
Aug
(80) |
Sep
(58) |
Oct
(80) |
Nov
(119) |
Dec
(220) |
2009 |
Jan
(202) |
Feb
(50) |
Mar
(70) |
Apr
(46) |
May
(80) |
Jun
(61) |
Jul
(146) |
Aug
(81) |
Sep
(71) |
Oct
(74) |
Nov
(66) |
Dec
(82) |
2010 |
Jan
(112) |
Feb
(169) |
Mar
(235) |
Apr
(77) |
May
(22) |
Jun
(31) |
Jul
(46) |
Aug
(46) |
Sep
(70) |
Oct
(36) |
Nov
(37) |
Dec
(79) |
2011 |
Jan
(46) |
Feb
(54) |
Mar
(65) |
Apr
(73) |
May
(31) |
Jun
(46) |
Jul
(40) |
Aug
(36) |
Sep
(44) |
Oct
(33) |
Nov
(19) |
Dec
(10) |
2012 |
Jan
(60) |
Feb
(37) |
Mar
(35) |
Apr
(28) |
May
(27) |
Jun
(50) |
Jul
(33) |
Aug
(88) |
Sep
(64) |
Oct
(74) |
Nov
(62) |
Dec
(41) |
2013 |
Jan
(30) |
Feb
(37) |
Mar
(39) |
Apr
(52) |
May
(40) |
Jun
(85) |
Jul
(74) |
Aug
(76) |
Sep
(26) |
Oct
(76) |
Nov
(63) |
Dec
(65) |
2014 |
Jan
(68) |
Feb
(82) |
Mar
(87) |
Apr
(24) |
May
(66) |
Jun
(34) |
Jul
(86) |
Aug
(75) |
Sep
(70) |
Oct
(41) |
Nov
(23) |
Dec
(53) |
2015 |
Jan
(40) |
Feb
(39) |
Mar
(69) |
Apr
(64) |
May
(40) |
Jun
(43) |
Jul
(20) |
Aug
(48) |
Sep
(38) |
Oct
(28) |
Nov
(34) |
Dec
(44) |
2016 |
Jan
(82) |
Feb
(49) |
Mar
(25) |
Apr
(21) |
May
(19) |
Jun
(46) |
Jul
(38) |
Aug
(21) |
Sep
(33) |
Oct
(44) |
Nov
(26) |
Dec
(10) |
2017 |
Jan
(52) |
Feb
(18) |
Mar
(61) |
Apr
(43) |
May
(57) |
Jun
(36) |
Jul
(37) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <co...@sq...> - 2006-05-04 04:06:53
|
<!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>[1386] sqlalchemy/branches/schema/lib/sqlalchemy: docs...</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1386</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 23:06:42 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>docs...</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdatamappingtxt">sqlalchemy/branches/schema/doc/build/content/datamapping.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentdocument_basemyt">sqlalchemy/branches/schema/doc/build/content/document_base.myt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginestrategiespy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyschemapy">sqlalchemy/branches/schema/lib/sqlalchemy/schema.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdatamappingtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/datamapping.txt (1385 => 1386)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-04 00:52:12 UTC (rev 1385) +++ sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-04 04:06:42 UTC (rev 1386) </span><span class="lines">@@ -5,22 +5,20 @@ </span><span class="cx"> </span><span class="cx"> Data mapping describes the process of defining *Mapper* objects, which associate table metadata with user-defined classes. </span><span class="cx"> </span><del>-The Mapper's role is to perform SQL operations upon the database, associating individual table rows with instances of those classes, and individual database columns with properties upon those instances, to transparently associate in-memory objects with a persistent database representation. </del><ins>+The `Mapper`'s role is to perform SQL operations upon the database, associating individual table rows with instances of those classes, and individual database columns with properties upon those instances, to transparently associate in-memory objects with a persistent database representation. </ins><span class="cx"> </span><del>-When a Mapper is created to associate a Table object with a class, all of the columns defined in the Table object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes. These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "flushes" the current state of objects (known as a *Unit of Work*). The `__init__()` method of the object is also decorated to communicate changes when new instances of the object are created. </del><ins>+When a `Mapper` is created to associate a `Table` object with a class, all of the columns defined in the `Table` object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes. These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "flushes" the current state of objects (known as a *Unit of Work*). </ins><span class="cx"> </span><del>-Two objects provide the primary interface for interacting with Mappers and the "unit of work" in SA 0.2, which are the `Query` object and the `Session` object. `Query` deals with selecting objects from the database, whereas `Session` provides a context for objects to be associated with as well as the ability to communicate changes on those objects back to the database. </del><ins>+Two objects provide the primary interface for interacting with Mappers and the "unit of work" in SA 0.2, which are the `Query` object and the `Session` object. `Query` deals with selecting objects from the database, whereas `Session` provides a context for loaded objects and the ability to communicate changes on those objects back to the database. </ins><span class="cx"> </span><del>-The primary method on `Query` for selecting arbitrary objects is its `select()` method, which has similar arguments to a `sqlalchemy.sql.Select` object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects. </del><ins>+The primary method on `Query` for loading objects is its `select()` method, which has similar arguments to a `sqlalchemy.sql.Select` object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects. </ins><span class="cx"> </span><span class="cx"> The three configurational elements to be defined, i.e. the `Table` metadata, the user-defined class, and the `Mapper`, are typically defined as module-level variables, and may be defined in any fashion suitable to the application, with the only requirement being that the class and table metadata are described before the mapper. For the sake of example, we will be defining these elements close together, but this should not be construed as a requirement; since SQLAlchemy is not a framework, those decisions are left to the developer or an external framework. </span><span class="cx"> </span><del>-Also, keep in mind that the examples in this section deal with explicit `Session` objects mapped directly to `Engine` objects, which represents the most explicit style of using the ORM. Options exist for how this is configured, including binding `Table` objects directly to `Engines` (described in [metadata_tables_binding](rel:metadata_tables_binding)), as well as using the "Threadlocal" plugin to provide a thread-local application-scoped `Session` object (described in [plugins_threadlocal](rel:plugins_threadlocal)). </del><ins>+Also, keep in mind that the examples in this section deal with explicit `Session` objects mapped directly to `Engine` objects, which represents the most explicit style of using the ORM. Options exist for how this is configured, including binding `Table` objects directly to `Engines` (described in [metadata_tables_binding](rel:metadata_tables_binding)), as well as using the "Threadlocal" plugin which provides various code shortcuts by using an implicit Session associated to the current thread (described in [plugins_threadlocal](rel:plugins_threadlocal)). </ins><span class="cx"> </span><span class="cx"> ### Synopsis {@name=synopsis} </span><span class="cx"> </span><del>-This is the simplest form of a full "round trip" of creating table meta data, creating a class, mapping the class to the table, getting some results, and saving changes. For each concept, the following sections will dig in deeper to the available capabilities. - </del><span class="cx"> First, the metadata/mapper configuration code: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -29,7 +27,7 @@ </span><span class="cx"> # metadata </span><span class="cx"> meta = MetaData() </span><span class="cx"> </span><del>- # table metadata </del><ins>+ # table object </ins><span class="cx"> users_table = Table('users', meta, </span><span class="cx"> Column('user_id', Integer, primary_key=True), </span><span class="cx"> Column('user_name', String(16)), </span><span class="lines">@@ -46,6 +44,7 @@ </span><span class="cx"> </span><span class="cx"> Note that no database definitions are required. Next we will define an `Engine` and connect a `Session` to it, and perform a simple select: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> # engine </span><span class="cx"> engine = create_engine("sqlite://mydb.db") </span><span class="cx"> </span><span class="lines">@@ -74,7 +73,6 @@ </span><span class="cx"> The method `session.query(class_or_mapper)` returns a `Query` object. Below is a synopsis of things you can do with `Query`: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- </del><span class="cx"> # get a query from a Session based on class: </span><span class="cx"> query = session.query(User) </span><span class="cx"> </span><span class="lines">@@ -87,7 +85,7 @@ </span><span class="cx"> </span><span class="cx"> # select_by can also combine SQL criterion with key/value properties </span><span class="cx"> result = query.select_by(users.c.user_name=='john', </span><del>- addresses.c.zip_code=='12345, street='123 green street') </del><ins>+ addresses.c.zip_code=='12345', street=='123 green street') </ins><span class="cx"> </span><span class="cx"> # get_by, which takes the same arguments as select_by </span><span class="cx"> # returns a single scalar result or None if no results </span><span class="lines">@@ -105,7 +103,7 @@ </span><span class="cx"> </span><span class="cx"> # get an object that has a composite primary key of three columns. </span><span class="cx"> # the order of the arguments matches that of the table meta data. </span><del>- myobj = query.get(27, 3, 'receipts') </del><ins>+ myobj = query.get((27, 3, 'receipts')) </ins><span class="cx"> </span><span class="cx"> # using a WHERE criterion </span><span class="cx"> result = query.select(or_(users.c.user_name == 'john', users.c.user_name=='fred')) </span><span class="lines">@@ -119,13 +117,7 @@ </span><span class="cx"> </span><span class="cx"> # using a full select object </span><span class="cx"> result = query.select(users.select(users.c.user_name=='john')) </span><del>- - # using straight text - result = query.select_text("select * from users where user_name='fred'") - - # or using a "text" object - result = query.select(text("select * from users where user_name='fred'", engine=engine)) - </del><ins>+ </ins><span class="cx"> Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause. These columns are also accessible off of the mapped class directly. When a mapper is assigned to a class, it also attaches a special property accessor `c` to the class itself, which can be used just like the table metadata to access the columns of the table: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -135,9 +127,9 @@ </span><span class="cx"> </span><span class="cx"> When objects corresponding to mapped classes are created or manipulated, all changes are logged by the `Session` object. The changes are then written to the database when an application calls `flush()`. This pattern is known as a *Unit of Work*, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The `flush()` operation batches its SQL statements into a transaction, and can also perform optimistic concurrency checks (using a version id column) to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). </span><span class="cx"> </span><del>-The Unit of Work is a powerful tool, and has some important concepts that must be understood in order to use it effectively. While this section illustrates rudimentary Unit of Work usage, it is strongly encouraged to consult the [unitofwork](rel:unitofwork) section for a full description on all its operations, including session control, deletion, and developmental guidelines. </del><ins>+The Unit of Work is a powerful tool, and has some important concepts that should be understood in order to use it effectively. See the [unitofwork](rel:unitofwork) section for a full description on all its operations. </ins><span class="cx"> </span><del>-When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes. New objects by default must be explicitly added to the `Session`, however this can be made more automatic by using [plugins_threadlocal](rel:plugins_threadlocal) or [plugins_sessioncontext](rel:plugins_sessioncontext). </del><ins>+When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes. New objects by default must be explicitly added to the `Session`, however this can be made automatic by using [plugins_threadlocal](rel:plugins_threadlocal) or [plugins_sessioncontext](rel:plugins_sessioncontext). </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> mapper(User, users_table) </span><span class="lines">@@ -176,7 +168,7 @@ </span><span class="cx"> INSERT INTO users (user_name, password) VALUES (:user_name, :password) </span><span class="cx"> {'password': 'lalalala', 'user_name': 'ed'} </span><span class="cx"> </span><del>-In the examples above, we defined a User class with basically no properties or methods. Theres no particular reason it has to be this way, the class can explicitly set up whatever properties it wants, whether or not they will be managed by the mapper. In SA 0.2 the class can specify any constructor it wants. </del><ins>+The mapped class can also specify whatever methods and/or constructor it wants: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> class User(object): </span><span class="lines">@@ -229,7 +221,7 @@ </span><span class="cx"> Column('zip', String(10)) </span><span class="cx"> ) </span><span class="cx"> </span><del>-Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a `Mapper` wants to indicate a relation of one object to another, this `ForeignKey` object is the default method by which the relationship is determined (although if you didn't define ForeignKeys, or you want to specify explicit relationship columns, that is available as well). </del><ins>+Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a `Mapper` wants to indicate a relation of one object to another, th `ForeignKey` relationships are the default method by which the relationship is determined (options also exist to describe the relationships explicitly). </ins><span class="cx"> </span><span class="cx"> So then lets define two classes, the familiar `User` class, as well as an `Address` class: </span><span class="cx"> </span><span class="lines">@@ -259,13 +251,15 @@ </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> engine = create_engine('sqlite://filename=mydb') </span><del>- sess = create_session(bind_to=engine) </del><ins>+ metadata.create_all(engine) </ins><span class="cx"> </span><ins>+ session = create_session(bind_to=engine) + </ins><span class="cx"> u = User('jane', 'hihilala') </span><span class="cx"> u.addresses.append(Address('123 anywhere street', 'big city', 'UT', '76543')) </span><span class="cx"> u.addresses.append(Address('1 Park Place', 'some other city', 'OK', '83923')) </span><span class="cx"> </span><del>- sess.save(u) </del><ins>+ session.save(u) </ins><span class="cx"> session.flush() </span><span class="cx"> {opensql}INSERT INTO users (user_name, password) VALUES (:user_name, :password) </span><span class="cx"> {'password': 'hihilala', 'user_name': 'jane'} </span><span class="lines">@@ -298,6 +292,7 @@ </span><span class="cx"> So our one address that was removed from the list, was updated to have a user_id of `None`, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the `private=True` parameter of `relation`: </span><span class="cx"> </span><span class="cx"> {python} </span><ins>+ clear_mappers() # clear mappers from the previous example </ins><span class="cx"> mapper(Address, addresses_table) </span><span class="cx"> mapper(User, users_table, properties = { </span><span class="cx"> 'addresses' : relation(Address, private=True) </span><span class="lines">@@ -419,19 +414,20 @@ </span><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><del>- {sql}user = session.query(User).get_by(user_name='jane') - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.password AS users_password, - addresses.address_id AS addresses_address_id, addresses.user_id AS addresses_user_id, - addresses.street AS addresses_street, addresses.city AS addresses_city, - addresses.state AS addresses_state, addresses.zip AS addresses_zip - FROM users LEFT OUTER JOIN addresses ON users.user_id = addresses.user_id - WHERE users.user_name = :users_user_name ORDER BY users.oid, addresses.oid </del><ins>+ {sql}users = session.query(User).select(User.c.user_name=='Jane') + SELECT users.user_name AS users_user_name, users.password AS users_password, + users.user_id AS users_user_id, addresses_4fb8.city AS addresses_4fb8_city, + addresses_4fb8.address_id AS addresses_4fb8_address_id, addresses_4fb8.user_id AS addresses_4fb8_user_id, + addresses_4fb8.zip AS addresses_4fb8_zip, addresses_4fb8.state AS addresses_4fb8_state, + addresses_4fb8.street AS addresses_4fb8_street + FROM users LEFT OUTER JOIN addresses AS addresses_4fb8 ON users.user_id = addresses_4fb8.user_id + WHERE users.user_name = :users_user_name ORDER BY users.oid, addresses_4fb8.oid </ins><span class="cx"> {'users_user_name': 'jane'} </span><span class="cx"> </span><del>- for a in user.addresses: - print repr(a) - </del><ins>+ for u in users: + print repr(u) + for a in u.addresses: + print repr(a) </ins><span class="cx"> </span><span class="cx"> Above, a pretty ambitious query is generated just by specifying that the User should be loaded with its child Addresses in one query. When the mapper processes the results, it uses an *Identity Map* to keep track of objects that were already loaded, based on their primary key identity. Through this method, the redundant rows produced by the join are organized into the distinct object instances they represent. </span><span class="cx"> </span><span class="lines">@@ -439,154 +435,168 @@ </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> {sql}users = session.query(User).select_by(street='123 Green Street') </span><del>- SELECT users.user_id AS users_user_id, - users.user_name AS users_user_name, users.password AS users_password, - addresses.address_id AS addresses_address_id, - addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, - addresses.city AS addresses_city, addresses.state AS addresses_state, - addresses.zip AS addresses_zip - FROM addresses AS addresses_417c, - users LEFT OUTER JOIN addresses ON users.user_id = addresses.user_id - WHERE addresses_417c.street = :addresses_street - AND users.user_id = addresses_417c.user_id - ORDER BY users.oid, addresses.oid </del><ins>+ + SELECT users.user_name AS users_user_name, + users.password AS users_password, users.user_id AS users_user_id, + addresses_6ca7.city AS addresses_6ca7_city, + addresses_6ca7.address_id AS addresses_6ca7_address_id, + addresses_6ca7.user_id AS addresses_6ca7_user_id, + addresses_6ca7.zip AS addresses_6ca7_zip, addresses_6ca7.state AS addresses_6ca7_state, + addresses_6ca7.street AS addresses_6ca7_street + FROM addresses, users LEFT OUTER JOIN addresses AS addresses_6ca7 ON users.user_id = addresses_6ca7.user_id + WHERE addresses.street = :addresses_street AND users.user_id = addresses.user_id ORDER BY users.oid, addresses_6ca7.oid </ins><span class="cx"> {'addresses_street': '123 Green Street'} </span><span class="cx"> </span><span class="cx"> The join implied by passing the "street" parameter is converted into an "aliasized" clause by the eager loader, so that it does not conflict with the join used to eager load the child address objects. </span><span class="cx"> </span><span class="cx"> #### Switching Lazy/Eager, No Load {@name=options} </span><span class="cx"> </span><del>-The `options` method of mapper provides an easy way to get alternate forms of a mapper from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions `eagerload()`, `lazyload()` and `noload()`: </del><ins>+The `options` method on the `Query` object provides an easy way to get alternate forms of a mapper query from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions `eagerload()`, `lazyload()` and `noload()`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # user mapper with lazy addresses </span><del>- User.mapper = mapper(User, users, properties = { - 'addresses' : relation(mapper(Address, addresses)) </del><ins>+ mapper(User, users_table, properties = { + 'addresses' : relation(mapper(Address, addresses_table)) </ins><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><del>- # make an eager loader - eagermapper = User.mapper.options(eagerload('addresses')) - u = eagermapper.select() </del><ins>+ # query object + query = session.query(User) </ins><span class="cx"> </span><del>- # make another mapper that wont load the addresses at all - plainmapper = User.mapper.options(noload('addresses')) </del><ins>+ # make an eager loading query + eagerquery = query.options(eagerload('addresses')) + u = eagerquery.select() </ins><span class="cx"> </span><ins>+ # make another query that wont load the addresses at all + plainquery = query.options(noload('addresses')) + </ins><span class="cx"> # multiple options can be specified </span><del>- mymapper = oldmapper.options(lazyload('tracker'), noload('streets'), eagerload('members')) </del><ins>+ myquery = oldquery.options(lazyload('tracker'), noload('streets'), eagerload('members')) </ins><span class="cx"> </span><span class="cx"> # to specify a relation on a relation, separate the property names by a "." </span><del>- mymapper = oldmapper.options(eagerload('orders.items')) </del><ins>+ myquery = oldquery.options(eagerload('orders.items')) </ins><span class="cx"> </span><span class="cx"> ### One to One/Many to One {@name=onetoone} </span><span class="cx"> </span><span class="cx"> The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the `relation` function can usually figure out what you want: </span><span class="cx"> </span><span class="cx"> {python} </span><ins>+ metadata = MetaData() + </ins><span class="cx"> # a table to store a user's preferences for a site </span><del>- prefs = Table('user_prefs', engine, </del><ins>+ prefs_table = Table('user_prefs', metadata, </ins><span class="cx"> Column('pref_id', Integer, primary_key = True), </span><span class="cx"> Column('stylename', String(20)), </span><span class="cx"> Column('save_password', Boolean, nullable = False), </span><span class="cx"> Column('timezone', CHAR(3), nullable = False) </span><span class="cx"> ) </span><span class="cx"> </span><del>- # user table gets 'preference_id' column added - users = Table('users', engine, </del><ins>+ # user table with a 'preference_id' column + users_table = Table('users', metadata, </ins><span class="cx"> Column('user_id', Integer, primary_key = True), </span><span class="cx"> Column('user_name', String(16), nullable = False), </span><span class="cx"> Column('password', String(20), nullable = False), </span><del>- Column('preference_id', Integer, ForeignKey("prefs.pref_id")) </del><ins>+ Column('preference_id', Integer, ForeignKey("user_prefs.pref_id")) </ins><span class="cx"> ) </span><span class="cx"> </span><del>- # class definition for preferences </del><ins>+ # engine and some test data + engine = create_engine('sqlite:///', echo=True) + metadata.create_all(engine) + engine.execute(prefs_table.insert(), dict(pref_id=1, stylename='green', save_password=1, timezone='EST')) + engine.execute(users_table.insert(), dict(user_name = 'fred', password='45nfss', preference_id=1)) + + # classes + class User(object): + def __init__(self, user_name, password): + self.user_name = user_name + self.password = password + </ins><span class="cx"> class UserPrefs(object): </span><span class="cx"> pass </span><del>- UserPrefs.mapper = mapper(UserPrefs, prefs) </del><ins>+ + mapper(UserPrefs, prefs_table) </ins><span class="cx"> </span><del>- # address mapper - Address.mapper = mapper(Address, addresses) - - # make a new mapper referencing everything. - m = mapper(User, users, properties = dict( - addresses = relation(Address.mapper, lazy=True, private=True), - preferences = relation(UserPrefs.mapper, lazy=False, private=True), </del><ins>+ mapper(User, users_table, properties = dict( + preferences = relation(UserPrefs, lazy=False, private=True), </ins><span class="cx"> )) </span><span class="cx"> </span><span class="cx"> # select </span><del>- {sql}user = m.get_by(user_name='fred') - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.password AS users_password, users.preference_id AS users_preference_id, - user_prefs.pref_id AS user_prefs_pref_id, user_prefs.stylename AS user_prefs_stylename, - user_prefs.save_password AS user_prefs_save_password, user_prefs.timezone AS user_prefs_timezone - FROM users LEFT OUTER JOIN user_prefs ON user_prefs.pref_id = users.preference_id - WHERE users.user_name = :users_user_name ORDER BY users.oid, user_prefs.oid </del><ins>+ session = create_session(bind_to=engine) + {sql}user = session.query(User).get_by(user_name='fred') + SELECT users.preference_id AS users_preference_id, users.user_name AS users_user_name, + users.password AS users_password, users.user_id AS users_user_id, + user_prefs_4eb2.timezone AS user_prefs_4eb2_timezone, user_prefs_4eb2.stylename AS user_prefs_4eb2_stylename, + user_prefs_4eb2.save_password AS user_prefs_4eb2_save_password, user_prefs_4eb2.pref_id AS user_prefs_4eb2_pref_id + FROM (SELECT users.user_id AS users_user_id FROM users WHERE users.user_name = :users_user_name ORDER BY users.oid + LIMIT 1 OFFSET 0) AS rowcount, + users LEFT OUTER JOIN user_prefs AS user_prefs_4eb2 ON user_prefs_4eb2.pref_id = users.preference_id + WHERE rowcount.users_user_id = users.user_id ORDER BY users.oid, user_prefs_4eb2.oid </ins><span class="cx"> {'users_user_name': 'fred'} </span><del>- </del><ins>+ </ins><span class="cx"> save_password = user.preferences.save_password </span><del>- </del><ins>+ </ins><span class="cx"> # modify </span><span class="cx"> user.preferences.stylename = 'bluesteel' </span><del>- {sql}user.addresses.append(Address('fr...@hi...')) - SELECT email_addresses.address_id AS email_addresses_address_id, - email_addresses.user_id AS email_addresses_user_id, - email_addresses.email_address AS email_addresses_email_address - FROM email_addresses - WHERE email_addresses.user_id = :users_user_id - ORDER BY email_addresses.oid, email_addresses.oid - {'users_user_id': 1} - - # commit - {sql}objectstore.commit() </del><ins>+ + # flush + {sql}session.flush() </ins><span class="cx"> UPDATE user_prefs SET stylename=:stylename </span><span class="cx"> WHERE user_prefs.pref_id = :pref_id </span><span class="cx"> [{'stylename': 'bluesteel', 'pref_id': 1}] </span><del>- INSERT INTO email_addresses (address_id, user_id, email_address) - VALUES (:address_id, :user_id, :email_address) - {'email_address': 'fr...@hi...', 'address_id': None, 'user_id': 1} </del><span class="cx"> </span><span class="cx"> ### Many to Many {@name=manytomany} </span><span class="cx"> </span><span class="cx"> The `relation` function handles a basic many-to-many relationship when you specify the association table: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- articles = Table('articles', engine, </del><ins>+ metadata = MetaData() + + articles_table = Table('articles', metadata, </ins><span class="cx"> Column('article_id', Integer, primary_key = True), </span><span class="cx"> Column('headline', String(150), key='headline'), </span><span class="cx"> Column('body', TEXT, key='body'), </span><span class="cx"> ) </span><span class="cx"> </span><del>- keywords = Table('keywords', engine, </del><ins>+ keywords_table = Table('keywords', metadata, </ins><span class="cx"> Column('keyword_id', Integer, primary_key = True), </span><span class="cx"> Column('keyword_name', String(50)) </span><span class="cx"> ) </span><span class="cx"> </span><del>- itemkeywords = Table('article_keywords', engine, </del><ins>+ itemkeywords_table = Table('article_keywords', metadata, </ins><span class="cx"> Column('article_id', Integer, ForeignKey("articles.article_id")), </span><span class="cx"> Column('keyword_id', Integer, ForeignKey("keywords.keyword_id")) </span><span class="cx"> ) </span><span class="cx"> </span><ins>+ engine = create_engine('sqlite:///') + metadata.create_all(engine) + </ins><span class="cx"> # class definitions </span><span class="cx"> class Keyword(object): </span><del>- def __init__(self, name = None): </del><ins>+ def __init__(self, name): </ins><span class="cx"> self.keyword_name = name </span><span class="cx"> </span><span class="cx"> class Article(object): </span><span class="cx"> pass </span><ins>+ + mapper(Keyword, keywords_table) </ins><span class="cx"> </span><span class="cx"> # define a mapper that does many-to-many on the 'itemkeywords' association </span><span class="cx"> # table </span><del>- Article.mapper = mapper(Article, articles, properties = dict( - keywords = relation(mapper(Keyword, keywords), itemkeywords, lazy=False) </del><ins>+ mapper(Article, articles_table, properties = dict( + keywords = relation(Keyword, secondary=itemkeywords_table, lazy=False) </ins><span class="cx"> ) </span><span class="cx"> ) </span><span class="cx"> </span><ins>+ session = create_session(bind_to=engine) + </ins><span class="cx"> article = Article() </span><span class="cx"> article.headline = 'a headline' </span><span class="cx"> article.body = 'this is the body' </span><span class="cx"> article.keywords.append(Keyword('politics')) </span><span class="cx"> article.keywords.append(Keyword('entertainment')) </span><del>- {sql}objectstore.commit() </del><ins>+ session.save(article) + + {sql}session.flush() </ins><span class="cx"> INSERT INTO keywords (name) VALUES (:name) </span><span class="cx"> {'name': 'politics'} </span><span class="cx"> INSERT INTO keywords (name) VALUES (:name) </span><span class="lines">@@ -597,33 +607,25 @@ </span><span class="cx"> [{'keyword_id': 1, 'article_id': 1}, {'keyword_id': 2, 'article_id': 1}] </span><span class="cx"> </span><span class="cx"> # select articles based on a keyword. select_by will handle the extra joins. </span><del>- {sql}articles = Article.mapper.select_by(keyword_name='politics') - SELECT articles.article_id AS articles_article_id, - articles.article_headline AS articles_article_headline, - articles.article_body AS articles_article_body, - keywords.keyword_id AS keywords_keyword_id, - keywords.keyword_name AS keywords_keyword_name - FROM keywords AS keywords_f008, - article_keywords AS article_keywords_dbf0, - articles LEFT OUTER JOIN article_keywords ON - articles.article_id = article_keywords.article_id - LEFT OUTER JOIN keywords ON - keywords.keyword_id = article_keywords.keyword_id - WHERE (keywords_f008.keyword_name = :keywords_keyword_name - AND articles.article_id = article_keywords_dbf0.article_id) - AND keywords_f008.keyword_id = article_keywords_dbf0.keyword_id - ORDER BY articles.oid, article_keywords.oid </del><ins>+ {sql}articles = session.query(Article).select_by(keyword_name='politics') + SELECT keywords_e2f2.keyword_id AS keywords_e2f2_keyword_id, keywords_e2f2.keyword_name AS keywords_e2f2_keyword_name, + articles.headline AS articles_headline, articles.body AS articles_body, articles.article_id AS articles_article_id + FROM keywords, article_keywords, articles + LEFT OUTER JOIN article_keywords AS article_keyword_3da2 ON articles.article_id = article_keyword_3da2.article_id + LEFT OUTER JOIN keywords AS keywords_e2f2 ON keywords_e2f2.keyword_id = article_keyword_3da2.keyword_id + WHERE (keywords.keyword_name = :keywords_keywords_name AND articles.article_id = article_keywords.article_id) + AND keywords.keyword_id = article_keywords.keyword_id ORDER BY articles.oid, article_keyword_3da2.oid </ins><span class="cx"> {'keywords_keyword_name': 'politics'} </span><span class="cx"> </span><del>- # modify </del><span class="cx"> a = articles[0] </span><del>- del a.keywords[:] </del><ins>+ + # clear out keywords with a new list + a.keywords = [] </ins><span class="cx"> a.keywords.append(Keyword('topstories')) </span><span class="cx"> a.keywords.append(Keyword('government')) </span><span class="cx"> </span><del>- # commit. individual INSERT/DELETE operations will take place only for the list - # elements that changed. - {sql}objectstore.commit() </del><ins>+ # flush + {sql}session.flush() </ins><span class="cx"> INSERT INTO keywords (name) VALUES (:name) </span><span class="cx"> {'name': 'topstories'} </span><span class="cx"> INSERT INTO keywords (name) VALUES (:name) </span><span class="lines">@@ -640,63 +642,83 @@ </span><span class="cx"> Many to Many can also be done with an association object, that adds additional information about how two items are related. This association object is set up in basically the same way as any other mapped object. However, since an association table typically has no primary key columns, you have to tell the mapper what columns will compose its "primary key", which are the two (or more) columns involved in the association. Also, the relation function needs an additional hint as to the fact that this mapped object is an association object, via the "association" argument which points to the class or mapper representing the other side of the association. </span><span class="cx"> </span><span class="cx"> {python} </span><ins>+ from sqlalchemy import * + metadata = MetaData() + + users_table = Table('users', metadata, + Column('user_id', Integer, primary_key = True), + Column('user_name', String(16), nullable = False), + ) + + articles_table = Table('articles', metadata, + Column('article_id', Integer, primary_key = True), + Column('headline', String(150), key='headline'), + Column('body', TEXT, key='body'), + ) + + keywords_table = Table('keywords', metadata, + Column('keyword_id', Integer, primary_key = True), + Column('keyword_name', String(50)) + ) + </ins><span class="cx"> # add "attached_by" column which will reference the user who attached this keyword </span><del>- itemkeywords = Table('article_keywords', engine, </del><ins>+ itemkeywords_table = Table('article_keywords', metadata, </ins><span class="cx"> Column('article_id', Integer, ForeignKey("articles.article_id")), </span><span class="cx"> Column('keyword_id', Integer, ForeignKey("keywords.keyword_id")), </span><span class="cx"> Column('attached_by', Integer, ForeignKey("users.user_id")) </span><span class="cx"> ) </span><span class="cx"> </span><del>- # define an association class </del><ins>+ engine = create_engine('sqlite:///', echo=True) + metadata.create_all(engine) + + # class definitions + class User(object): + pass + class Keyword(object): + def __init__(self, name): + self.keyword_name = name + class Article(object): + pass </ins><span class="cx"> class KeywordAssociation(object): </span><span class="cx"> pass </span><span class="cx"> </span><ins>+ mapper(User, users_table) + mapper(Keyword, keywords_table) + </ins><span class="cx"> # mapper for KeywordAssociation </span><span class="cx"> # specify "primary key" columns manually </span><del>- KeywordAssociation.mapper = mapper(KeywordAssociation, itemkeywords, - primary_key = [itemkeywords.c.article_id, itemkeywords.c.keyword_id], </del><ins>+ mapper(KeywordAssociation, itemkeywords_table, + primary_key = [itemkeywords_table.c.article_id, itemkeywords_table.c.keyword_id], </ins><span class="cx"> properties={ </span><del>- 'keyword' : relation(Keyword, lazy = False), # uses primary Keyword mapper - 'user' : relation(User, lazy = True) # uses primary User mapper </del><ins>+ 'keyword' : relation(Keyword, lazy = False), + 'user' : relation(User, lazy = False) </ins><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><del>- # mappers for Users, Keywords - User.mapper = mapper(User, users) - Keyword.mapper = mapper(Keyword, keywords) - - # define the mapper. - m = mapper(Article, articles, properties={ - 'keywords':relation(KeywordAssociation.mapper, lazy=False, association=Keyword) </del><ins>+ # Article mapper, relates to Keyword via KeywordAssociation + mapper(Article, articles_table, properties={ + 'keywords':relation(KeywordAssociation, lazy=False, association=Keyword) </ins><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><del>- # bonus step - well, we do want to load the users in one shot, - # so modify the mapper via an option. - # this returns a new mapper with the option switched on. - m2 = mapper.options(eagerload('keywords.user')) </del><ins>+ session = create_session(bind_to=engine) + # select by keyword + {sql}alist = session.query(Article).select_by(keyword_name='jacks_stories') + SELECT article_keyword_f9af.keyword_id AS article_keyword_f9af_key_b3e1, + article_keyword_f9af.attached_by AS article_keyword_f9af_att_95d4, + article_keyword_f9af.article_id AS article_keyword_f9af_art_fd49, + users_9c30.user_name AS users_9c30_user_name, users_9c30.user_id AS users_9c30_user_id, + keywords_dc54.keyword_id AS keywords_dc54_keyword_id, keywords_dc54.keyword_name AS keywords_dc54_keyword_name, + articles.headline AS articles_headline, articles.body AS articles_body, articles.article_id AS articles_article_id + FROM keywords, article_keywords, articles + LEFT OUTER JOIN article_keywords AS article_keyword_f9af ON articles.article_id = article_keyword_f9af.article_id + LEFT OUTER JOIN users AS users_9c30 ON users_9c30.user_id = article_keyword_f9af.attached_by + LEFT OUTER JOIN keywords AS keywords_dc54 ON keywords_dc54.keyword_id = article_keyword_f9af.keyword_id + WHERE (keywords.keyword_name = :keywords_keywords_name AND keywords.keyword_id = article_keywords.keyword_id) + AND articles.article_id = article_keywords.article_id + ORDER BY articles.oid, article_keyword_f9af.oid, users_9c30.oid, keywords_dc54.oid + {'keywords_keywords_name': 'jacks_stories'} </ins><span class="cx"> </span><del>- # select by keyword again - {sql}alist = m2.select_by(keyword_name='jacks_stories') - SELECT articles.article_id AS articles_article_id, - articles.article_headline AS articles_article_headline, - articles.article_body AS articles_article_body, - article_keywords.article_id AS article_keywords_article_id, - article_keywords.keyword_id AS article_keywords_keyword_id, - article_keywords.attached_by AS article_keywords_attached_by, - users.user_id AS users_user_id, users.user_name AS users_user_name, - users.password AS users_password, users.preference_id AS users_preference_id, - keywords.keyword_id AS keywords_keyword_id, keywords.name AS keywords_name - FROM article_keywords article_keywords_3a64, keywords keywords_11b7, - articles LEFT OUTER JOIN article_keywords ON articles.article_id = article_keywords.article_id - LEFT OUTER JOIN users ON users.user_id = article_keywords.attached_by - LEFT OUTER JOIN keywords ON keywords.keyword_id = article_keywords.keyword_id - WHERE keywords_11b7.keyword_id = article_keywords_3a64.keyword_id - AND article_keywords_3a64.article_id = articles.article_id - AND keywords_11b7.name = :keywords_name - ORDER BY articles.oid, article_keywords.oid, users.oid, keywords.oid - {'keywords_name': 'jacks_stories'} - </del><span class="cx"> # user is available </span><span class="cx"> for a in alist: </span><span class="cx"> for k in a.keywords: </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1385 => 1386)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-04 00:52:12 UTC (rev 1385) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-04 04:06:42 UTC (rev 1386) </span><span class="lines">@@ -37,6 +37,8 @@ </span><span class="cx"> mysql_db = create_engine('mysql://localhost/foo') </span><span class="cx"> oracle_db = create_engine('oracle://scott:tiger@dsn') </span><span class="cx"> </span><ins>+The `Engine` will create its first connection to the database when a SQL statement is executed. As concurrent statements are executed, the underlying connection pool will grow to a default size of five connections, and will allow a default "overflow" of ten. Since the `Engine` is essentially "home base" for the connection pool, it follows that you should keep a single `Engine` per database established within an application, rather than creating a new one for each connection. + </ins><span class="cx"> ### Database Engine Options {@name=options} </span><span class="cx"> </span><span class="cx"> Keyword options can also be specified to `create_engine()`, following the string URL as follows: </span><span class="lines">@@ -61,6 +63,8 @@ </span><span class="cx"> </span><span class="cx"> engine = create_engine('mysql', pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40)) </span><span class="cx"> </span><ins>+* pool_size=5 : the number of connections to keep open inside the connection pool. This is only used with `QueuePool`. +* max_overflow=10 : the number of connections to allow in "overflow", that is connections that can be opened above and beyond the initial five. this is only used with `QueuePool`. </ins><span class="cx"> * echo=False : if True, the Engine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' "echo" data member can be modified at any time to turn logging on and off. If set to the string 'debug', result rows will be printed to the standard output as well. </span><span class="cx"> * logger=None : a file-like object where logging output can be sent, if echo is set to True. This defaults to sys.stdout. </span><span class="cx"> * module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if 2 cannot be found. For Oracle, its cx_Oracle. </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentdocument_basemyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/document_base.myt (1385 => 1386)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/document_base.myt 2006-05-04 00:52:12 UTC (rev 1385) +++ sqlalchemy/branches/schema/doc/build/content/document_base.myt 2006-05-04 04:06:42 UTC (rev 1386) </span><span class="lines">@@ -12,6 +12,7 @@ </span><span class="cx"> 'adv_datamapping', </span><span class="cx"> 'types', </span><span class="cx"> 'pooling', </span><ins>+ 'plugins', </ins><span class="cx"> 'docstrings', </span><span class="cx"> ] </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginestrategiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py (1385 => 1386)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py 2006-05-04 00:52:12 UTC (rev 1385) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py 2006-05-04 04:06:42 UTC (rev 1386) </span><span class="lines">@@ -33,6 +33,8 @@ </span><span class="cx"> </span><span class="cx"> poolargs = kwargs.copy() </span><span class="cx"> poolargs['echo'] = poolargs.get('echo_pool', False) </span><ins>+ poolargs['pool_size'] = poolargs.get('pool_size', False) + poolargs['max_overflow'] = poolargs.get('max_overflow', False) </ins><span class="cx"> poolclass = getattr(module, 'poolclass', None) </span><span class="cx"> if poolclass is not None: </span><span class="cx"> poolargs.setdefault('poolclass', poolclass) </span><span class="lines">@@ -53,6 +55,8 @@ </span><span class="cx"> </span><span class="cx"> poolargs = kwargs.copy() </span><span class="cx"> poolargs['echo'] = poolargs.get('echo_pool', False) </span><ins>+ poolargs['pool_size'] = poolargs.get('pool_size', False) + poolargs['max_overflow'] = poolargs.get('max_overflow', False) </ins><span class="cx"> poolclass = getattr(module, 'poolclass', None) </span><span class="cx"> if poolclass is not None: </span><span class="cx"> poolargs.setdefault('poolclass', poolclass) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/schema.py (1385 => 1386)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-04 00:52:12 UTC (rev 1385) +++ sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-04 04:06:42 UTC (rev 1386) </span><span class="lines">@@ -611,7 +611,7 @@ </span><span class="cx"> def table_iterator(self, reverse=True): </span><span class="cx"> return self._sort_tables(self.tables.values(), reverse=reverse) </span><span class="cx"> </span><del>- def create_all(self, tables=None, engine=None): </del><ins>+ def create_all(self, engine=None, tables=None): </ins><span class="cx"> if not tables: </span><span class="cx"> tables = self.tables.values() </span><span class="cx"> </span><span class="lines">@@ -627,7 +627,7 @@ </span><span class="cx"> conn.create(table) </span><span class="cx"> engine.run_callable(do) </span><span class="cx"> </span><del>- def drop_all(self, tables=None, engine=None): </del><ins>+ def drop_all(self, engine=None, tables=None): </ins><span class="cx"> if not tables: </span><span class="cx"> tables = self.tables.values() </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-04 00:52:20
|
<!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>[1385] sqlalchemy/branches/schema/doc/build/content/plugins.txt: plugins doc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1385</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 19:52:12 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>plugins doc</pre> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentpluginstxt">sqlalchemy/branches/schema/doc/build/content/plugins.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentpluginstxt"></a> <div class="addfile"><h4>Added: sqlalchemy/branches/schema/doc/build/content/plugins.txt (1384 => 1385)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/plugins.txt 2006-05-04 00:51:54 UTC (rev 1384) +++ sqlalchemy/branches/schema/doc/build/content/plugins.txt 2006-05-04 00:52:12 UTC (rev 1385) </span><span class="lines">@@ -0,0 +1,78 @@ </span><ins>+Plugins {@name=plugins} +====================== + +(SECTION UNDER CONSTRUCTION) + +SQLAlchemy has a variety of extensions and "mods" available which provide extra functionality to SA, either via explicit usage or by augmenting the core behavior. + +### threadlocal + + {python} + import sqlalchemy.mods.threadlocal + from sqlalchemy import * + + metadata = BoundMetaData('sqlite:///') + user_table = Table('users', metadata, + Column('user_id', Integer, primary_key=True), + Column('user_name', String(50), nullable=False) + ) + + class User(object): + pass + mapper(User, user_table) + + # thread local session + session = get_session() + + # "user" object is added to the session automatically + user = User() + + session.flush() + +Establishes 'threadlocal' as the default strategy for new `Engine`s, and installs a thread local Session context for the `get_session()` function. Usually this is used in combination with `BoundMetaData` or `DynamicMetaData` for `Table` objects, so that the `Session` does not need to be bound to any `Engine` explicitly. + +#### get_session() Method {@name=getsession} +#### objectstore Namespace {@name=objectstore} +#### Attaching Mappers to their Class {@name=attaching} + +A full-blown "monkeypatch" function that creates a primary mapper, attaches the mapper to the class, and also the methods `get, get_by, select, select_by, selectone, selectfirst, commit, expire, refresh, expunge` and `delete`: + + {python} + # "assign" a mapper to the User class/users table + assign_mapper(User, users) + + # methods are attached to the class for selecting + userlist = User.select_by(user_id=12) + + myuser = User.get(1) + + # mark an object as deleted for the next commit + myuser.delete() + + # flush the changes on a specific object + myotheruser.flush() + +### SessionContext + +This plugin solves many of the problems that `threadlocal` solves, but does it in a more class-localized way. + + {python} + + ctx = SessionContext() + class User(object): + __session__ = ctx + + mapper(User, users_table) + + # 'u' is automatically added to the current session of 'ctx' + u = User() + + ctx.current.flush() + +### ProxyEngine + +### SelectResults + +### LegacySession + +(this plugin probably doesnt even work right now) </ins><span class="cx">\ No newline at end of file </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-04 00:52:11
|
<!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>[1384] sqlalchemy/branches/schema/doc/build/content: dodc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1384</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 19:51:54 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>dodc</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentadv_datamappingmyt">sqlalchemy/branches/schema/doc/build/content/adv_datamapping.myt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentdatamappingtxt">sqlalchemy/branches/schema/doc/build/content/datamapping.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentmetadatatxt">sqlalchemy/branches/schema/doc/build/content/metadata.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentadv_datamappingmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/adv_datamapping.myt (1383 => 1384)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/adv_datamapping.myt 2006-05-03 19:15:39 UTC (rev 1383) +++ sqlalchemy/branches/schema/doc/build/content/adv_datamapping.myt 2006-05-04 00:51:54 UTC (rev 1384) </span><span class="lines">@@ -39,6 +39,26 @@ </span><span class="cx"> </span><span class="cx"> </&> </span><span class="cx"> </span><ins>+### Overriding Properties {@name=overriding} + +A common request is the ability to create custom class properties that override the behavior of setting/getting an attribute. Currently, the easiest way to do this in SQLAlchemy is how it would be done in any Python program; define your attribute with a different name, such as "_attribute", and use a property to get/set its value. The mapper just needs to be told of the special name: + + {python} + class MyClass(object): + def _set_email(self, email): + self._email = email + def _get_email(self, email): + return self._email + email = property(_get_email, _set_email) + + m = mapper(MyClass, mytable, properties = { + # map the '_email' attribute to the "email" column + # on the table + '_email': mytable.c.email + }) + +In a later release, SQLAlchemy will also allow _get_email and _set_email to be attached directly to the "email" property created by the mapper, and will also allow this association to occur via decorators. + </ins><span class="cx"> <&|doclib.myt:item, name="relations", description="More On Relations" &> </span><span class="cx"> <&|doclib.myt:item, name="customjoin", description="Custom Join Conditions" &> </span><span class="cx"> <p>When creating relations on a mapper, most examples so far have illustrated the mapper and relationship joining up based on the foreign keys of the tables they represent. in fact, this "automatic" inspection can be completely circumvented using the <span class="codeline">primaryjoin</span> and <span class="codeline">secondaryjoin</span> arguments to <span class="codeline">relation</span>, as in this example which creates a User object which has a relationship to all of its Addresses which are in Boston: </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentdatamappingtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/datamapping.txt (1383 => 1384)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-03 19:15:39 UTC (rev 1383) +++ sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-04 00:51:54 UTC (rev 1384) </span><span class="lines">@@ -7,24 +7,30 @@ </span><span class="cx"> </span><span class="cx"> The Mapper's role is to perform SQL operations upon the database, associating individual table rows with instances of those classes, and individual database columns with properties upon those instances, to transparently associate in-memory objects with a persistent database representation. </span><span class="cx"> </span><del>-When a Mapper is created to associate a Table object with a class, all of the columns defined in the Table object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes. These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "commits" the current transactional context (known as a *Unit of Work*). The `__init__()` method of the object is also decorated to communicate changes when new instances of the object are created. </del><ins>+When a Mapper is created to associate a Table object with a class, all of the columns defined in the Table object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes. These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "flushes" the current state of objects (known as a *Unit of Work*). The `__init__()` method of the object is also decorated to communicate changes when new instances of the object are created. </ins><span class="cx"> </span><del>-The Mapper also provides the interface by which instances of the object are loaded from the database. The primary method for this is its `select()` method, which has similar arguments to a `sqlalchemy.sql.Select` object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects. </del><ins>+Two objects provide the primary interface for interacting with Mappers and the "unit of work" in SA 0.2, which are the `Query` object and the `Session` object. `Query` deals with selecting objects from the database, whereas `Session` provides a context for objects to be associated with as well as the ability to communicate changes on those objects back to the database. </ins><span class="cx"> </span><del>-The three elements to be defined, i.e. the Table metadata, the user-defined class, and the Mapper, are typically defined as module-level variables, and may be defined in any fashion suitable to the application, with the only requirement being that the class and table metadata are described before the mapper. For the sake of example, we will be defining these elements close together, but this should not be construed as a requirement; since SQLAlchemy is not a framework, those decisions are left to the developer or an external framework. </del><ins>+The primary method on `Query` for selecting arbitrary objects is its `select()` method, which has similar arguments to a `sqlalchemy.sql.Select` object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects. </ins><span class="cx"> </span><ins>+The three configurational elements to be defined, i.e. the `Table` metadata, the user-defined class, and the `Mapper`, are typically defined as module-level variables, and may be defined in any fashion suitable to the application, with the only requirement being that the class and table metadata are described before the mapper. For the sake of example, we will be defining these elements close together, but this should not be construed as a requirement; since SQLAlchemy is not a framework, those decisions are left to the developer or an external framework. + +Also, keep in mind that the examples in this section deal with explicit `Session` objects mapped directly to `Engine` objects, which represents the most explicit style of using the ORM. Options exist for how this is configured, including binding `Table` objects directly to `Engines` (described in [metadata_tables_binding](rel:metadata_tables_binding)), as well as using the "Threadlocal" plugin to provide a thread-local application-scoped `Session` object (described in [plugins_threadlocal](rel:plugins_threadlocal)). + </ins><span class="cx"> ### Synopsis {@name=synopsis} </span><span class="cx"> </span><span class="cx"> This is the simplest form of a full "round trip" of creating table meta data, creating a class, mapping the class to the table, getting some results, and saving changes. For each concept, the following sections will dig in deeper to the available capabilities. </span><span class="cx"> </span><ins>+First, the metadata/mapper configuration code: + </ins><span class="cx"> {python} </span><span class="cx"> from sqlalchemy import * </span><span class="cx"> </span><del>- # engine - engine = create_engine("sqlite://mydb.db") - </del><ins>+ # metadata + meta = MetaData() + </ins><span class="cx"> # table metadata </span><del>- users = Table('users', engine, </del><ins>+ users_table = Table('users', meta, </ins><span class="cx"> Column('user_id', Integer, primary_key=True), </span><span class="cx"> Column('user_name', String(16)), </span><span class="cx"> Column('password', String(20)) </span><span class="lines">@@ -34,11 +40,20 @@ </span><span class="cx"> class User(object): </span><span class="cx"> pass </span><span class="cx"> </span><del>- # create a mapper - usermapper = mapper(User, users) </del><ins>+ # create a mapper and associate it with the User class. + # technically we dont really need the 'usermapper' variable. + usermapper = mapper(User, users_table) + +Note that no database definitions are required. Next we will define an `Engine` and connect a `Session` to it, and perform a simple select: + + # engine + engine = create_engine("sqlite://mydb.db") </ins><span class="cx"> </span><ins>+ # session + session = create_session(bind_to=engine) + </ins><span class="cx"> # select </span><del>- {sql}user = usermapper.select_by(user_name='fred')[0] </del><ins>+ {sql}user = session.query(User).select_by(user_name='fred')[0] </ins><span class="cx"> SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, </span><span class="cx"> users.password AS users_password </span><span class="cx"> FROM users </span><span class="lines">@@ -48,151 +63,111 @@ </span><span class="cx"> # modify </span><span class="cx"> user.user_name = 'fred jones' </span><span class="cx"> </span><del>- # commit - saves everything that changed - {sql}objectstore.commit() </del><ins>+ # flush - saves everything that changed + {sql}session.flush() </ins><span class="cx"> UPDATE users SET user_name=:user_name </span><span class="cx"> WHERE users.user_id = :user_id </span><span class="cx"> [{'user_name': 'fred jones', 'user_id': 1}] </span><del>- - -#### Attaching Mappers to their Class {@name=attaching} </del><span class="cx"> </span><del>-For convenience's sake, the Mapper can be attached as an attribute on the class itself as well: </del><ins>+### The Query Object {@name=query} </ins><span class="cx"> </span><del>- {python} - User.mapper = mapper(User, users) - - userlist = User.mapper.select_by(user_id=12) - -There is also a full-blown "monkeypatch" function that creates a primary mapper, attaches the above mapper class property, and also the methods `get, get_by, select, select_by, selectone, selectfirst, commit, expire, refresh, expunge` and `delete`: </del><ins>+The method `session.query(class_or_mapper)` returns a `Query` object. Below is a synopsis of things you can do with `Query`: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- # "assign" a mapper to the User class/users table - assign_mapper(User, users) </del><span class="cx"> </span><del>- # methods are attached to the class for selecting - userlist = User.select_by(user_id=12) </del><ins>+ # get a query from a Session based on class: + query = session.query(User) </ins><span class="cx"> </span><del>- myuser = User.get(1) </del><ins>+ # get a query from a Session given a Mapper: + query = session.query(usermapper) </ins><span class="cx"> </span><del>- # mark an object as deleted for the next commit - myuser.delete() - - # commit the changes on a specific object - myotheruser.commit() - -Other methods of associating mappers and finder methods with their corresponding classes, such as via common base classes or mixins, can be devised as well. SQLAlchemy does not aim to dictate application architecture and will always allow the broadest variety of architectural patterns, but may include more helper objects and suggested architectures in the future. - -#### Overriding Properties {@name=overriding} - -A common request is the ability to create custom class properties that override the behavior of setting/getting an attribute. Currently, the easiest way to do this in SQLAlchemy is how it would be done in any Python program; define your attribute with a different name, such as "_attribute", and use a property to get/set its value. The mapper just needs to be told of the special name: - - {python} - class MyClass(object): - def _set_email(self, email): - self._email = email - def _get_email(self, email): - return self._email - email = property(_get_email, _set_email) - - m = mapper(MyClass, mytable, properties = { - # map the '_email' attribute to the "email" column - # on the table - '_email': mytable.c.email - }) - -In a later release, SQLAlchemy will also allow _get_email and _set_email to be attached directly to the "email" property created by the mapper, and will also allow this association to occur via decorators. - -### Selecting from a Mapper {@name=selecting} - -There are a variety of ways to select from a mapper. These range from minimalist to explicit. Below is a synopsis of the these methods: - - {python} </del><span class="cx"> # select_by, using property names or column names as keys </span><span class="cx"> # the keys are grouped together by an AND operator </span><del>- result = mapper.select_by(name='john', street='123 green street') </del><ins>+ result = query.select_by(name='john', street='123 green street') </ins><span class="cx"> </span><span class="cx"> # select_by can also combine SQL criterion with key/value properties </span><del>- result = mapper.select_by(users.c.user_name=='john', </del><ins>+ result = query.select_by(users.c.user_name=='john', </ins><span class="cx"> addresses.c.zip_code=='12345, street='123 green street') </span><span class="cx"> </span><span class="cx"> # get_by, which takes the same arguments as select_by </span><span class="cx"> # returns a single scalar result or None if no results </span><del>- user = mapper.get_by(id=12) </del><ins>+ user = query.get_by(id=12) </ins><span class="cx"> </span><span class="cx"> # "dynamic" versions of select_by and get_by - everything past the </span><span class="cx"> # "select_by_" or "get_by_" is used as the key, and the function argument </span><span class="cx"> # as the value </span><del>- result = mapper.select_by_name('fred') - u = mapper.get_by_name('fred') </del><ins>+ result = query.select_by_name('fred') + u = query.get_by_name('fred') </ins><span class="cx"> </span><span class="cx"> # get an object directly from its primary key. this will bypass the SQL </span><span class="cx"> # call if the object has already been loaded </span><del>- u = mapper.get(15) </del><ins>+ u = query.get(15) </ins><span class="cx"> </span><span class="cx"> # get an object that has a composite primary key of three columns. </span><span class="cx"> # the order of the arguments matches that of the table meta data. </span><del>- myobj = mapper.get(27, 3, 'receipts') </del><ins>+ myobj = query.get(27, 3, 'receipts') </ins><span class="cx"> </span><span class="cx"> # using a WHERE criterion </span><del>- result = mapper.select(or_(users.c.user_name == 'john', users.c.user_name=='fred')) </del><ins>+ result = query.select(or_(users.c.user_name == 'john', users.c.user_name=='fred')) </ins><span class="cx"> </span><span class="cx"> # using a WHERE criterion to get a scalar </span><del>- u = mapper.selectfirst(users.c.user_name=='john') - </del><ins>+ u = query.selectfirst(users.c.user_name=='john') + </ins><span class="cx"> # selectone() is a stricter version of selectfirst() which </span><span class="cx"> # will raise an exception if there is not exactly one row </span><del>- u = mapper.selectone(users.c.user_name=='john') </del><ins>+ u = query.selectone(users.c.user_name=='john') </ins><span class="cx"> </span><span class="cx"> # using a full select object </span><del>- result = mapper.select(users.select(users.c.user_name=='john')) </del><ins>+ result = query.select(users.select(users.c.user_name=='john')) </ins><span class="cx"> </span><span class="cx"> # using straight text </span><del>- result = mapper.select_text("select * from users where user_name='fred'") - </del><ins>+ result = query.select_text("select * from users where user_name='fred'") + </ins><span class="cx"> # or using a "text" object </span><del>- result = mapper.select(text("select * from users where user_name='fred'", engine=engine)) </del><ins>+ result = query.select(text("select * from users where user_name='fred'", engine=engine)) </ins><span class="cx"> </span><span class="cx"> Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause. These columns are also accessible off of the mapped class directly. When a mapper is assigned to a class, it also attaches a special property accessor `c` to the class itself, which can be used just like the table metadata to access the columns of the table: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- User.mapper = mapper(User, users) - - userlist = User.mapper.select(User.c.user_id==12) - </del><ins>+ userlist = session.query(User).select(User.c.user_id==12) </ins><span class="cx"> </span><span class="cx"> ### Saving Objects {@name=saving} </span><span class="cx"> </span><del>-When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called `sqlalchemy.mapping.objectstore`. The changes are then written to the database when an application calls `objectstore.commit()`. This pattern is known as a *Unit of Work*, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The commit() operation uses a transaction as well, and will also perform "concurrency checking" to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). Transactional resources are used effectively in all cases; the unit of work handles all the details. </del><ins>+When objects corresponding to mapped classes are created or manipulated, all changes are logged by the `Session` object. The changes are then written to the database when an application calls `flush()`. This pattern is known as a *Unit of Work*, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The `flush()` operation batches its SQL statements into a transaction, and can also perform optimistic concurrency checks (using a version id column) to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). </ins><span class="cx"> </span><span class="cx"> The Unit of Work is a powerful tool, and has some important concepts that must be understood in order to use it effectively. While this section illustrates rudimentary Unit of Work usage, it is strongly encouraged to consult the [unitofwork](rel:unitofwork) section for a full description on all its operations, including session control, deletion, and developmental guidelines. </span><span class="cx"> </span><del>-When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its `__init__()` method is also decorated to mark new objects as "new". </del><ins>+When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes. New objects by default must be explicitly added to the `Session`, however this can be made more automatic by using [plugins_threadlocal](rel:plugins_threadlocal) or [plugins_sessioncontext](rel:plugins_sessioncontext). </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- User.mapper = mapper(User, users) - </del><ins>+ mapper(User, users_table) + </ins><span class="cx"> # create a new User </span><span class="cx"> myuser = User() </span><span class="cx"> myuser.user_name = 'jane' </span><span class="cx"> myuser.password = 'hello123' </span><del>- </del><ins>+ </ins><span class="cx"> # create another new User </span><span class="cx"> myuser2 = User() </span><span class="cx"> myuser2.user_name = 'ed' </span><span class="cx"> myuser2.password = 'lalalala' </span><del>- </del><ins>+ + # create a Session and save them + sess = create_session() + sess.save(myuser) + sess.save(myuser2) + </ins><span class="cx"> # load a third User from the database </span><del>- {sql}myuser3 = User.mapper.select(User.c.user_name=='fred')[0] </del><ins>+ {sql}myuser3 = sess.query(User).select(User.c.user_name=='fred')[0] </ins><span class="cx"> SELECT users.user_id AS users_user_id, </span><span class="cx"> users.user_name AS users_user_name, users.password AS users_password </span><span class="cx"> FROM users WHERE users.user_name = :users_user_name </span><span class="cx"> {'users_user_name': 'fred'} </span><del>- </del><ins>+ </ins><span class="cx"> myuser3.user_name = 'fredjones' </span><del>- </del><ins>+ </ins><span class="cx"> # save all changes </span><del>- {sql}objectstore.commit() </del><ins>+ {sql}session.flush() </ins><span class="cx"> UPDATE users SET user_name=:user_name </span><span class="cx"> WHERE users.user_id =:users_user_id </span><span class="cx"> [{'users_user_id': 1, 'user_name': 'fredjones'}] </span><span class="lines">@@ -201,11 +176,11 @@ </span><span class="cx"> INSERT INTO users (user_name, password) VALUES (:user_name, :password) </span><span class="cx"> {'password': 'lalalala', 'user_name': 'ed'} </span><span class="cx"> </span><del>-In the examples above, we defined a User class with basically no properties or methods. Theres no particular reason it has to be this way, the class can explicitly set up whatever properties it wants, whether or not they will be managed by the mapper. It can also specify a constructor, with the restriction that the constructor is able to function with no arguments being passed to it (this restriction can be lifted with some extra parameters to the mapper; more on that later): </del><ins>+In the examples above, we defined a User class with basically no properties or methods. Theres no particular reason it has to be this way, the class can explicitly set up whatever properties it wants, whether or not they will be managed by the mapper. In SA 0.2 the class can specify any constructor it wants. </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> class User(object): </span><del>- def __init__(self, user_name = None, password = None): </del><ins>+ def __init__(self, user_name, password): </ins><span class="cx"> self.user_id = None </span><span class="cx"> self.user_name = user_name </span><span class="cx"> self.password = password </span><span class="lines">@@ -214,17 +189,19 @@ </span><span class="cx"> def __repr__(self): </span><span class="cx"> return "User id %s name %s password %s" % (repr(self.user_id), </span><span class="cx"> repr(self.user_name), repr(self.password)) </span><del>- User.mapper = mapper(User, users) </del><ins>+ mapper(User, users_table) </ins><span class="cx"> </span><ins>+ sess = create_session() </ins><span class="cx"> u = User('john', 'foo') </span><del>- {sql}objectstore.commit() </del><ins>+ sess.save(u) + {sql}session.flush() </ins><span class="cx"> INSERT INTO users (user_name, password) VALUES (:user_name, :password) </span><span class="cx"> {'password': 'foo', 'user_name': 'john'} </span><span class="cx"> </span><span class="cx"> >>> u </span><span class="cx"> User id 1 name 'john' password 'foo' </span><span class="cx"> </span><del>-Recent versions of SQLAlchemy will only put modified object attributes columns into the UPDATE statements generated upon commit. This is to conserve database traffic and also to successfully interact with a "deferred" attribute, which is a mapped object attribute against the mapper's primary table that isnt loaded until referenced by the application. </del><ins>+SQLAlchemy will only put modified object attributes columns into the UPDATE statements generated upon flush. This is to conserve database traffic and also to successfully interact with a "deferred" attribute, which is a mapped object attribute against the mapper's primary table that isnt loaded until referenced by the application. </ins><span class="cx"> </span><span class="cx"> ### Defining and Using Relationships {@name=relations} </span><span class="cx"> </span><span class="lines">@@ -232,17 +209,18 @@ </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> from sqlalchemy import * </span><del>- engine = create_engine('sqlite://filename=mydb') </del><ins>+ + metadata = MetaData() </ins><span class="cx"> </span><span class="cx"> # define user table </span><del>- users = Table('users', engine, </del><ins>+ users_table = Table('users', metadata, </ins><span class="cx"> Column('user_id', Integer, primary_key=True), </span><span class="cx"> Column('user_name', String(16)), </span><span class="cx"> Column('password', String(20)) </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> # define user address table </span><del>- addresses = Table('addresses', engine, </del><ins>+ addresses_table = Table('addresses', metadata, </ins><span class="cx"> Column('address_id', Integer, primary_key=True), </span><span class="cx"> Column('user_id', Integer, ForeignKey("users.user_id")), </span><span class="cx"> Column('street', String(100)), </span><span class="lines">@@ -251,18 +229,18 @@ </span><span class="cx"> Column('zip', String(10)) </span><span class="cx"> ) </span><span class="cx"> </span><del>-Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a Mapper wants to indicate a relation of one object to another, this ForeignKey object is the default method by which the relationship is determined (although if you didn't define ForeignKeys, or you want to specify explicit relationship columns, that is available as well). </del><ins>+Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a `Mapper` wants to indicate a relation of one object to another, this `ForeignKey` object is the default method by which the relationship is determined (although if you didn't define ForeignKeys, or you want to specify explicit relationship columns, that is available as well). </ins><span class="cx"> </span><del>-So then lets define two classes, the familiar User class, as well as an Address class: </del><ins>+So then lets define two classes, the familiar `User` class, as well as an `Address` class: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> class User(object): </span><del>- def __init__(self, user_name = None, password = None): </del><ins>+ def __init__(self, user_name, password): </ins><span class="cx"> self.user_name = user_name </span><span class="cx"> self.password = password </span><span class="cx"> </span><span class="cx"> class Address(object): </span><del>- def __init__(self, street=None, city=None, state=None, zip=None): </del><ins>+ def __init__(self, street, city, state, zip): </ins><span class="cx"> self.street = street </span><span class="cx"> self.city = city </span><span class="cx"> self.state = state </span><span class="lines">@@ -271,19 +249,24 @@ </span><span class="cx"> And then a Mapper that will define a relationship of the User and the Address classes to each other as well as their table metadata. We will add an additional mapper keyword argument `properties` which is a dictionary relating the name of an object property to a database relationship, in this case a `relation` object against a newly defined mapper for the Address class: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- User.mapper = mapper(User, users, properties = { - 'addresses' : relation(mapper(Address, addresses)) - } - ) </del><ins>+ mapper(Address, addresses_table) + mapper(User, users_table, properties = { + 'addresses' : relation(Address) + } + ) </ins><span class="cx"> </span><span class="cx"> Lets do some operations with these classes and see what happens: </span><span class="cx"> </span><span class="cx"> {python} </span><ins>+ engine = create_engine('sqlite://filename=mydb') + sess = create_session(bind_to=engine) + </ins><span class="cx"> u = User('jane', 'hihilala') </span><span class="cx"> u.addresses.append(Address('123 anywhere street', 'big city', 'UT', '76543')) </span><span class="cx"> u.addresses.append(Address('1 Park Place', 'some other city', 'OK', '83923')) </span><span class="cx"> </span><del>- objectstore.commit() </del><ins>+ sess.save(u) + session.flush() </ins><span class="cx"> {opensql}INSERT INTO users (user_name, password) VALUES (:user_name, :password) </span><span class="cx"> {'password': 'hihilala', 'user_name': 'jane'} </span><span class="cx"> INSERT INTO addresses (user_id, street, city, state, zip) VALUES (:user_id, :street, :city, :state, :zip) </span><span class="lines">@@ -291,7 +274,7 @@ </span><span class="cx"> INSERT INTO addresses (user_id, street, city, state, zip) VALUES (:user_id, :street, :city, :state, :zip) </span><span class="cx"> {'city': 'some other city', 'state': 'OK', 'street': '1 Park Place', 'user_id':1, 'zip': '83923'} </span><span class="cx"> </span><del>-A lot just happened there! The Mapper object figured out how to relate rows in the addresses table to the users table, and also upon commit had to determine the proper order in which to insert rows. After the insert, all the User and Address objects have all their new primary and foreign keys populated. </del><ins>+A lot just happened there! The Mapper object figured out how to relate rows in the addresses table to the users table, and also upon flush had to determine the proper order in which to insert rows. After the insert, all the User and Address objects have all their new primary and foreign keys populated. </ins><span class="cx"> </span><span class="cx"> Also notice that when we created a Mapper on the User class which defined an 'addresses' relation, the newly created User instance magically had an "addresses" attribute which behaved like a list. This list is in reality a property accessor function, which returns an instance of `sqlalchemy.util.HistoryArraySet`, which fulfills the full set of Python list accessors, but maintains a *unique* set of objects (based on their in-memory identity), and also tracks additions and deletions to the list: </span><span class="cx"> </span><span class="lines">@@ -299,7 +282,7 @@ </span><span class="cx"> del u.addresses[1] </span><span class="cx"> u.addresses.append(Address('27 New Place', 'Houston', 'TX', '34839')) </span><span class="cx"> </span><del>- objectstore.commit() </del><ins>+ session.flush() </ins><span class="cx"> </span><span class="cx"> {opensql}UPDATE addresses SET user_id=:user_id </span><span class="cx"> WHERE addresses.address_id = :addresses_address_id </span><span class="lines">@@ -307,36 +290,42 @@ </span><span class="cx"> INSERT INTO addresses (user_id, street, city, state, zip) </span><span class="cx"> VALUES (:user_id, :street, :city, :state, :zip) </span><span class="cx"> {'city': 'Houston', 'state': 'TX', 'street': '27 New Place', 'user_id': 1, 'zip': '34839'} </span><del>- </del><ins>+ +Note that when creating a relation with the `relation()` function, the target can either be a class, in which case the primary mapper for that class is used as the target, or a `Mapper` instance itself, as returned by the `mapper()` function. + </ins><span class="cx"> #### Useful Feature: Private Relations {@name=private} </span><span class="cx"> </span><span class="cx"> So our one address that was removed from the list, was updated to have a user_id of `None`, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the `private=True` parameter of `relation`: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- User.mapper = mapper(User, users, properties = { - 'addresses' : relation(mapper(Address, addresses), private=True) - } - ) </del><ins>+ mapper(Address, addresses_table) + mapper(User, users_table, properties = { + 'addresses' : relation(Address, private=True) + } + ) + </ins><span class="cx"> del u.addresses[1] </span><span class="cx"> u.addresses.append(Address('27 New Place', 'Houston', 'TX', '34839')) </span><span class="cx"> </span><del>- objectstore.commit() </del><ins>+ session.flush() </ins><span class="cx"> {opensql}INSERT INTO addresses (user_id, street, city, state, zip) </span><span class="cx"> VALUES (:user_id, :street, :city, :state, :zip) </span><span class="cx"> {'city': 'Houston', 'state': 'TX', 'street': '27 New Place', 'user_id': 1, 'zip': '34839'} </span><span class="cx"> DELETE FROM addresses WHERE addresses.address_id = :address_id </span><span class="cx"> [{'address_id': 2}] </span><span class="cx"> </span><del>-In this case, with the private flag set, the element that was removed from the addresses list was also removed from the database. By specifying the `private` flag on a relation, it is indicated to the Mapper that these related objects exist only as children of the parent object, otherwise should be deleted. </del><ins>+In this case, with the private flag set, the element that was removed from the addresses list was also removed from the database. The "private" flag indicates that the Address object is a *lifecycle object* of User. </ins><span class="cx"> </span><ins>+`private` is also a synonym for a more configurable set of rules called **cascade rules**. Cascading is described in [session_cascade](rel:session_cascade). + </ins><span class="cx"> #### Useful Feature: Backreferences {@name=backreferences} </span><span class="cx"> </span><span class="cx"> By creating relations with the `backref` keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, even without any database queries being executed. Below, the User mapper is created with an "addresses" property, and the corresponding Address mapper receives a "backreference" to the User object via the property name "user": </span><span class="cx"> </span><span class="cx"> {python} </span><del>- Address.mapper = mapper(Address, addresses) - User.mapper = mapper(User, users, properties = { - 'addresses' : relation(Address.mapper, backref='user') </del><ins>+ Address = mapper(Address, addresses_table) + User = mapper(User, users_table, properties = { + 'addresses' : relation(Address, backref='user') </ins><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><span class="lines">@@ -359,48 +348,28 @@ </span><span class="cx"> The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property is placed on the child mapper. The default arguments to this property can be overridden using the `backref()` function: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- Address.mapper = mapper(Address, addresses) </del><ins>+ mapper(Address, addresseses) </ins><span class="cx"> </span><del>- User.mapper = mapper(User, users, properties = { - 'addresses' : relation(Address.mapper, - backref=backref('user', lazy=False, private=True)) - } - ) </del><ins>+ mapper(User, users, properties = { + 'addresses' : relation(Address, + backref=backref('user', lazy=False, private=True) + ) + } + ) </ins><span class="cx"> </span><del>-#### Creating Relationships Automatically with cascade_mappers {@name=cascade} - -The mapper package has a helper function `cascade_mappers()` which can simplify the task of linking several mappers together. Given a list of classes and/or mappers, it identifies the foreign key relationships between the given mappers or corresponding class mappers, and creates relation() objects representing those relationships, including a backreference. Attempts to find the "secondary" table in a many-to-many relationship as well. The names of the relations are a lowercase version of the related class. In the case of one-to-many or many-to-many, the name is "pluralized", which currently is based on the English language (i.e. an 's' or 'es' added to it): - - {python} - # create two mappers. the 'users' and 'addresses' tables have a foreign key - # relationship - mapper1 = mapper(User, users) - mapper2 = mapper(Address, addresses) - - # cascade the two mappers together (can also specify User, Address as the arguments) - cascade_mappers(mapper1, mapper2) - - # two new object instances - u = User('user1') - a = Address('test') - - # "addresses" and "user" property are automatically added - u.addresses.append(a) - print a.user - </del><span class="cx"> #### Selecting from Relationships: Lazy Load {@name=lazyload} </span><span class="cx"> </span><span class="cx"> We've seen how the `relation` specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a *Lazy Loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent. </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # define a mapper </span><del>- User.mapper = mapper(User, users, properties = { - 'addresses' : relation(mapper(Address, addresses), private=True) - }) </del><ins>+ mapper(User, users_table, properties = { + 'addresses' : relation(mapper(Address, addresses_table), private=True) + }) </ins><span class="cx"> </span><span class="cx"> # select users where username is 'jane', get the first element of the list </span><span class="cx"> # this will incur a load operation for the parent table </span><del>- {sql}user = User.mapper.select(user_name='jane')[0] </del><ins>+ {sql}user = session.query(User).select(user_name='jane')[0] </ins><span class="cx"> SELECT users.user_id AS users_user_id, </span><span class="cx"> users.user_name AS users_user_name, users.password AS users_password </span><span class="cx"> FROM users WHERE users.user_name = :users_user_name ORDER BY users.oid </span><span class="lines">@@ -419,10 +388,10 @@ </span><span class="cx"> </span><span class="cx"> ##### Useful Feature: Creating Joins via select_by {@name=relselectby} </span><span class="cx"> </span><del>-In mappers that have relationships, the `select_by` method and its cousins include special functionality that can be used to create joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: </del><ins>+For mappers that have relationships, the `select_by` method of the Query object and its cousins include special functionality that can be used to create joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- {sql}l = User.mapper.select_by(street='123 Green Street') </del><ins>+ {sql}l = session.query(User).select_by(street='123 Green Street') </ins><span class="cx"> SELECT users.user_id AS users_user_id, </span><span class="cx"> users.user_name AS users_user_name, users.password AS users_password </span><span class="cx"> FROM users, addresses </span><span class="lines">@@ -434,41 +403,23 @@ </span><span class="cx"> The above example is shorthand for: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- l = User.mapper.select(and_( </del><ins>+ l = session.query(User).select(and_( </ins><span class="cx"> Address.c.user_id==User.c.user_id, </span><span class="cx"> Address.c.street=='123 Green Street') </span><span class="cx"> ) </span><del>- -##### How to Refresh the List? {@name=refreshing} </del><span class="cx"> </span><del>-Once the child list of Address objects is loaded, it is done loading for the lifetime of the object instance. Changes to the list will not be interfered with by subsequent loads, and upon commit those changes will be saved. Similarly, if a new User object is created and child Address objects added, a subsequent select operation which happens to touch upon that User instance, will also not affect the child list, since it is already loaded. - -The issue of when the mapper actually gets brand new objects from the database versus when it assumes the in-memory version is fine the way it is, is a subject of *transactional scope*. Described in more detail in the Unit of Work section, for now it should be noted that the total storage of all newly created and selected objects, *within the scope of the current thread*, can be reset via releasing or otherwise disregarding all current object instances, and calling: - - {python} - objectstore.clear() - -This operation will clear out all currently mapped object instances, and subsequent select statements will load fresh copies from the databse. - -To operate upon a single object, just use the `remove` function: - - {python} - # (this function coming soon) - objectstore.remove(myobject) - - </del><span class="cx"> #### Selecting from Relationships: Eager Load {@name=eagerload} </span><span class="cx"> </span><span class="cx"> With just a single parameter "lazy=False" specified to the relation object, the parent and child SQL queries can be joined together. </span><span class="cx"> </span><span class="cx"> {python} </span><del>- Address.mapper = mapper(Address, addresses) - User.mapper = mapper(User, users, properties = { - 'addresses' : relation(Address.mapper, lazy=False) - } - ) </del><ins>+ mapper(Address, addresses_table) + mapper(User, users_table, properties = { + 'addresses' : relation(Address, lazy=False) + } + ) </ins><span class="cx"> </span><del>- {sql}user = User.mapper.get_by(user_name='jane') </del><ins>+ {sql}user = session.query(User).get_by(user_name='jane') </ins><span class="cx"> SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, </span><span class="cx"> users.password AS users_password, </span><span class="cx"> addresses.address_id AS addresses_address_id, addresses.user_id AS addresses_user_id, </span><span class="lines">@@ -487,7 +438,7 @@ </span><span class="cx"> The generation of this query is also immune to the effects of additional joins being specified in the original query. To use our select_by example above, joining against the "addresses" table to locate users with a certain street results in this behavior: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- {sql}users = User.mapper.select_by(street='123 Green Street') </del><ins>+ {sql}users = session.query(User).select_by(street='123 Green Street') </ins><span class="cx"> SELECT users.user_id AS users_user_id, </span><span class="cx"> users.user_name AS users_user_name, users.password AS users_password, </span><span class="cx"> addresses.address_id AS addresses_address_id, </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentmetadatatxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/metadata.txt (1383 => 1384)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-03 19:15:39 UTC (rev 1383) +++ sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-04 00:51:54 UTC (rev 1384) </span><span class="lines">@@ -93,7 +93,7 @@ </span><span class="cx"> # get the table related by a foreign key </span><span class="cx"> fcolumn = employees.c.employee_dept.foreign_key.column.table </span><span class="cx"> </span><del>-#### Binding MetaData to an Engine </del><ins>+#### Binding MetaData to an Engine {@name=binding} </ins><span class="cx"> </span><span class="cx"> A MetaData object can be associated with one or more Engine instances. This allows the MetaData and the elements within it to perform operations automatically, using the connection resources of that Engine. This includes being able to "reflect" the columns of tables, as well as to perform create and drop operations without needing to pass an `Engine` or `Connection` around. It also allows SQL constructs to be created which know how to execute themselves (called "implicit execution"). </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 19:15:59
|
<!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>[1383] sqlalchemy/branches/schema/doc: docs...</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1383</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 14:15:39 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>docs...</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentsqlconstructiontxt">sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt</a></li> <li><a href="#sqlalchemybranchesschemadocdocscss">sqlalchemy/branches/schema/doc/docs.css</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1382 => 1383)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-03 17:53:04 UTC (rev 1382) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-03 19:15:39 UTC (rev 1383) </span><span class="lines">@@ -78,7 +78,7 @@ </span><span class="cx"> {python title="Explicit Connection"} </span><span class="cx"> engine = create_engine('sqlite:///:memory:') </span><span class="cx"> connection = engine.connect() </span><del>- result = connection.execute("select * from mytable where col1=:col1", {'col1':5}) </del><ins>+ result = connection.execute("select * from mytable where col1=:col1", col1=5) </ins><span class="cx"> for row in result: </span><span class="cx"> print row['col1'], row['col2'] </span><span class="cx"> connection.close() </span><span class="lines">@@ -87,7 +87,7 @@ </span><span class="cx"> </span><span class="cx"> {python title="Implicit Connection"} </span><span class="cx"> engine = create_engine('sqlite:///:memory:') </span><del>- result = engine.execute("select * from mytable where col1=:col1", {'col1':5}) </del><ins>+ result = engine.execute("select * from mytable where col1=:col1", col1=5) </ins><span class="cx"> for row in result: </span><span class="cx"> print row['col1'], row['col2'] </span><span class="cx"> result.close() </span><span class="lines">@@ -181,7 +181,7 @@ </span><span class="cx"> trans = connection.begin() </span><span class="cx"> try: </span><span class="cx"> r1 = connection.execute(table1.select()) </span><del>- connection.execute(table1.insert().execute(col1=7, col2='this is some data)) </del><ins>+ connection.execute(table1.insert(), col1=7, col2='this is some data') </ins><span class="cx"> trans.commit() </span><span class="cx"> except: </span><span class="cx"> trans.rollback() </span><span class="lines">@@ -205,7 +205,7 @@ </span><span class="cx"> trans.begin() </span><span class="cx"> try: </span><span class="cx"> connection.execute("insert into mytable values ('bat', 'lala')") </span><del>- connection.execute(mytable.insert(), dict(col1='bat', col2='lala')) </del><ins>+ connection.execute(mytable.insert(), col1='bat', col2='lala') </ins><span class="cx"> trans.commit() # transaction is not committed yet </span><span class="cx"> except: </span><span class="cx"> trans.rollback() # this rolls back the transaction unconditionally </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentsqlconstructiontxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt (1382 => 1383)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt 2006-05-03 17:53:04 UTC (rev 1382) +++ sqlalchemy/branches/schema/doc/build/content/sqlconstruction.txt 2006-05-03 19:15:39 UTC (rev 1383) </span><span class="lines">@@ -3,27 +3,27 @@ </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 class="cx"> </span><del>-Once you have used the `sqlalchemy.schema` module to construct your tables and/or reflect them from the database, performing SQL queries using those table meta data objects is done via the `sqlalchemy.sql` package. This package defines a large set of classes, each of which represents a particular kind of lexical construct within a SQL query; all are descendants of the common base class `sqlalchemy.sql.ClauseElement`. A full query is represented via a structure of ClauseElements. A set of reasonably intuitive creation functions is provided by the `sqlalchemy.sql` package to create these structures; these functions are described in the rest of this section. </del><ins>+Once you have used the `sqlalchemy.schema` module to construct your tables and/or reflect them from the database, performing SQL queries using those table meta data objects is done via the `sqlalchemy.sql` package. This package defines a large set of classes, each of which represents a particular kind of lexical construct within a SQL query; all are descendants of the common base class `sqlalchemy.sql.ClauseElement`. A full query is represented via a structure of `ClauseElement`s. A set of reasonably intuitive creation functions is provided by the `sqlalchemy.sql` package to create these structures; these functions are described in the rest of this section. </ins><span class="cx"> </span><del>-To execute a query, you create its structure, then call the resulting structure's `execute()` method, which returns a cursor-like object (more on that later). The same clause structure can be used repeatedly. A ClauseElement is compiled into a string representation by an underlying SQLEngine object, which is located by searching through the clause's child items for a Table object, which provides a reference to its SQLEngine. - </del><ins>+Executing a `ClauseElement` structure can be performed in two general ways. You can use an `Engine` or a `Connection` object's `execute()` method to which you pass the query structure; this is known as **explicit style**. Or, if the `ClauseElement` structure is built upon Table metadata which is bound to an `Engine` directly, you can simply call `execute()` on the structure itself, known as **implicit style**. In both cases, the execution returns a cursor-like object (more on that later). The same clause structure can be executed repeatedly. The `ClauseElement` is compiled into a string representation by an underlying `Compiler` object which is associated with the `Engine` via its `Dialect`. </ins><span class="cx"> </span><del>-The examples below all include a dump of the generated SQL corresponding to the query object, as well as a dump of the statement's bind parameters. In all cases, bind parameters are named parameters using the colon format (i.e. ':name'). A named parameter scheme, either ':name' or '%(name)s', is used with all databases, including those that use positional schemes. For those, the named-parameter statement and its bind values are converted to the proper list-based format right before execution. Therefore a SQLAlchemy application that uses ClauseElements can standardize on named parameters for all databases. </del><ins>+The examples below all include a dump of the generated SQL corresponding to the query object, as well as a dump of the statement's bind parameters. In all cases, bind parameters are shown as named parameters using the colon format (i.e. ':name'). When the statement is compiled into a database-specific version, the named-parameter statement and its bind values are converted to the proper paramstyle for that database automatically. </ins><span class="cx"> </span><del>-For this section, we will assume the following tables: </del><ins>+For this section, we will mostly use the implcit style of execution, meaning the `Table` objects are associated with an instance of `BoundMetaData`, and constructed `ClauseElement` objects support self-execution. We will also assume the following configuration: </ins><span class="cx"> </span><del>- {python}from sqlalchemy import * - db = create_engine('sqlite://filename=mydb', echo=True) </del><ins>+ {python} + from sqlalchemy import * + metadata = BoundMetaData('sqlite://filename=mydb', strategy='threadlocal', echo=True) </ins><span class="cx"> </span><span class="cx"> # a table to store users </span><del>- users = Table('users', db, </del><ins>+ users = Table('users', metadata, </ins><span class="cx"> Column('user_id', Integer, primary_key = True), </span><span class="cx"> Column('user_name', String(40)), </span><span class="cx"> Column('password', String(80)) </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> # a table that stores mailing addresses associated with a specific user </span><del>- addresses = Table('addresses', db, </del><ins>+ addresses = Table('addresses', metadata, </ins><span class="cx"> Column('address_id', Integer, primary_key = True), </span><span class="cx"> Column('user_id', Integer, ForeignKey("users.user_id")), </span><span class="cx"> Column('street', String(100)), </span><span class="lines">@@ -33,13 +33,13 @@ </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> # a table that stores keywords </span><del>- keywords = Table('keywords', db, </del><ins>+ keywords = Table('keywords', metadata, </ins><span class="cx"> Column('keyword_id', Integer, primary_key = True), </span><span class="cx"> Column('name', VARCHAR(50)) </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> # a table that associates keywords with users </span><del>- userkeywords = Table('userkeywords', db, </del><ins>+ userkeywords = Table('userkeywords', metadata, </ins><span class="cx"> Column('user_id', INT, ForeignKey("users")), </span><span class="cx"> Column('keyword_id', INT, ForeignKey("keywords")) </span><span class="cx"> ) </span><span class="lines">@@ -48,7 +48,8 @@ </span><span class="cx"> </span><span class="cx"> A select is done by constructing a `Select` object with the proper arguments, adding any extra arguments if desired, then calling its `execute()` method. </span><span class="cx"> </span><del>- {python}from sqlalchemy import * </del><ins>+ {python title="Basic Select"} + from sqlalchemy import * </ins><span class="cx"> </span><span class="cx"> # use the select() function defined in the sql package </span><span class="cx"> s = select([users]) </span><span class="lines">@@ -57,7 +58,7 @@ </span><span class="cx"> s = users.select() </span><span class="cx"> </span><span class="cx"> # then, call execute on the Select object: </span><del>- {sql}c = s.execute() </del><ins>+ {sql}result = s.execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password FROM users </span><span class="cx"> {} </span><span class="cx"> </span><span class="lines">@@ -65,15 +66,48 @@ </span><span class="cx"> >>> str(s) </span><span class="cx"> SELECT users.user_id, users.user_name, users.password FROM users </span><span class="cx"> </span><del>-The object returned by the execute call is a `sqlalchemy.engine.ResultProxy` object, which acts much like a DBAPI `cursor` object in the context of a result set, except that the rows returned can address their columns by ordinal position, column name, or even column object: </del><ins>+#### Explicit Execution {@name=explicit} </ins><span class="cx"> </span><del>- {python}# select rows, get resulting ResultProxy object - {sql}c = users.select().execute() </del><ins>+As mentioned above, `ClauseElement` structures can also be executed with a `Connection` object explicitly: + + {python} + engine = create_engine('sqlite:///myfile.db') + conn = engine.connect() + + s = users.select() + {sql}result = conn.execute(s) </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password FROM users </span><span class="cx"> {} </span><span class="cx"> </span><ins>+ conn.close() + +#### Binding ClauseElements to Engines {@name=binding} + +For queries that don't contain any tables, `ClauseElement`s that represent a fully executeable statement support an `engine` keyword parameter which can bind the object to an `Engine`, thereby allowing implicit execution: + + {python} + # select a literal + select(["current_time"], engine=myengine).execute() + {sql}SELECT current_time + {} + + # select a function + select([func.now()], engine=db).execute() + {sql}SELECT now() + {} + +#### Getting Results {@name=resultproxy} + +The object returned by `execute()` is a `sqlalchemy.engine.ResultProxy` object, which acts much like a DBAPI `cursor` object in the context of a result set, except that the rows returned can address their columns by ordinal position, column name, or even column object: + + {python title="Using the ResultProxy"} + # select rows, get resulting ResultProxy object + {sql}result = users.select().execute() + SELECT users.user_id, users.user_name, users.password FROM users + {} + </ins><span class="cx"> # get one row </span><del>- row = c.fetchone() </del><ins>+ row = result.fetchone() </ins><span class="cx"> </span><span class="cx"> # get the 'user_id' column via integer index: </span><span class="cx"> user_id = row[0] </span><span class="lines">@@ -88,16 +122,23 @@ </span><span class="cx"> password = row.password </span><span class="cx"> </span><span class="cx"> # ResultProxy object also supports fetchall() </span><del>- rows = c.fetchall() </del><ins>+ rows = result.fetchall() </ins><span class="cx"> </span><span class="cx"> # or get the underlying DBAPI cursor object </span><del>- cursor = c.cursor </del><ins>+ cursor = result.cursor + + # close the result. If the statement was implicitly executed (i.e. without an explicit Connection), this will + # return the underlying connection resources back to the connection pool. de-referencing the result + # will also have the same effect. + # if an explicit Connection was used, then close() does nothing. + result.close() </ins><span class="cx"> </span><span class="cx"> #### Using Column Labels {@name=labels} </span><span class="cx"> </span><span class="cx"> A common need when writing statements that reference multiple tables is to create labels for columns, thereby separating columns from different tables with the same name. The Select construct supports automatic generation of column labels via the `use_labels=True` parameter: </span><span class="cx"> </span><del>- {python}{sql}c = select([users, addresses], </del><ins>+ {python title="use_labels Flag"} + {sql}c = select([users, addresses], </ins><span class="cx"> users.c.user_id==addresses.c.address_id, </span><span class="cx"> use_labels=True).execute() </span><span class="cx"> SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, </span><span class="lines">@@ -111,7 +152,8 @@ </span><span class="cx"> </span><span class="cx"> The table name part of the label is affected if you use a construct such as a table alias: </span><span class="cx"> </span><del>- {python}person = users.alias('person') </del><ins>+ {python title="use_labels with an Alias"} + person = users.alias('person') </ins><span class="cx"> {sql}c = select([person, addresses], </span><span class="cx"> person.c.user_id==addresses.c.address_id, </span><span class="cx"> use_labels=True).execute() </span><span class="lines">@@ -122,9 +164,21 @@ </span><span class="cx"> addresses.zip AS addresses_zip FROM users AS person, addresses </span><span class="cx"> WHERE person.user_id = addresses.address_id </span><span class="cx"> </span><ins>+Labels are also generated in such a way as to never go beyond 30 characters. Most databases support a limit on the length of symbols, such as Postgres, and particularly Oracle which has a rather short limit of 30: + + {python title="use_labels Generates Abbreviated Labels"} + long_named_table = users.alias('this_is_the_person_table') + {sql}c = select([person], use_labels=True).execute() + SELECT this_is_the_person_table.user_id AS this_is_the_person_table_b36c, + this_is_the_person_table.user_name AS this_is_the_person_table_f76a, + this_is_the_person_table.password AS this_is_the_person_table_1e7c + FROM users AS this_is_the_person_table + {} + </ins><span class="cx"> You can also specify custom labels on a per-column basis using the `label()` function: </span><span class="cx"> </span><del>- {python}{sql}c = select([users.c.user_id.label('id'), users.c.user_name.label('name')]).execute() </del><ins>+ {python title="label() Function on Column"} + {sql}c = select([users.c.user_id.label('id'), users.c.user_name.label('name')]).execute() </ins><span class="cx"> SELECT users.user_id AS id, users.user_name AS name </span><span class="cx"> FROM users </span><span class="cx"> {} </span><span class="lines">@@ -135,7 +189,8 @@ </span><span class="cx"> </span><span class="cx"> But in addition to selecting all the columns off a single table, any set of columns can be specified, as well as full tables, and any combination of the two: </span><span class="cx"> </span><del>- {python}# individual columns </del><ins>+ {python title="Specify Columns to Select"} + # individual columns </ins><span class="cx"> {sql}c = select([users.c.user_id, users.c.user_name]).execute() </span><span class="cx"> SELECT users.user_id, users.user_name FROM users </span><span class="cx"> {} </span><span class="lines">@@ -154,13 +209,14 @@ </span><span class="cx"> addresses.zip FROM users, addresses </span><span class="cx"> {} </span><span class="cx"> </span><del>-#### WHERE Clause {@name=whereclause} </del><ins>+### WHERE Clause {@name=whereclause} </ins><span class="cx"> </span><span class="cx"> The WHERE condition is the named keyword argument `whereclause`, or the second positional argument to the `select()` constructor and the first positional argument to the `select()` method of `Table`. </span><span class="cx"> </span><span class="cx"> WHERE conditions are constructed using column objects, literal values, and functions defined in the `sqlalchemy.sql` module. Column objects override the standard Python operators to provide clause compositional objects, which compile down to SQL operations: </span><span class="cx"> </span><del>- {python}{sql}c = users.select(users.c.user_id == 7).execute() </del><ins>+ {python title="Basic WHERE Clause"} + {sql}c = users.select(users.c.user_id == 7).execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password, </span><span class="cx"> FROM users WHERE users.user_id = :users_user_id </span><span class="cx"> {'users_user_id': 7} </span><span class="lines">@@ -169,7 +225,8 @@ </span><span class="cx"> </span><span class="cx"> More where clauses: </span><span class="cx"> </span><del>- {python}# another comparison operator </del><ins>+ {python} + # another comparison operator </ins><span class="cx"> {sql}c = select([users], users.c.user_id>7).execute() </span><span class="cx"> SELECT users.user_id, users.user_name, users.password, </span><span class="cx"> FROM users WHERE users.user_id > :users_user_id </span><span class="lines">@@ -233,7 +290,8 @@ </span><span class="cx"> </span><span class="cx"> Select statements can also generate a WHERE clause based on the parameters you give it. If a given parameter, which matches the name of a column or its "label" (the combined tablename + "_" + column name), and does not already correspond to a bind parameter in the select object, it will be added as a comparison against that column. This is a shortcut to creating a full WHERE clause: </span><span class="cx"> </span><del>- {python}# specify a match for the "user_name" column </del><ins>+ {python} + # specify a match for the "user_name" column </ins><span class="cx"> {sql}c = users.select().execute(user_name='ed') </span><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users WHERE users.user_name = :users_user_name </span><span class="lines">@@ -246,11 +304,12 @@ </span><span class="cx"> FROM users WHERE users.user_name = :users_user_name AND users.user_id = :users_user_id </span><span class="cx"> {'users_user_name': 'ed', 'users_user_id': 10} </span><span class="cx"> </span><del>-##### Operators {@name=operators} </del><ins>+#### Operators {@name=operators} </ins><span class="cx"> </span><span class="cx"> Supported column operators so far are all the numerical comparison operators, i.e. '==', '>', '>=', etc., as well as like(), startswith(), endswith(), between(), and in(). Boolean operators include not_(), and_() and or_(), which also can be used inline via '~', '&amp;', and '|'. Math operators are '+', '-', '*', '/'. Any custom operator can be specified via the op() function shown below. </span><span class="cx"> </span><del>- {python}# "like" operator </del><ins>+ {python} + # "like" operator </ins><span class="cx"> users.select(users.c.user_name.like('%ter')) </span><span class="cx"> </span><span class="cx"> # equality operator </span><span class="lines">@@ -274,21 +333,13 @@ </span><span class="cx"> # any custom operator </span><span class="cx"> select([users.c.user_name.op('||')('_category')]) </span><span class="cx"> </span><del>-#### Specifying the Engine {@name=engine} </del><span class="cx"> </span><del>-For queries that don't contain any tables, the SQLEngine can be specified to any constructed statement via the `engine` keyword parameter: - - {python}# select a literal - select(["hi"], engine=myengine) - - # select a function - select([func.now()], engine=db) - </del><span class="cx"> #### Functions {@name=functions} </span><span class="cx"> </span><span class="cx"> Functions can be specified using the `func` keyword: </span><span class="cx"> </span><del>- {python}{sql}select([func.count(users.c.user_id)]).execute() </del><ins>+ {python} + {sql}select([func.count(users.c.user_id)]).execute() </ins><span class="cx"> SELECT count(users.user_id) FROM users </span><span class="cx"> </span><span class="cx"> {sql}users.select(func.substr(users.c.user_name, 1) == 'J').execute() </span><span class="lines">@@ -298,7 +349,8 @@ </span><span class="cx"> </span><span class="cx"> Functions also are callable as standalone values: </span><span class="cx"> </span><del>- {python}# call the "now()" function </del><ins>+ {python} + # call the "now()" function </ins><span class="cx"> time = func.now(engine=myengine).scalar() </span><span class="cx"> </span><span class="cx"> # call myfunc(1,2,3) </span><span class="lines">@@ -311,7 +363,8 @@ </span><span class="cx"> </span><span class="cx"> You can drop in a literal value anywhere there isnt a column to attach to via the `literal` keyword: </span><span class="cx"> </span><del>- {python}{sql}select([literal('foo') + literal('bar'), users.c.user_name]).execute() </del><ins>+ {python} + {sql}select([literal('foo') + literal('bar'), users.c.user_name]).execute() </ins><span class="cx"> SELECT :literal + :literal_1, users.user_name </span><span class="cx"> FROM users </span><span class="cx"> {'literal_1': 'bar', 'literal': 'foo'} </span><span class="lines">@@ -323,7 +376,8 @@ </span><span class="cx"> </span><span class="cx"> Literals also take an optional `type` parameter to give literals a type. This can sometimes be significant, for example when using the "+" operator with SQLite, the String type is detected and the operator is converted to "||": </span><span class="cx"> </span><del>- {python}{sql}select([literal('foo', type=String) + 'bar'], engine=e).execute() </del><ins>+ {python} + {sql}select([literal('foo', type=String) + 'bar'], engine=e).execute() </ins><span class="cx"> SELECT ? || ? </span><span class="cx"> ['foo', 'bar'] </span><span class="cx"> </span><span class="lines">@@ -331,7 +385,8 @@ </span><span class="cx"> </span><span class="cx"> The ORDER BY clause of a select statement can be specified as individual columns to order by within an array specified via the `order_by` parameter, and optional usage of the asc() and desc() functions: </span><span class="cx"> </span><del>- {python}# straight order by </del><ins>+ {python} + # straight order by </ins><span class="cx"> {sql}c = users.select(order_by=[users.c.user_name]).execute() </span><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users ORDER BY users.user_name </span><span class="lines">@@ -349,7 +404,8 @@ </span><span class="cx"> </span><span class="cx"> These are specified as keyword arguments: </span><span class="cx"> </span><del>- {python}{sql}c = select([users.c.user_name], distinct=True).execute() </del><ins>+ {python} + {sql}c = select([users.c.user_name], distinct=True).execute() </ins><span class="cx"> SELECT DISTINCT users.user_name FROM users </span><span class="cx"> </span><span class="cx"> {sql}c = users.select(limit=10, offset=20).execute() </span><span class="lines">@@ -361,7 +417,8 @@ </span><span class="cx"> </span><span class="cx"> As some of the examples indicated above, a regular inner join can be implicitly stated, just like in a SQL expression, by just specifying the tables to be joined as well as their join conditions: </span><span class="cx"> </span><del>- {python}{sql}addresses.select(addresses.c.user_id==users.c.user_id).execute() </del><ins>+ {python} + {sql}addresses.select(addresses.c.user_id==users.c.user_id).execute() </ins><span class="cx"> SELECT addresses.address_id, addresses.user_id, addresses.street, </span><span class="cx"> addresses.city, addresses.state, addresses.zip FROM addresses, users </span><span class="cx"> WHERE addresses.user_id = users.user_id </span><span class="lines">@@ -369,7 +426,8 @@ </span><span class="cx"> </span><span class="cx"> There is also an explicit join constructor, which can be embedded into a select query via the `from_obj` parameter of the select statement: </span><span class="cx"> </span><del>- {python}{sql}addresses.select(from_obj=[ </del><ins>+ {python} + {sql}addresses.select(from_obj=[ </ins><span class="cx"> addresses.join(users, addresses.c.user_id==users.c.user_id) </span><span class="cx"> ]).execute() </span><span class="cx"> SELECT addresses.address_id, addresses.user_id, addresses.street, addresses.city, </span><span class="lines">@@ -379,7 +437,8 @@ </span><span class="cx"> </span><span class="cx"> The join constructor can also be used by itself: </span><span class="cx"> </span><del>- {python}{sql}join(users, addresses, users.c.user_id==addresses.c.user_id).select().execute() </del><ins>+ {python} + {sql}join(users, addresses, users.c.user_id==addresses.c.user_id).select().execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password, </span><span class="cx"> addresses.address_id, addresses.user_id, addresses.street, addresses.city, </span><span class="cx"> addresses.state, addresses.zip </span><span class="lines">@@ -388,7 +447,8 @@ </span><span class="cx"> </span><span class="cx"> The join criterion in a join() call is optional. If not specified, the condition will be derived from the foreign key relationships of the two tables. If no criterion can be constructed, an exception will be raised. </span><span class="cx"> </span><del>- {python}{sql}join(users, addresses).select().execute() </del><ins>+ {python} + {sql}join(users, addresses).select().execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password, </span><span class="cx"> addresses.address_id, addresses.user_id, addresses.street, addresses.city, </span><span class="cx"> addresses.state, addresses.zip </span><span class="lines">@@ -399,7 +459,8 @@ </span><span class="cx"> </span><span class="cx"> A join can be created on its own using the `join` or `outerjoin` functions, or can be created off of an existing Table or other selectable unit via the `join` or `outerjoin` methods: </span><span class="cx"> </span><del>- {python}{sql}outerjoin(users, addresses, users.c.user_id==addresses.c.address_id).select().execute() </del><ins>+ {python} + {sql}outerjoin(users, addresses, users.c.user_id==addresses.c.address_id).select().execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password, addresses.address_id, </span><span class="cx"> addresses.user_id, addresses.street, addresses.city, addresses.state, addresses.zip </span><span class="cx"> FROM users LEFT OUTER JOIN addresses ON users.user_id = addresses.address_id </span><span class="lines">@@ -420,7 +481,8 @@ </span><span class="cx"> </span><span class="cx"> Aliases are used primarily when you want to use the same table more than once as a FROM expression in a statement: </span><span class="cx"> </span><del>- {python}address_b = addresses.alias('addressb') </del><ins>+ {python} + address_b = addresses.alias('addressb') </ins><span class="cx"> {sql}# select users who have an address on Green street as well as Orange street </span><span class="cx"> users.select(and_( </span><span class="cx"> users.c.user_id==addresses.c.user_id, </span><span class="lines">@@ -440,10 +502,12 @@ </span><span class="cx"> </span><span class="cx"> SQLAlchemy allows the creation of select statements from not just Table objects, but from a whole class of objects that implement the `Selectable` interface. This includes Tables, Aliases, Joins and Selects. Therefore, if you have a Select, you can select from the Select: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> >>> s = users.select() </span><span class="cx"> >>> str(s) </span><span class="cx"> SELECT users.user_id, users.user_name, users.password FROM users </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> >>> s = s.select() </span><span class="cx"> >>> str(s) </span><span class="cx"> SELECT user_id, user_name, password </span><span class="lines">@@ -451,13 +515,15 @@ </span><span class="cx"> </span><span class="cx"> Any Select, Join, or Alias object supports the same column accessors as a Table: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> >>> s = users.select() </span><span class="cx"> >>> [c.key for c in s.columns] </span><span class="cx"> ['user_id', 'user_name', 'password'] </span><span class="cx"> </span><span class="cx"> When you use `use_labels=True` in a Select object, the label version of the column names become the keys of the accessible columns. In effect you can create your own "view objects": </span><span class="cx"> </span><del>- {python}s = select([users, addresses], users.c.user_id==addresses.c.user_id, use_labels=True) </del><ins>+ {python} + s = select([users, addresses], users.c.user_id==addresses.c.user_id, use_labels=True) </ins><span class="cx"> {sql}select([ </span><span class="cx"> s.c.users_user_name, s.c.addresses_street, s.c.addresses_zip </span><span class="cx"> ], s.c.addresses_city=='San Francisco').execute() </span><span class="lines">@@ -474,7 +540,8 @@ </span><span class="cx"> </span><span class="cx"> To specify a SELECT statement as one of the selectable units in a FROM clause, it usually should be given an alias. </span><span class="cx"> </span><del>- {python}{sql}s = users.select().alias('u') </del><ins>+ {python} + {sql}s = users.select().alias('u') </ins><span class="cx"> select([addresses, s]).execute() </span><span class="cx"> SELECT addresses.address_id, addresses.user_id, addresses.street, addresses.city, </span><span class="cx"> addresses.state, addresses.zip, u.user_id, u.user_name, u.password </span><span class="lines">@@ -484,7 +551,8 @@ </span><span class="cx"> </span><span class="cx"> Select objects can be used in a WHERE condition, in operators such as IN: </span><span class="cx"> </span><del>- {python}# select user ids for all users whos name starts with a "p" </del><ins>+ {python} + # select user ids for all users whos name starts with a "p" </ins><span class="cx"> s = select([users.c.user_id], users.c.user_name.like('p%')) </span><span class="cx"> </span><span class="cx"> # now select all addresses for those users </span><span class="lines">@@ -501,7 +569,8 @@ </span><span class="cx"> </span><span class="cx"> Subqueries can be used in the column clause of a select statement by specifying the `scalar=True` flag: </span><span class="cx"> </span><del>- {python}{sql}select([table2.c.col1, table2.c.col2, select([table1.c.col1], table1.c.col2==7, scalar=True)]) </del><ins>+ {python} + {sql}select([table2.c.col1, table2.c.col2, select([table1.c.col1], table1.c.col2==7, scalar=True)]) </ins><span class="cx"> SELECT table2.col1, table2.col2, </span><span class="cx"> (SELECT table1.col1 AS col1 FROM table1 WHERE col2=:table1_col2) </span><span class="cx"> FROM table2 </span><span class="lines">@@ -511,7 +580,8 @@ </span><span class="cx"> </span><span class="cx"> When a select object is embedded inside of another select object, and both objects reference the same table, SQLAlchemy makes the assumption that the table should be correlated from the child query to the parent query. To disable this behavior, specify the flag `correlate=False` to the Select statement. </span><span class="cx"> </span><del>- {python}# make an alias of a regular select. </del><ins>+ {python} + # make an alias of a regular select. </ins><span class="cx"> s = select([addresses.c.street], addresses.c.user_id==users.c.user_id).alias('s') </span><span class="cx"> >>> str(s) </span><span class="cx"> SELECT addresses.street FROM addresses, users </span><span class="lines">@@ -529,7 +599,8 @@ </span><span class="cx"> </span><span class="cx"> An EXISTS clause can function as a higher-scaling version of an IN clause, and is usually used in a correlated fashion: </span><span class="cx"> </span><del>- {python}# find all users who have an address on Green street: </del><ins>+ {python} + # find all users who have an address on Green street: </ins><span class="cx"> {sql}users.select( </span><span class="cx"> exists( </span><span class="cx"> [addresses.c.address_id], </span><span class="lines">@@ -549,7 +620,8 @@ </span><span class="cx"> </span><span class="cx"> Unions come in two flavors, UNION and UNION ALL, which are available via module level functions or methods off a Selectable: </span><span class="cx"> </span><del>- {python}{sql}union( </del><ins>+ {python} + {sql}union( </ins><span class="cx"> addresses.select(addresses.c.street=='123 Green Street'), </span><span class="cx"> addresses.select(addresses.c.street=='44 Park Ave.'), </span><span class="cx"> addresses.select(addresses.c.street=='3 Mill Road'), </span><span class="lines">@@ -589,28 +661,27 @@ </span><span class="cx"> </span><span class="cx"> ### Custom Bind Parameters {@name=bindparams} </span><span class="cx"> </span><del>-Throughout all these examples, SQLAlchemy is busy creating bind parameters wherever literal expressions occur. You can also specify your own bind parameters with your own names, and use the same statement repeatedly. As mentioned at the top of this section, named bind parameters are always used regardless of the type of DBAPI being used; for DBAPI's that expect positional arguments, bind parameters are converted to lists right before execution, and Pyformat strings in statements, i.e. '%(name)s', are converted to the appropriate positional style. </del><ins>+Throughout all these examples, SQLAlchemy is busy creating bind parameters wherever literal expressions occur. You can also specify your own bind parameters with your own names, and use the same statement repeatedly. The bind parameters, shown here in the "named" format, will be converted to the appropriate named or positional style according to the database implementation being used. </ins><span class="cx"> </span><del>- {python}s = users.select(users.c.user_name==bindparam('username')) </del><ins>+ {python title="Custom Bind Params"} + s = users.select(users.c.user_name==bindparam('username')) + + # execute implicitly </ins><span class="cx"> {sql}s.execute(username='fred') </span><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users WHERE users.user_name = :username </span><span class="cx"> {'username': 'fred'} </span><span class="cx"> </span><del>- {sql}s.execute(username='jane') </del><ins>+ # execute explicitly + conn = engine.connect() + {sql}conn.execute(s, username='fred') </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users WHERE users.user_name = :username </span><del>- {'username': 'jane'} </del><ins>+ {'username': 'fred'} </ins><span class="cx"> </span><del>- {sql}s.execute(username='mary') - SELECT users.user_id, users.user_name, users.password - FROM users WHERE users.user_name = :username - {'username': 'mary'} </del><span class="cx"> </span><del>-`executemany()` is also available, but that applies more to INSERT/UPDATE/DELETE, described later. </del><ins>+`executemany()` is also available by supplying multiple dictionary arguments instead of keyword arguments to the `execute()` method of `ClauseElement` or `Connection`. Examples can be found later in the sections on INSERT/UPDATE/DELETE. </ins><span class="cx"> </span><del>-The generation of bind parameters is performed specific to the engine being used. The examples in this document all show "named" parameters like those used in sqlite and oracle. Depending on the parameter type specified by the DBAPI module, the correct bind parameter scheme will be used. - </del><span class="cx"> #### Precompiling a Query {@name=precompiling} </span><span class="cx"> </span><span class="cx"> By throwing the `compile()` method onto the end of any query object, the query can be "compiled" by the SQLEngine into a `sqlalchemy.sql.Compiled` object just once, and the resulting compiled object reused, which eliminates repeated internal compilation of the SQL string: </span><span class="lines">@@ -709,24 +780,25 @@ </span><span class="cx"> </span><span class="cx"> One of the primary motivations for a programmatic SQL library is to allow the piecemeal construction of a SQL statement based on program variables. All the above examples typically show Select objects being created all at once. The Select object also includes "builder" methods to allow building up an object. The below example is a "user search" function, where users can be selected based on primary key, user name, street address, keywords, or any combination: </span><span class="cx"> </span><del>- {python}def find_users(id=None, name=None, street=None, keywords=None): - statement = users.select() - if id is not None: - statement.append_whereclause(users.c.user_id==id) - if name is not None: - statement.append_whereclause(users.c.user_name==name) - if street is not None: - # append_whereclause joins "WHERE" conditions together with AND - statement.append_whereclause(users.c.user_id==addresses.c.user_id) - statement.append_whereclause(addresses.c.street==street) - if keywords is not None: - statement.append_from( - users.join(userkeywords, users.c.user_id==userkeywords.c.user_id).join( - keywords, userkeywords.c.keyword_id==keywords.c.keyword_id)) - statement.append_whereclause(keywords.c.name.in_(keywords)) - # to avoid multiple repeats, set query to be DISTINCT: - statement.distinct=True - return statement.execute() </del><ins>+ {python} + def find_users(id=None, name=None, street=None, keywords=None): + statement = users.select() + if id is not None: + statement.append_whereclause(users.c.user_id==id) + if name is not None: + statement.append_whereclause(users.c.user_name==name) + if street is not None: + # append_whereclause joins "WHERE" conditions together with AND + statement.append_whereclause(users.c.user_id==addresses.c.user_id) + statement.append_whereclause(addresses.c.street==street) + if keywords is not None: + statement.append_from( + users.join(userkeywords, users.c.user_id==userkeywords.c.user_id).join( + keywords, userkeywords.c.keyword_id==keywords.c.keyword_id)) + statement.append_whereclause(keywords.c.name.in_(keywords)) + # to avoid multiple repeats, set query to be DISTINCT: + statement.distinct=True + return statement.execute() </ins><span class="cx"> </span><span class="cx"> {sql}find_users(id=7) </span><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="lines">@@ -753,7 +825,8 @@ </span><span class="cx"> </span><span class="cx"> The values to be populated for an INSERT or an UPDATE can be specified to the insert()/update() functions as the `values` named argument, or the query will be compiled based on the values of the parameters sent to the execute() method. </span><span class="cx"> </span><del>- {python}# basic insert </del><ins>+ {python title="Using insert()"} + # basic insert </ins><span class="cx"> {sql}users.insert().execute(user_id=1, user_name='jack', password='asdfdaf') </span><span class="cx"> INSERT INTO users (user_id, user_name, password) </span><span class="cx"> VALUES (:user_id, :user_name, :password) </span><span class="lines">@@ -799,7 +872,8 @@ </span><span class="cx"> </span><span class="cx"> Updates work a lot like INSERTS, except there is an additional WHERE clause that can be specified. </span><span class="cx"> </span><del>- {python}# change 'jack' to 'ed' </del><ins>+ {python title="Using update()"} + # change 'jack' to 'ed' </ins><span class="cx"> {sql}users.update(users.c.user_name=='jack').execute(user_name='ed') </span><span class="cx"> UPDATE users SET user_name=:user_name WHERE users.user_name = :users_user_name </span><span class="cx"> {'users_user_name': 'jack', 'user_name': 'ed'} </span></span></pre></div> <a id="sqlalchemybranchesschemadocdocscss"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/docs.css (1382 => 1383)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/docs.css 2006-05-03 17:53:04 UTC (rev 1382) +++ sqlalchemy/branches/schema/doc/docs.css 2006-05-03 19:15:39 UTC (rev 1383) </span><span class="lines">@@ -88,7 +88,7 @@ </span><span class="cx"> font-size: 12px; </span><span class="cx"> font-weight: bold; </span><span class="cx"> text-decoration:underline; </span><del>- padding:5px; </del><ins>+ padding:5px 5px 5px 0px; </ins><span class="cx"> } </span><span class="cx"> </span><span class="cx"> code { </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 17:53:18
|
<!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>[1382] sqlalchemy/branches/schema/test: more dorking around with params....</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1382</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 12:53:04 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>more dorking around with params....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginebasepy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormmapperpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py</a></li> <li><a href="#sqlalchemybranchesschematesttestbasepy">sqlalchemy/branches/schema/test/testbase.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyenginebasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py (1381 => 1382)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-03 17:31:15 UTC (rev 1381) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-03 17:53:04 UTC (rev 1382) </span><span class="lines">@@ -195,14 +195,19 @@ </span><span class="cx"> return Connection.executors[type(object).__mro__[-2]](self, object, *multiparams, **params) </span><span class="cx"> def execute_default(self, default, **kwargs): </span><span class="cx"> return default.accept_schema_visitor(self.engine.dialect.defaultrunner(self.engine, self.proxy, **kwargs)) </span><del>- def execute_text(self, statement, *multiparams, **params): - cursor = self._execute_raw(statement, self._conv_params(*multiparams, **params)) </del><ins>+ def execute_text(self, statement, parameters): + cursor = self._execute_raw(statement, parameters) </ins><span class="cx"> return ResultProxy(self.engine, self, cursor) </span><del>- def _conv_params(self, *multiparams, **params): </del><ins>+ def _params_to_listofdicts(self, *multiparams, **params): </ins><span class="cx"> if len(multiparams) == 0: </span><del>- return params </del><ins>+ return [params] </ins><span class="cx"> elif len(multiparams) == 1: </span><del>- return multiparams[0] </del><ins>+ if multiparams[0] == None: + return [{}] + elif isinstance (multiparams[0], list) or isinstance (multiparams[0], tuple): + return multiparams[0] + else: + return [multiparams[0]] </ins><span class="cx"> else: </span><span class="cx"> return multiparams </span><span class="cx"> def execute_clauseelement(self, elem, *multiparams, **params): </span><span class="lines">@@ -215,13 +220,9 @@ </span><span class="cx"> def execute_compiled(self, compiled, *multiparams, **params): </span><span class="cx"> """executes a sql.Compiled object.""" </span><span class="cx"> cursor = self.connection.cursor() </span><del>- executemany = len(multiparams) > 0 - if executemany: - parameters = [compiled.get_params(**m) for m in multiparams] - elif len(params): - parameters = compiled.get_params(**params) - else: - parameters = compiled.get_params() </del><ins>+ parameters = [compiled.get_params(**m) for m in self._params_to_listofdicts(*multiparams, **params)] + if len(parameters) == 1: + parameters = parameters[0] </ins><span class="cx"> def proxy(statement=None, parameters=None): </span><span class="cx"> if statement is None: </span><span class="cx"> return cursor </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py (1381 => 1382)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py 2006-05-03 17:31:15 UTC (rev 1381) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py 2006-05-03 17:53:04 UTC (rev 1382) </span><span class="lines">@@ -726,6 +726,7 @@ </span><span class="cx"> if self.version_id_col is not None: </span><span class="cx"> clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col.key)) </span><span class="cx"> statement = table.delete(clause) </span><ins>+ print "DELETE IS", delete </ins><span class="cx"> c = connection.execute(statement, delete) </span><span class="cx"> if c.supports_sane_rowcount() and c.rowcount != len(delete): </span><span class="cx"> raise CommitError("ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(delete))) </span></span></pre></div> <a id="sqlalchemybranchesschematesttestbasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/testbase.py (1381 => 1382)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/testbase.py 2006-05-03 17:31:15 UTC (rev 1381) +++ sqlalchemy/branches/schema/test/testbase.py 2006-05-03 17:53:04 UTC (rev 1382) </span><span class="lines">@@ -243,9 +243,10 @@ </span><span class="cx"> (query, params) = item </span><span class="cx"> if callable(params): </span><span class="cx"> params = params(ctx) </span><del>- </del><ins>+ if params is not None and isinstance(params, list) and len(params) == 1: + params = params[0] + </ins><span class="cx"> query = self.convert_statement(query) </span><del>- </del><span class="cx"> self.unittest.assert_(statement == query and (params is None or params == parameters), "Testing for query '%s' params %s, received '%s' with params %s" % (query, repr(params), statement, repr(parameters))) </span><span class="cx"> self.sql_count += 1 </span><span class="cx"> return realexec(ctx, proxy, compiled, parameters, **kwargs) </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 17:31:28
|
<!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>[1381] sqlalchemy/branches/schema/test: connection query format moves to *multiparams, **params</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1381</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 12:31:15 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>connection query format moves to *multiparams, **params</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginebasepy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginedefaultpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyschemapy">sqlalchemy/branches/schema/lib/sqlalchemy/schema.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemysqlpy">sqlalchemy/branches/schema/lib/sqlalchemy/sql.py</a></li> <li><a href="#sqlalchemybranchesschematestquerypy">sqlalchemy/branches/schema/test/query.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyenginebasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py (1380 => 1381)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-03 17:08:46 UTC (rev 1380) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-03 17:31:15 UTC (rev 1381) </span><span class="lines">@@ -115,11 +115,11 @@ </span><span class="cx"> The pre_exec and post_exec methods will be called for compiled statements, afterwhich </span><span class="cx"> it is expected that the various methods last_inserted_ids, last_inserted_params, etc. </span><span class="cx"> will contain appropriate values, if applicable.""" </span><del>- def pre_exec(self, engine, proxy, compiled, parameters, **kwargs): </del><ins>+ def pre_exec(self, engine, proxy, compiled, parameters): </ins><span class="cx"> """called before an execution of a compiled statement. proxy is a callable that </span><span class="cx"> takes a string statement and a bind parameter list/dictionary.""" </span><span class="cx"> raise NotImplementedError() </span><del>- def post_exec(self, engine, proxy, compiled, parameters, **kwargs): </del><ins>+ def post_exec(self, engine, proxy, compiled, parameters): </ins><span class="cx"> """called after the execution of a compiled statement. proxy is a callable that </span><span class="cx"> takes a string statement and a bind parameter list/dictionary.""" </span><span class="cx"> raise NotImplementedError() </span><span class="lines">@@ -191,29 +191,35 @@ </span><span class="cx"> return row[0] </span><span class="cx"> else: </span><span class="cx"> return None </span><del>- def execute(self, object, parameters=None, **kwargs): - return Connection.executors[type(object).__mro__[-2]](self, object, parameters, **kwargs) - def execute_text(self, statement, parameters, **kwargs): - cursor = self._execute_raw(statement, parameters, **kwargs) </del><ins>+ def execute(self, object, *multiparams, **params): + return Connection.executors[type(object).__mro__[-2]](self, object, *multiparams, **params) + def execute_default(self, default, **kwargs): + return default.accept_schema_visitor(self.engine.dialect.defaultrunner(self.engine, self.proxy, **kwargs)) + def execute_text(self, statement, *multiparams, **params): + cursor = self._execute_raw(statement, self._conv_params(*multiparams, **params)) </ins><span class="cx"> return ResultProxy(self.engine, self, cursor) </span><del>- def execute_clauseelement(self, elem, parameters, **kwargs): - # TODO: get this into ClauseElement - executemany = parameters is not None and (isinstance(parameters, list) or isinstance(parameters, tuple)) </del><ins>+ def _conv_params(self, *multiparams, **params): + if len(multiparams) == 0: + return params + elif len(multiparams) == 1: + return multiparams[0] + else: + return multiparams + def execute_clauseelement(self, elem, *multiparams, **params): + executemany = len(multiparams) > 0 </ins><span class="cx"> if executemany: </span><del>- param = parameters[0] </del><ins>+ param = multiparams[0] </ins><span class="cx"> else: </span><del>- param = parameters - return self.execute_compiled(elem.compile(engine=self.engine, parameters=param, **kwargs), parameters, **kwargs) - def execute_default(self, default, parameters=None, **kwargs): - return default.accept_schema_visitor(self.engine.dialect.defaultrunner(self.engine, self.proxy, **kwargs)) - def execute_compiled(self, compiled, parameters, **kwargs): </del><ins>+ param = params + return self.execute_compiled(elem.compile(engine=self.engine, parameters=param), *multiparams, **params) + def execute_compiled(self, compiled, *multiparams, **params): </ins><span class="cx"> """executes a sql.Compiled object.""" </span><span class="cx"> cursor = self.connection.cursor() </span><del>- executemany = parameters is not None and (isinstance(parameters, list) or isinstance(parameters, tuple)) </del><ins>+ executemany = len(multiparams) > 0 </ins><span class="cx"> if executemany: </span><del>- parameters = [compiled.get_params(**m) for m in parameters] - elif parameters is not None: - parameters = compiled.get_params(**parameters) </del><ins>+ parameters = [compiled.get_params(**m) for m in multiparams] + elif len(params): + parameters = compiled.get_params(**params) </ins><span class="cx"> else: </span><span class="cx"> parameters = compiled.get_params() </span><span class="cx"> def proxy(statement=None, parameters=None): </span><span class="lines">@@ -224,9 +230,9 @@ </span><span class="cx"> self._execute_raw(statement, parameters, cursor=cursor, context=context) </span><span class="cx"> return cursor </span><span class="cx"> context = self.engine.dialect.create_execution_context() </span><del>- context.pre_exec(self.engine, proxy, compiled, parameters, **kwargs) </del><ins>+ context.pre_exec(self.engine, proxy, compiled, parameters) </ins><span class="cx"> proxy(str(compiled), parameters) </span><del>- context.post_exec(self.engine, proxy, compiled, parameters, **kwargs) </del><ins>+ context.post_exec(self.engine, proxy, compiled, parameters) </ins><span class="cx"> return ResultProxy(self.engine, self, cursor, context, typemap=compiled.typemap) </span><span class="cx"> </span><span class="cx"> # poor man's multimethod/generic function thingy </span><span class="lines">@@ -387,13 +393,13 @@ </span><span class="cx"> if connection is None: </span><span class="cx"> conn.close() </span><span class="cx"> </span><del>- def execute(self, *args, **kwargs): </del><ins>+ def execute(self, statement, *multiparams, **params): </ins><span class="cx"> connection = self.contextual_connect(close_with_result=True) </span><del>- return connection.execute(*args, **kwargs) </del><ins>+ return connection.execute(statement, *multiparams, **params) </ins><span class="cx"> </span><del>- def execute_compiled(self, compiled, parameters, **kwargs): </del><ins>+ def execute_compiled(self, compiled, *multiparams, **params): </ins><span class="cx"> connection = self.contextual_connect(close_with_result=True) </span><del>- return connection.execute_compiled(compiled, parameters, **kwargs) </del><ins>+ return connection.execute_compiled(compiled, *multiparams, **params) </ins><span class="cx"> </span><span class="cx"> def compiler(self, statement, parameters, **kwargs): </span><span class="cx"> return self.dialect.compiler(statement, parameters, engine=self, **kwargs) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginedefaultpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py (1380 => 1381)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-05-03 17:08:46 UTC (rev 1380) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-05-03 17:31:15 UTC (rev 1381) </span><span class="lines">@@ -137,9 +137,9 @@ </span><span class="cx"> class DefaultExecutionContext(base.ExecutionContext): </span><span class="cx"> def __init__(self, dialect): </span><span class="cx"> self.dialect = dialect </span><del>- def pre_exec(self, engine, proxy, compiled, parameters, **kwargs): - self._process_defaults(engine, proxy, compiled, parameters, **kwargs) - def post_exec(self, engine, proxy, compiled, parameters, **kwargs): </del><ins>+ def pre_exec(self, engine, proxy, compiled, parameters): + self._process_defaults(engine, proxy, compiled, parameters) + def post_exec(self, engine, proxy, compiled, parameters): </ins><span class="cx"> pass </span><span class="cx"> def get_rowcount(self, cursor): </span><span class="cx"> if hasattr(self, '_rowcount'): </span><span class="lines">@@ -156,7 +156,7 @@ </span><span class="cx"> return self._last_updated_params </span><span class="cx"> def lastrow_has_defaults(self): </span><span class="cx"> return self._lastrow_has_defaults </span><del>- def _process_defaults(self, engine, proxy, compiled, parameters, **kwargs): </del><ins>+ def _process_defaults(self, engine, proxy, compiled, parameters): </ins><span class="cx"> """INSERT and UPDATE statements, when compiled, may have additional columns added to their </span><span class="cx"> VALUES and SET lists corresponding to column defaults/onupdates that are present on the </span><span class="cx"> Table object (i.e. ColumnDefault, Sequence, PassiveDefault). This method pre-execs those </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/schema.py (1380 => 1381)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-03 17:08:46 UTC (rev 1380) +++ sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-03 17:31:15 UTC (rev 1381) </span><span class="lines">@@ -399,6 +399,8 @@ </span><span class="cx"> "schema.tablename.columnname" """ </span><span class="cx"> self._colspec = column </span><span class="cx"> self._column = None </span><ins>+ if isinstance(column, unicode): + column = str(column) </ins><span class="cx"> </span><span class="cx"> def __repr__(self): </span><span class="cx"> return "ForeignKey(%s)" % repr(self._get_colspec()) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemysqlpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/sql.py (1380 => 1381)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/sql.py 2006-05-03 17:08:46 UTC (rev 1380) +++ sqlalchemy/branches/schema/lib/sqlalchemy/sql.py 2006-05-03 17:31:15 UTC (rev 1381) </span><span class="lines">@@ -349,13 +349,10 @@ </span><span class="cx"> </span><span class="cx"> def execute(self, *multiparams, **params): </span><span class="cx"> """executes this compiled object using the AbstractEngine it is bound to.""" </span><del>- if len(multiparams): - params = multiparams - </del><span class="cx"> e = self.engine </span><span class="cx"> if e is None: </span><span class="cx"> raise InvalidRequestError("This Compiled object is not bound to any engine.") </span><del>- return e.execute_compiled(self, params) </del><ins>+ return e.execute_compiled(self, *multiparams, **params) </ins><span class="cx"> </span><span class="cx"> def scalar(self, *multiparams, **params): </span><span class="cx"> """executes this compiled object via the execute() method, then </span></span></pre></div> <a id="sqlalchemybranchesschematestquerypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/query.py (1380 => 1381)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/query.py 2006-05-03 17:08:46 UTC (rev 1380) +++ sqlalchemy/branches/schema/test/query.py 2006-05-03 17:31:15 UTC (rev 1381) </span><span class="lines">@@ -111,14 +111,6 @@ </span><span class="cx"> </span><span class="cx"> print repr(self.users.select().execute().fetchall()) </span><span class="cx"> </span><del>- def testtransaction(self): - def dostuff(): - self.users.insert().execute(user_id = 7, user_name = 'john') - self.users.insert().execute(user_id = 8, user_name = 'jack') - - db.transaction(dostuff) - print repr(self.users.select().execute().fetchall()) - </del><span class="cx"> def testselectlimit(self): </span><span class="cx"> self.users.insert().execute(user_id=1, user_name='john') </span><span class="cx"> self.users.insert().execute(user_id=2, user_name='jack') </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 17:08:56
|
<!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>[1380] sqlalchemy/trunk/lib/sqlalchemy/schema.py: foreignkey checks for unicode incoming string</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1380</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 12:08:46 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>foreignkey checks for unicode incoming string</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyschemapy">sqlalchemy/trunk/lib/sqlalchemy/schema.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/schema.py (1379 => 1380)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/schema.py 2006-05-03 16:55:56 UTC (rev 1379) +++ sqlalchemy/trunk/lib/sqlalchemy/schema.py 2006-05-03 17:08:46 UTC (rev 1380) </span><span class="lines">@@ -406,7 +406,8 @@ </span><span class="cx"> "schemaname.tablename.columnname" """ </span><span class="cx"> self._colspec = column </span><span class="cx"> self._column = None </span><del>- </del><ins>+ if isinstance(column, unicode): + column = str(column) </ins><span class="cx"> def __repr__(self): </span><span class="cx"> return "ForeignKey(%s)" % repr(self._get_colspec()) </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 16:56:07
|
<!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>[1379] sqlalchemy/trunk/lib/sqlalchemy/mapping: "order_by" parameter propigated to inheriting mappers</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1379</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 11:55:56 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>"order_by" parameter propigated to inheriting mappers oracle ROW_NUMBER logic uses select.oid_column to get default order by</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemydatabasesoraclepy">sqlalchemy/trunk/lib/sqlalchemy/databases/oracle.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemydatabasesoraclepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/databases/oracle.py (1378 => 1379)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/databases/oracle.py 2006-05-03 15:53:45 UTC (rev 1378) +++ sqlalchemy/trunk/lib/sqlalchemy/databases/oracle.py 2006-05-03 16:55:56 UTC (rev 1379) </span><span class="lines">@@ -276,16 +276,10 @@ </span><span class="cx"> return </span><span class="cx"> if select.limit is not None or select.offset is not None: </span><span class="cx"> select._oracle_visit = True </span><ins>+ # to use ROW_NUMBER(), an ORDER BY is required. </ins><span class="cx"> orderby = self.strings[select.order_by_clause] </span><span class="cx"> if not orderby: </span><del>- # to use ROW_NUMBER(), an ORDER BY is required. so here we dig in - # as best we can to find some column we can order by - # TODO: try to get "oid_column" to be used here - if len(select.primary_key): - col = select.primary_key[0].original.table.name - else: - col = [c for c in select.c][0].original.table.name - orderby = "%s.rowid ASC" % col </del><ins>+ orderby = select.oid_column </ins><span class="cx"> select.append_column(sql.ColumnClause("ROW_NUMBER() OVER (ORDER BY %s)" % orderby).label("ora_rn")) </span><span class="cx"> limitselect = sql.select([c for c in select.c if c.key!='ora_rn']) </span><span class="cx"> if select.offset is not None: </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1378 => 1379)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-05-03 15:53:45 UTC (rev 1378) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-05-03 16:55:56 UTC (rev 1379) </span><span class="lines">@@ -114,6 +114,8 @@ </span><span class="cx"> self._synchronizer = None </span><span class="cx"> self.inherits = inherits </span><span class="cx"> self.noninherited_table = table </span><ins>+ if self.order_by is False: + self.order_by = inherits.order_by </ins><span class="cx"> else: </span><span class="cx"> self.primarytable = self.table </span><span class="cx"> self.noninherited_table = self.table </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-03 15:54:00
|
<!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>[1378] sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py: engine prop on ComposedSQLEngine</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1378</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-03 10:53:45 -0500 (Wed, 03 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>engine prop on ComposedSQLEngine</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginebasepy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyenginebasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py (1377 => 1378)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-02 22:36:21 UTC (rev 1377) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-03 15:53:45 UTC (rev 1378) </span><span class="lines">@@ -337,10 +337,11 @@ </span><span class="cx"> self.echo = echo </span><span class="cx"> self.logger = logger or util.Logger(origin='engine') </span><span class="cx"> </span><ins>+ </ins><span class="cx"> def _get_name(self): </span><span class="cx"> return sys.modules[self.dialect.__module__].descriptor()['name'] </span><span class="cx"> name = property(_get_name) </span><del>- </del><ins>+ engine = property(lambda s:s) </ins><span class="cx"> def dispose(self): </span><span class="cx"> self.connection_provider.dispose() </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 22:36:36
|
<!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>[1377] sqlalchemy/branches/schema/test: docs etc, reversed order of table sorts</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1377</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-02 17:36:21 -0500 (Tue, 02 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>docs etc, reversed order of table sorts</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentmetadatatxt">sqlalchemy/branches/schema/doc/build/content/metadata.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="#sqlalchemybranchesschemalibsqlalchemyschemapy">sqlalchemy/branches/schema/lib/sqlalchemy/schema.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemysql_utilpy">sqlalchemy/branches/schema/lib/sqlalchemy/sql_util.py</a></li> <li><a href="#sqlalchemybranchesschematestreflectionpy">sqlalchemy/branches/schema/test/reflection.py</a></li> <li><a href="#sqlalchemybranchesschematesttablespy">sqlalchemy/branches/schema/test/tables.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -123,7 +123,7 @@ </span><span class="cx"> </span><span class="cx"> #### Implicit Connection Contexts {@name=context} </span><span class="cx"> </span><del>-"Implicit" connections refer to the example above when the `execute()` method is called directly off the `Engine` object, *without* the usage of a `Connection` object, and resources are released by calling the `close()` method on the result object. When using "implicit" connections, the user has two choices, determined when the Engine is first created, as to how the resources of this connection should be used in relation to other connections. This is determined by the `strategy` argument to `create_engine()`, which has two possible values: `plain` and `threadlocal`. In `plain`, every `execute` call uses a distinct connection from the database, which is only released when the `close()` method on the Result is called. In `threadlocal`, multiple calls to `execute()` within the same thread will use the already-checked out connection resource if one is available, or if none is available will request a connection resource. </del><ins>+"Implicit" connections refer to the example above when the `execute()` method is called directly off the `Engine` object, *without* the usage of a `Connection` object, and resources are released by calling the `close()` method on the result object. When using "implicit" connections, the user has two choices, determined when the Engine is first created, as to how the resources of this connection should be used in relation to other connections. This is determined by the `strategy` argument to `create_engine()`, which has two possible values: `plain` and `threadlocal`. In `plain`, every `execute` call uses a distinct connection from the database, which is only released when the `close()` method on the Result is called, or the result object itself and its underlying Connection falls out of scope and is garbage collected. In `threadlocal`, multiple calls to `execute()` within the same thread will use the already-checked out connection resource ! if one is available, or if none is available will request a connection resource. </ins><span class="cx"> </span><span class="cx"> It is crucial to note that the `plain` and `threadlocal` contexts **do not impact the connect() method on the Engine.** If you are using explicit Connection objects returned by `connect()` method, you have full control over the connection resources used. </span><span class="cx"> </span><span class="lines">@@ -218,5 +218,5 @@ </span><span class="cx"> </span><span class="cx"> Above, `method_a` is called first, which calls `connection.begin()`. Then it calls `method_b`. When `method_b` calls `connection.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. </span><span class="cx"> </span><del>-Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in [unitofwork_transactions](rel:unitofwork_transactions). </del><ins>+Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in [unitofwork_transaction](rel:unitofwork_transaction). </ins><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentmetadatatxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/metadata.txt (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -29,15 +29,15 @@ </span><span class="cx"> Column('pref_value', String(100)) </span><span class="cx"> ) </span><span class="cx"> </span><del>-The specific datatypes for each Column, such as Integer, String, etc. are defined in [types](rel:types) and are automatically pulled in when you import * from `sqlalchemy`. Note that for Column objects, an altername name can be specified via the "key" parameter; if this parameter is given, then all programmatic references to this Column object will be based on its key, instead of its actual column name. </del><ins>+The specific datatypes for each Column, such as Integer, String, etc. are defined in [types](rel:types) and are part of the `sqlalchemy` module namespace. </ins><span class="cx"> </span><del>-The `MetaData` object supports some handy methods, such as getting a list of Tables in the order of their dependency: </del><ins>+The `MetaData` object supports some handy methods, such as getting a list of Tables in the order (or reverse) of their dependency: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> for t in metadata.table_iterator(reverse=True): </del><ins>+ >>> for t in metadata.table_iterator(reverse=False): </ins><span class="cx"> ... print t.name </span><ins>+ users </ins><span class="cx"> user_prefs </span><del>- users </del><span class="cx"> </span><span class="cx"> And `Table` provides an interface to the table's properties as well as that of its columns: </span><span class="cx"> </span><span class="lines">@@ -95,7 +95,7 @@ </span><span class="cx"> </span><span class="cx"> #### Binding MetaData to an Engine </span><span class="cx"> </span><del>-A MetaData object can be associated with one or more Engine instances. This allows the MetaData and the elements within it to perform operations automatically, using the connection resources of that Engine. This includes being able to "reflect" the columns of tables, as well as to perform create and drop operations without needing to pass an Engine around. It also allows SQL constructs to be created which know how to execute themselves (called "implicit execution"). </del><ins>+A MetaData object can be associated with one or more Engine instances. This allows the MetaData and the elements within it to perform operations automatically, using the connection resources of that Engine. This includes being able to "reflect" the columns of tables, as well as to perform create and drop operations without needing to pass an `Engine` or `Connection` around. It also allows SQL constructs to be created which know how to execute themselves (called "implicit execution"). </ins><span class="cx"> </span><span class="cx"> To bind `MetaData` to a single `Engine`, use `BoundMetaData`: </span><span class="cx"> </span><span class="lines">@@ -126,7 +126,7 @@ </span><span class="cx"> >>> [c.name for c in messages.columns] </span><span class="cx"> ['message_id', 'message_name', 'date'] </span><span class="cx"> </span><del>-At the moment the Table is constructed, it will query the database for the columns and constraints of the `mytable` table. </del><ins>+At the moment the Table is constructed, it will query the database for the columns and constraints of the `messages` table. </ins><span class="cx"> </span><span class="cx"> Note that if a reflected table has a foreign key referencing another table, then the metadata for the related table will be loaded as well, even if it has not been defined by the application: </span><span class="cx"> </span><span class="lines">@@ -152,7 +152,32 @@ </span><span class="cx"> >>> othertable = Table('news', meta) </span><span class="cx"> >>> othertable is news_articles </span><span class="cx"> True </span><del>- </del><ins>+ +#### Specifying the Schema Name {@name=schema} + +Some databases support the concept of multiple schemas. A `Table` can reference this by specifying the `schema` keyword argument: + + {python} + financial_info = Table('financial_info', meta, + Column('id', Integer, primary_key=True), + Column('value', String(100), nullable=False), + schema='remote_banks' + ) + +Within the `MetaData` collection, this table will be identified by the combination of `financial_info` and `remote_banks`. If another table called `financial_info` is referenced without the `remote_banks` schema, it will refer to a different `Table`. `ForeignKey` objects can reference columns in this table using the form `remote_banks.financial_info.id`. + +#### Other Options {@name=options} + +`Tables` may support database-specific options, such as MySQL's `engine` option that can specify "MyISAM", "InnoDB", and other backends for the table: + + {python} + addresses = Table('engine_email_addresses', meta, + Column('address_id', Integer, primary_key = True), + Column('remote_user_id', Integer, ForeignKey(users.c.user_id)), + Column('email_address', String(20)), + mysql_engine='InnoDB' + ) + </ins><span class="cx"> ### Creating and Dropping Database Tables {@name=creating} </span><span class="cx"> </span><span class="cx"> Creating and dropping individual tables can be done via a `Connection`: </span><span class="lines">@@ -192,6 +217,7 @@ </span><span class="cx"> </span><span class="cx"> Similarly, both `Connection` and `Table` have a `drop()` method: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> {sql}employees.drop(engine=e) </span><span class="cx"> DROP TABLE employees </span><span class="cx"> {} </span><span class="lines">@@ -225,7 +251,6 @@ </span><span class="cx"> email_address VARCHAR(60), </span><span class="cx"> password VARCHAR(20) NOT NULL </span><span class="cx"> ) </span><del>- </del><span class="cx"> PRAGMA table_info(user_prefs){} </span><span class="cx"> CREATE TABLE user_prefs( </span><span class="cx"> pref_id INTEGER NOT NULL PRIMARY KEY, </span><span class="lines">@@ -234,7 +259,6 @@ </span><span class="cx"> pref_value VARCHAR(100) </span><span class="cx"> ) </span><span class="cx"> </span><del>- </del><span class="cx"> ### Column Defaults and OnUpdates {@name=defaults} </span><span class="cx"> </span><span class="cx"> SQLAlchemy includes flexible constructs in which to create default values for columns upon the insertion of rows, as well as upon update. These defaults can take several forms: a constant, a Python callable to be pre-executed before the SQL is executed, a SQL expression or function to be pre-executed before the SQL is executed, a pre-executed Sequence (for databases that support sequences), or a "passive" default, which is a default function triggered by the database itself upon insert, the value of which can then be post-fetched by the engine, provided the row provides a primary key in which to call upon. </span><span class="lines">@@ -339,8 +363,7 @@ </span><span class="cx"> ##### The Catch: Postgres Primary Key Defaults always Pre-Execute {@name=postgres} </span><span class="cx"> </span><span class="cx"> Current Postgres support does not rely upon OID's to determine the identity of a row. This is because the usage of OIDs has been deprecated with Postgres and they are disabled by default for table creates as of PG version 8. Pyscopg2's "cursor.lastrowid" function only returns OIDs. Therefore, when inserting a new row which has passive defaults set on the primary key columns, the default function is <b>still pre-executed</b> since SQLAlchemy would otherwise have no way of retrieving the row just inserted. </span><del>- - </del><ins>+ </ins><span class="cx"> #### Defining Sequences {@name=sequences} </span><span class="cx"> </span><span class="cx"> A table with a sequence looks like: </span><span class="lines">@@ -395,7 +418,7 @@ </span><span class="cx"> </span><span class="cx"> ### Adapting Tables to Alternate Metadata {@name=adapting} </span><span class="cx"> </span><del>-A `Table` object created against a specific `MetaData` object can be re-created against a new engine using the `tometadata` method: </del><ins>+A `Table` object created against a specific `MetaData` object can be re-created against a new MetaData using the `tometadata` method: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # create two metadata </span><span class="lines">@@ -406,6 +429,6 @@ </span><span class="cx"> users_table = Table('users', meta1, autoload=True) </span><span class="cx"> </span><span class="cx"> # create the same Table object for the plain metadata </span><del>- users_table_2 = users.tometadata(meta2) </del><ins>+ users_table_2 = users_table.tometadata(meta2) </ins><span class="cx"> </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -84,7 +84,7 @@ </span><span class="cx"> </span><span class="cx"> ### Introduction to the Identity Map {@name=identitymap} </span><span class="cx"> </span><del>-A primary concept of the Session's underlying Unit of Work is that it is keeping track of all persistent instances; recall that a persistent instance has a database identity and is attached to a Session. In particular, the Unit of Work must insure that only *one* copy of a particular persistent instance exists within the Session at any given time. The UOW accomplishes this task using a dictionary known as an *Identity Map*. When a `Query` is used to issue `select` or `get` requests to the database, it will in nearly all cases result in an actual SQL execution to the database, and a corresponding traversal of rows received from that execution. However, when the underlying mapper *instantiates* objects corresponding to the result set rows it receives, it will *check the session's identity map first* before instantating a new object, and return *the same instance* already present in the identiy map if it already exists, essentially *ignoring* the object state ! represented by that row. There are several ways to override this behavior and truly refresh an already-loaded instance which are described later, but the main idea is that once your instance is loaded into a particular Session, it will *never change* its state without your explicit approval, regardless of what the database says about it. </del><ins>+A primary concept of the Session's underlying Unit of Work is that it is keeping track of all persistent instances; recall that a persistent instance has a database identity and is attached to a Session. In particular, the Unit of Work must insure that only *one* copy of a particular persistent instance exists within the Session at any given time. The UOW accomplishes this task using a dictionary known as an *Identity Map*. When a `Query` is used to issue `select` or `get` requests to the database, it will in nearly all cases result in an actual SQL execution to the database, and a corresponding traversal of rows received from that execution. However, when the underlying mapper *instantiates* objects corresponding to the result set rows it receives, it will check the session's identity map first before instantating a new object, and return the same instance already present in the identity map if it already exists, essentially *ignoring* the object state repr! esented by that row. There are several ways to override this behavior and truly refresh an already-loaded instance which are described later, but the main idea is that once your instance is loaded into a particular Session, it will *never change* its state without your explicit approval, regardless of what the database says about it. </ins><span class="cx"> </span><span class="cx"> For example; below, two separate calls to load an instance with database identity "15" are issued, and the results assigned to two separate variables. However, since the same `Session` was used, the two instances are the same instance: </span><span class="cx"> </span><span class="lines">@@ -100,13 +100,25 @@ </span><span class="cx"> </span><span class="cx"> The Identity Map is an instance of `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. To guarantee that an instance is removed from the identity map before removing references to it, use the `expunge()` method, described later, to remove it. </span><span class="cx"> </span><del>-The Session's identity map is accessible via the `identity_map` accessor: </del><ins>+The Session supports an iterator interface in order to see all objects in the identity map: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> objectstore.get_session().identity_map.values() </del><ins>+ for obj in session: + print obj + +As well as `__contains__()`: + + {python} + if obj in session: + print "Object is present" + +The identity map itself is accessible via the `identity_map` accessor: + + {python} + >>> session.identity_map.values() </ins><span class="cx"> [<__main__.User object at 0x712630>, <__main__.Address object at 0x712a70>] </span><span class="cx"> </span><del>-The identity of each object instance is available via the _instance_key property attached to each object instance, and is a tuple consisting of the object's class and an additional tuple of primary key values, in the order that they appear within the table definition: </del><ins>+The identity of each object instance is available via the `_instance_key` property attached to each object instance, and is a tuple consisting of the object's class and an additional tuple of primary key values, in the order that they appear within the table definition: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> obj._instance_key </span><span class="lines">@@ -114,13 +126,13 @@ </span><span class="cx"> </span><span class="cx"> At the moment that an object is assigned this key within a `flush()` operation, it is also added to the session's identity map. </span><span class="cx"> </span><del>-The get() method on `Query`, which retrieves an object based on primary key identity, also checks in the Session's identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the get() method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. </del><ins>+The `get()` method on `Query`, which retrieves an object based on primary key identity, also checks in the Session's identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the `get()` method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. </ins><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 lists of all *newly created* (i.e. pending) objects, lists of all persistent objects whose attributes have been *modified*, and lists of all persistent objects that have been marked as *deleted*. These lists are used when a `flush()` call is issued to save all changes. After the flush 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 lists of all *newly created* (i.e. pending) objects, lists of all persistent objects whose attributes have been *modified*, and lists of all persistent objects that have been marked as *deleted*. 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><del>-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: </del><ins>+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`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # pending objects recently added to the Session </span><span class="lines">@@ -152,13 +164,13 @@ </span><span class="cx"> q = session.query(User, entity_name='alt_users') </span><span class="cx"> y = q.options(eagerload('orders')).select() </span><span class="cx"> </span><del>-"entity_name" is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the "entity_name" argument, so that a given class can be properly matched to the desired primary mapper. </del><ins>+`entity_name` is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the `entity_name` argument, so that a given class can be properly matched to the desired primary mapper. </ins><span class="cx"> </span><del>-All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating Session. </del><ins>+All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating `Session`. </ins><span class="cx"> </span><span class="cx"> #### get() {@name=get} </span><span class="cx"> </span><del>-Given a class or mapper, a scalar or tuple-based identity, and an optional "entity_name" keyword argument, creates a `Query` corresponding to the given mapper or class/entity_name combination, and calls the `get()` method with the given identity value. If the object already exists within this Session, it is simply returned, else it is queried from the database. If the instance is not found, the method returns `None`. </del><ins>+Given a class or mapper, a scalar or tuple-based identity, and an optional `entity_name` keyword argument, creates a `Query` corresponding to the given mapper or class/entity_name combination, and calls the `get()` method with the given identity value. If the object already exists within this Session, it is simply returned, else it is queried from the database. If the instance is not found, the method returns `None`. </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # get Employer primary key 5 </span><span class="lines">@@ -181,14 +193,8 @@ </span><span class="cx"> </span><span class="cx"> #### save() {@name=save} </span><span class="cx"> </span><del>-save() is called with a single transient (unsaved, unattached) instance as an argument, which is then added to the Session and becomes pending. When the session is next `flush()`ed, the instance will be saved to the database uponwhich it becomes persistent (saved, attached). If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised. </del><ins>+save() is called with a single transient (unsaved, unattached) instance as an argument, which is then added to the Session and becomes pending. When the session is next `flush`ed, the instance will be saved to the database uponwhich it becomes persistent (saved, attached). If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised. </ins><span class="cx"> </span><del>-save() is called automatically for new instances by the classes' associated mapper, if a default Session context is in effect (such as a thread-local session), which means that newly created instances automatically become pending. If there is no default session available, then the instance remains transient (unattached) until it is explicitly added to a Session via the save() method. - -A transient instance also can be automatically `save()`ed if it is associated with a parent object which specifies `save-update` within its *cascade* rules, and that parent is already attached or becomes attached to a Session. For more information on cascade, see the next section. - -The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). - </del><span class="cx"> {python} </span><span class="cx"> user1 = User(name='user1') </span><span class="cx"> user2 = User(name='user2') </span><span class="lines">@@ -197,9 +203,15 @@ </span><span class="cx"> </span><span class="cx"> session.flush() # write changes to the database </span><span class="cx"> </span><ins>+save() is called automatically for new instances by the classes' associated mapper, if a default Session context is in effect (such as a thread-local session), which means that newly created instances automatically become pending. If there is no default session available, then the instance remains transient (unattached) until it is explicitly added to a Session via the save() method. + +A transient instance also can be automatically `save`ed if it is associated with a parent object which specifies `save-update` within its `cascade` rules, and that parent is already attached or becomes attached to a Session. For more information on `cascade`, see the next section. + +The `save_or_update()` method, covered later, is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). + </ins><span class="cx"> #### flush() {@name=flush} </span><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 flush 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 what a flush looks like: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> session.flush() </span><span class="lines">@@ -211,7 +223,7 @@ </span><span class="cx"> # objects remain present in the session. </span><span class="cx"> session.flush(user1, address2) </span><span class="cx"> </span><del>-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. </del><ins>+This second form of flush should be used 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><span class="cx"> ##### Notes on Flush {@name=whatis} </span><span class="cx"> </span><span class="lines">@@ -219,15 +231,15 @@ </span><span class="cx"> </span><span class="cx"> This misunderstanding is related to the observed behavior of backreferences ([datamapping_relations_backreferences](rel:datamapping_relations_backreferences)), which automatically associates an instance "A" with another instance "B", in response to the manual association of instance "B" to instance "A" by the user. The backreference operation occurs completely externally to the `flush()` operation, and is pretty much the only example of a SQLAlchemy feature that manipulates the relationships of persistent objects. </span><span class="cx"> </span><del>-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. </del><ins>+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"> #### close() {@name=close} </span><span class="cx"> </span><del>-This method first calls `clear()`, removing all objects from this Session, and then insures that any transactional resources are closed. </del><ins>+This method first calls `clear()`, removing all objects from this `Session`, and then insures that any transactional resources are closed. </ins><span class="cx"> </span><span class="cx"> #### delete() {@name=delete} </span><span class="cx"> </span><del>-The delete call places an instance into the Unit of Work's list of objects to be marked as deleted: </del><ins>+The `delete` method places an instance into the Unit of Work's list of objects to be marked as deleted: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # mark two objects to be deleted </span><span class="lines">@@ -277,21 +289,32 @@ </span><span class="cx"> </span><span class="cx"> Both of these methods receive two arguments; in the case of `bind_mapper()`, it is a `Mapper` and an `Engine` or `Connection` instance; in the case of `bind_table()`, it is a `Table` instance or other `Selectable` (such as an `Alias`, `Select`, etc.), and an `Engine` or `Connection` instance. </span><span class="cx"> </span><del>-Normally, when a Session is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retreieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raiased. A second form of `create_session()` takes the argument `bind_to=engine_or_connection`, where all operations performed by this Session are done via the single Engine or Connection passed to the constructor. </del><ins>+ {python} + engine1 = create_engine('sqlite:///file1.db') + engine2 = create_engine('mysql://localhost') + + sqlite_conneciton = engine1.connect() + + sess = create_session() + + sess.bind_mapper(mymapper, sqlite_connection) # bind mymapper operations to a single SQLite connection + sess.bind_table(email_addresses_table, engine2) # bind operations with the email_addresses_table to mysql + +Normally, when a `Session` is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retrieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raised. A second form of `create_session()` takes the argument `bind_to=engine_or_connection`, where all SQL operations performed by this `Session` use the single `Engine` or `Connection` passed to the constructor. With `bind_mapper()` and `bind_table()`, the operations of individual mapper and/or tables are bound to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the `create_session()` function. Configurations which interact with multiple explicit database connections at ! one time must use either or both of these methods in order to associate `Session` operations with the appropriate connection resource. </ins><span class="cx"> </span><del>-The point of these methods is to bind individual mapper and/or table operations to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the constructor. Configurations which interact with multiple explicit database connections at one time must use either or both of these methods in order to associate Session operations with the appropriate connection resource. </del><ins>+Binding a `Mapper` to a resource takes precedence over a `Table` bind, meaning if mapper A is associated with table B, and the Session binds mapper A to connection X and table B to connection Y, an operation with mapper A will use connection X, not connection Y. </ins><span class="cx"> </span><del>-Binding a `Mapper` to a resource takes precedence over a `Table` bind, meaning if mapper A is associated with table B, and the Session binds mapper A to connection X and table B to connection Y, an opertaion with mapper A will use connection X, not connection Y. - </del><span class="cx"> #### update() {@name=update} </span><span class="cx"> </span><del>-The update() method is used *only* with detached instances. A detached instance only exists if its `Session` was cleared or closed, or the instance was `expunge()`d from its session. `update()` will re-attach the detached instance with this Session, bringing it back to the persistent state. If the instance is already attached to an existing Session, an exception is raised. </del><ins>+The update() method is used *only* with detached instances. A detached instance only exists if its `Session` was cleared or closed, or the instance was `expunge()`d from its session. `update()` will re-attach the detached instance with this Session, bringing it back to the persistent state, and allowing any changes on the instance to be saved when the `Session` is next `flush`ed. If the instance is already attached to an existing `Session`, an exception is raised. </ins><span class="cx"> </span><ins>+A detached instance also can be automatically `update`ed if it is associated with a parent object which specifies `save-update` within its `cascade` rules, and that parent is already attached or becomes attached to a Session. For more information on `cascade`, see the next section. + </ins><span class="cx"> The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). </span><span class="cx"> </span><span class="cx"> #### save\_or\_update() {@name=saveorupdate} </span><span class="cx"> </span><del>-This method is a combination of the `save()` and `update()` methods, which will examine the given instance for a database identity (i.e. if it is transient or detached), and will call the implementation of `save()` or `update()` as appropriate. Use `save_or_update()` to add unattached instances to a session when you're not sure if they were newly created or not. </del><ins>+This method is a combination of the `save()` and `update()` methods, which will examine the given instance for a database identity (i.e. if it is transient or detached), and will call the implementation of `save()` or `update()` as appropriate. Use `save_or_update()` to add unattached instances to a session when you're not sure if they were newly created or not. Like `save()` and `update()`, `save_or_update()` cascades along the `save-update` cascade indicator, described in the `cascade` section below. </ins><span class="cx"> </span><span class="cx"> #### merge() {@name=merge} </span><span class="cx"> </span><span class="lines">@@ -393,13 +416,31 @@ </span><span class="cx"> </span><span class="cx"> The `connection()` method also exists on the `Session` object itself, and can be called regardless of whether or not a `SessionTransaction` is in progress. If an `Engine` is being used with `threadlocal` strategy, the `Connection` returned will correspond to the connection resources that are bound to the current thread, if any. </span><span class="cx"> </span><ins>+#### Using Engine-level Transactions with Sessions + +The transactions issued by `SessionTransaction` as well as internally by the `Session`'s `flush()` operation use the same `Transaction` object off of `Connection` that is publically available. Recall that this object supports "nestable" behavior, meaning any number of actors can call `begin()` off a particular `Connection` object, and they will all be managed within the scope of a single transaction. Therefore, the `flush()` operation can similarly take place within the scope of a regular `Transaction`: + + {python title="Transactions with Sessions"} + connection = engine.connect() # Connection + session = create_session(bind_to=connection) # Session bound to the Connection + trans = connection.begin() # start transaction + try: + stuff = session.query(MyClass).select() # Session operation uses connection + stuff[2].foo = 'bar' + connection.execute(mytable.insert(), dict(id=12, value="bar")) # use connection explicitly + session.flush() # Session flushes with "connection", using transaction "trans" + trans.commit() # commit + except: + trans.rollback() # or rollback + raise + </ins><span class="cx"> ### Analyzing Object Flushes {@name=logging} </span><span class="cx"> </span><span class="cx"> The session module can log an extensive display of its "flush plans", which is a graph of its internal representation of objects before they are written to the database. To turn this logging on: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- # make an engine with echo_uow - engine = create_engine('myengine...', echo_uow=True) </del><ins>+ # make an Session with echo_uow + session = create_session(echo_uow=True) </ins><span class="cx"> </span><span class="cx"> The `flush()` operation will then dump to the standard output displays like the following: </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildtxt2mytpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/txt2myt.py (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/txt2myt.py 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/doc/build/txt2myt.py 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -90,7 +90,7 @@ </span><span class="cx"> # consumed as Myghty comments. </span><span class="cx"> text = re.compile(r'^(?!<&)', re.M).sub(' ', text) </span><span class="cx"> </span><del>- sqlre = re.compile(r'{sql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S) </del><ins>+ sqlre = re.compile(r'{sql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*(\n|$)', re.S) </ins><span class="cx"> if sqlre.search(text) is not None: </span><span class="cx"> use_sliders = False </span><span class="cx"> else: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/schema.py (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -634,13 +634,13 @@ </span><span class="cx"> </span><span class="cx"> def do(conn): </span><span class="cx"> e = conn.engine </span><del>- ts = self._sort_tables( tables, reverse=False ) </del><ins>+ ts = self._sort_tables( tables, reverse=True ) </ins><span class="cx"> for table in ts: </span><span class="cx"> if e.dialect.has_table(conn, table.name): </span><span class="cx"> conn.drop(table) </span><span class="cx"> engine.run_callable(do) </span><span class="cx"> </span><del>- def _sort_tables(self, tables, reverse=True): </del><ins>+ def _sort_tables(self, tables, reverse=False): </ins><span class="cx"> import sqlalchemy.sql_util </span><span class="cx"> sorter = sqlalchemy.sql_util.TableCollection() </span><span class="cx"> for t in self.tables.values(): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemysql_utilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/sql_util.py (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/sql_util.py 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/lib/sqlalchemy/sql_util.py 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -11,14 +11,14 @@ </span><span class="cx"> def add(self, table): </span><span class="cx"> self.tables.append(table) </span><span class="cx"> </span><del>- def sort(self, reverse=True ): </del><ins>+ def sort(self, reverse=False ): </ins><span class="cx"> import sqlalchemy.orm.topological </span><span class="cx"> tuples = [] </span><span class="cx"> class TVisitor(schema.SchemaVisitor): </span><span class="cx"> def visit_foreign_key(self, fkey): </span><span class="cx"> parent_table = fkey.column.table </span><span class="cx"> child_table = fkey.parent.table </span><del>- tuples.append( ( child_table, parent_table ) ) </del><ins>+ tuples.append( ( parent_table, child_table ) ) </ins><span class="cx"> vis = TVisitor() </span><span class="cx"> for table in self.tables: </span><span class="cx"> table.accept_schema_visitor(vis) </span></span></pre></div> <a id="sqlalchemybranchesschematestreflectionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/reflection.py (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/reflection.py 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/test/reflection.py 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -199,7 +199,7 @@ </span><span class="cx"> def test_sorter( self ): </span><span class="cx"> tables = metadata._sort_tables(metadata.tables.values()) </span><span class="cx"> table_names = [t.name for t in tables] </span><del>- self.assertEqual( table_names, ['users', 'orders', 'items', 'email_addresses'] ) </del><ins>+ self.assert_( table_names == ['users', 'orders', 'items', 'email_addresses'] or table_names == ['users', 'email_addresses', 'orders', 'items']) </ins><span class="cx"> </span><span class="cx"> </span><span class="cx"> def test_createdrop(self): </span></span></pre></div> <a id="sqlalchemybranchesschematesttablespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/tables.py (1376 => 1377)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/tables.py 2006-05-02 21:32:21 UTC (rev 1376) +++ sqlalchemy/branches/schema/test/tables.py 2006-05-02 22:36:21 UTC (rev 1377) </span><span class="lines">@@ -56,7 +56,7 @@ </span><span class="cx"> def drop(): </span><span class="cx"> metadata.drop_all() </span><span class="cx"> def delete(): </span><del>- for t in metadata.table_iterator(reverse=False): </del><ins>+ for t in metadata.table_iterator(reverse=True): </ins><span class="cx"> t.delete().execute() </span><span class="cx"> def user_data(): </span><span class="cx"> users.insert().execute( </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 21:32:34
|
<!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>[1376] sqlalchemy/branches/schema/lib/sqlalchemy: docs, tweaks, gambits DISTINCT ON pg patch</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1376</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-02 16:32:21 -0500 (Tue, 02 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>docs, tweaks, gambits DISTINCT ON pg patch</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentmetadatatxt">sqlalchemy/branches/schema/doc/build/content/metadata.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemydatabasespostgrespy">sqlalchemy/branches/schema/lib/sqlalchemy/databases/postgres.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginebasepy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginedefaultpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyschemapy">sqlalchemy/branches/schema/lib/sqlalchemy/schema.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -26,10 +26,12 @@ </span><span class="cx"> </span><span class="cx"> SQLAlchemy 0.2 indicates the source of an Engine strictly via [RFC-1738](http://rfc.net/rfc1738.html) style URLs, combined with optional keyword arguments to specify options for the Engine. The form of the URL is: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> driver://username:password@host:port/database </span><span class="cx"> </span><span class="cx"> Available drivernames are `sqlite`, `mysql`, `postgres`, `oracle`, `mssql`, and `firebird`. For sqlite, the database name is the filename to connect to, or the special name ":memory:" which indicates an in-memory database. The URL is typically sent as a string to the `create_engine()` function: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> pg_db = create_engine('postgres://scott:tiger@localhost:5432/mydatabase') </span><span class="cx"> sqlite_db = create_engine('sqlite:///mydb.txt') </span><span class="cx"> mysql_db = create_engine('mysql://localhost/foo') </span><span class="lines">@@ -45,7 +47,7 @@ </span><span class="cx"> Options that can be specified include the following: </span><span class="cx"> </span><span class="cx"> * strategy='plain' : the Strategy describes the general configuration used to create this Engine. The two available values are `plain`, which is the default, and `threadlocal`, which applies a "thread-local context" to implicit executions performed by the Engine. This context is further described in the sections below. </span><del>-* pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in [pool](rel:pool)). If None, a default `Pool` (usually `QueuePool`, or `SingletonThreadPool` in the case of SQLite) will be created using the engine's connect arguments. </del><ins>+* pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in [pooling](rel:pooling)). If None, a default `Pool` (usually `QueuePool`, or `SingletonThreadPool` in the case of SQLite) will be created using the engine's connect arguments. </ins><span class="cx"> </span><span class="cx"> Example: </span><span class="cx"> </span><span class="lines">@@ -62,12 +64,10 @@ </span><span class="cx"> * echo=False : if True, the Engine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' "echo" data member can be modified at any time to turn logging on and off. If set to the string 'debug', result rows will be printed to the standard output as well. </span><span class="cx"> * logger=None : a file-like object where logging output can be sent, if echo is set to True. This defaults to sys.stdout. </span><span class="cx"> * module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if 2 cannot be found. For Oracle, its cx_Oracle. </span><del>-* default_ordering=False : if True, table objects and associated joins and aliases will generate information used for ordering by primary keys (or OIDs, if the database supports OIDs). This information is used by the Mapper system to when it constructs select queries to supply a default ordering to mapped objects. </del><span class="cx"> * use_ansi=True : used only by Oracle; when False, the Oracle driver attempts to support a particular "quirk" of some Oracle databases, that the LEFT OUTER JOIN SQL syntax is not supported, and the "Oracle join" syntax of using &lt;column1&gt;(+)=&lt;column2&gt; must be used in order to achieve a LEFT OUTER JOIN. Its advised that the Oracle database be configured to have full ANSI support instead of using this feature. </span><span class="cx"> * use_oids=False : used only by Postgres, will enable the column name "oid" as the object ID column. Postgres as of 8.1 has object IDs disabled by default. </span><span class="cx"> * convert_unicode=False : if set to True, all String/character based types will convert Unicode values to raw byte values going into the database, and all raw byte values to Python Unicode coming out in result sets. This is an engine-wide method to provide unicode across the board. For unicode conversion on a column-by-column level, use the Unicode column type instead. </span><span class="cx"> * encoding='utf-8' : the encoding to use for Unicode translations - passed to all encode/decode methods. </span><del>-* echo_uow=False : when True, logs unit of work commit plans to the standard output. </del><span class="cx"> </span><span class="cx"> ### Using Connections {@name=connections} </span><span class="cx"> </span><span class="lines">@@ -84,7 +84,7 @@ </span><span class="cx"> connection.close() </span><span class="cx"> </span><span class="cx"> The `close` method on `Connection` does not actually remove the underlying connection to the database, but rather indicates that the underlying resources can be returned to the connection pool. When using the `connect()` method, the DBAPI connection referenced by the `Connection` object is not referenced anywhere else. </span><del>- </del><ins>+ </ins><span class="cx"> {python title="Implicit Connection"} </span><span class="cx"> engine = create_engine('sqlite:///:memory:') </span><span class="cx"> result = engine.execute("select * from mytable where col1=:col1", {'col1':5}) </span><span class="lines">@@ -105,6 +105,22 @@ </span><span class="cx"> print row['col1'], row['col2'] </span><span class="cx"> connection.close() </span><span class="cx"> </span><ins>+In many cases, the `Connection` and `Engine` can be used interchangeably; as they both provide an `engine` attribute as well as similar `execute` methods, most SQLAlchemy functions which take an `Engine` as a parameter with which to execute SQL will also accept a `Connection`: + + {python title="Specify Engine or Connection"} + engine = create_engine('sqlite:///:memory:') + + # specify some Table metadata + metadata = MetaData() + table = Table('sometable', metadata, Column('col1', Integer)) + + # create the table with the Engine + table.create(engine=engine) + + # drop the table with a Connection off the Engine + connection = engine.connect() + table.drop(engine=connection) + </ins><span class="cx"> #### Implicit Connection Contexts {@name=context} </span><span class="cx"> </span><span class="cx"> "Implicit" connections refer to the example above when the `execute()` method is called directly off the `Engine` object, *without* the usage of a `Connection` object, and resources are released by calling the `close()` method on the result object. When using "implicit" connections, the user has two choices, determined when the Engine is first created, as to how the resources of this connection should be used in relation to other connections. This is determined by the `strategy` argument to `create_engine()`, which has two possible values: `plain` and `threadlocal`. In `plain`, every `execute` call uses a distinct connection from the database, which is only released when the `close()` method on the Result is called. In `threadlocal`, multiple calls to `execute()` within the same thread will use the already-checked out connection resource if one is available, or if none is available will request a connection resource. </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentmetadatatxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/metadata.txt (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/doc/build/content/metadata.txt 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -1,34 +1,48 @@ </span><span class="cx"> Database Meta Data {@name=metadata} </span><span class="cx"> ================== </span><span class="cx"> </span><del>-### Describing Tables with MetaData {@name=tables} </del><ins>+### Describing Databases with MetaData {@name=tables} </ins><span class="cx"> </span><del>-The core of SQLAlchemy's query and object mapping operations is table metadata, which are Python objects that describe tables. Metadata objects can be created by explicitly naming the table and all its properties, using the Table, Column, ForeignKey, and Sequence objects imported from `sqlalchemy.schema`, and a database engine constructed as described in the previous section, or they can be automatically pulled from an existing database schema. First, the explicit version: </del><ins>+The core of SQLAlchemy's query and object mapping operations is database metadata, which are Python objects that describe tables and other schema-level objects. Metadata objects can be created by explicitly naming the various components and their properties, using the Table, Column, ForeignKey, Index, and Sequence objects imported from `sqlalchemy.schema`. There is also support for *reflection*, which means you only specify the *name* of the entities and they are recreated from the database automatically. </ins><span class="cx"> </span><ins>+A collection of metadata entities is stored in an object aptly named `MetaData`. This object takes an optional `name` parameter: + </ins><span class="cx"> {python} </span><span class="cx"> from sqlalchemy import * </span><del>- engine = create_engine('sqlite', {'filename':':memory:'}, **opts) </del><span class="cx"> </span><del>- users = Table('users', engine, </del><ins>+ metadata = MetaData(name='my metadata') + +Then to construct a Table, use the `Table` class: + + {python} + users = Table('users', metadata, </ins><span class="cx"> Column('user_id', Integer, primary_key = True), </span><span class="cx"> Column('user_name', String(16), nullable = False), </span><span class="cx"> Column('email_address', String(60), key='email'), </span><span class="cx"> Column('password', String(20), nullable = False) </span><span class="cx"> ) </span><span class="cx"> </span><del>- user_prefs = Table('user_prefs', engine, </del><ins>+ user_prefs = Table('user_prefs', metadata, </ins><span class="cx"> Column('pref_id', Integer, primary_key=True), </span><span class="cx"> Column('user_id', Integer, ForeignKey("users.user_id"), nullable=False), </span><span class="cx"> Column('pref_name', String(40), nullable=False), </span><span class="cx"> Column('pref_value', String(100)) </span><span class="cx"> ) </span><ins>+ +The specific datatypes for each Column, such as Integer, String, etc. are defined in [types](rel:types) and are automatically pulled in when you import * from `sqlalchemy`. Note that for Column objects, an altername name can be specified via the "key" parameter; if this parameter is given, then all programmatic references to this Column object will be based on its key, instead of its actual column name. + +The `MetaData` object supports some handy methods, such as getting a list of Tables in the order of their dependency: + + {python} + >>> for t in metadata.table_iterator(reverse=True): + ... print t.name + user_prefs + users </ins><span class="cx"> </span><del>-The specific datatypes, such as Integer, String, etc. are defined in [types](rel:types) and are automatically pulled in when you import * from `sqlalchemy`. Note that for Column objects, an altername name can be specified via the "key" parameter; if this parameter is given, then all programmatic references to this Column object will be based on its key, instead of its actual column name. </del><ins>+And `Table` provides an interface to the table's properties as well as that of its columns: </ins><span class="cx"> </span><del>-Once constructed, the Table object provides a clean interface to the table's properties as well as that of its columns: - </del><span class="cx"> {python} </span><del>- employees = Table('employees', engine, </del><ins>+ employees = Table('employees', metadata, </ins><span class="cx"> Column('employee_id', Integer, primary_key=True), </span><span class="cx"> Column('employee_name', String(60), nullable=False, key='name'), </span><span class="cx"> Column('employee_dept', Integer, ForeignKey("departments.department_id")) </span><span class="lines">@@ -55,7 +69,10 @@ </span><span class="cx"> for fkey in employees.foreign_keys: </span><span class="cx"> # ... </span><span class="cx"> </span><del>- # access the table's SQLEngine object: </del><ins>+ # access the table's MetaData: + employees.metadata + + # access the table's Engine, if its MetaData is bound: </ins><span class="cx"> employees.engine </span><span class="cx"> </span><span class="cx"> # access a column's name, type, nullable, primary key, foreign key </span><span class="lines">@@ -75,49 +92,96 @@ </span><span class="cx"> </span><span class="cx"> # get the table related by a foreign key </span><span class="cx"> fcolumn = employees.c.employee_dept.foreign_key.column.table </span><del>- -Metadata objects can also be <b>reflected</b> from tables that already exist in the database. Reflection means based on a table name, the names, datatypes, and attributes of all columns, including foreign keys, will be loaded automatically. This feature is supported by all database engines: </del><span class="cx"> </span><ins>+#### Binding MetaData to an Engine + +A MetaData object can be associated with one or more Engine instances. This allows the MetaData and the elements within it to perform operations automatically, using the connection resources of that Engine. This includes being able to "reflect" the columns of tables, as well as to perform create and drop operations without needing to pass an Engine around. It also allows SQL constructs to be created which know how to execute themselves (called "implicit execution"). + +To bind `MetaData` to a single `Engine`, use `BoundMetaData`: + </ins><span class="cx"> {python} </span><del>- >>> messages = Table('messages', engine, autoload = True) </del><ins>+ engine = create_engine('sqlite://', **kwargs) + + # create BoundMetaData from an Engine + meta = BoundMetaData(engine) + + # create the Engine and MetaData in one step + meta = BoundMetaData('postgres://db/', **kwargs) + +Another form of `MetaData` exists which allows connecting to any number of engines, within the context of the current thread. This is `DynamicMetaData`: + + {python} + meta = DynamicMetaData() + + meta.connect(engine) # connect to an existing Engine + + meta.connect('mysql://user@host/dsn') # create a new Engine and connect + +#### Reflecting Tables + +Once you have a `BoundMetaData` or a connected `DynamicMetaData`, you can create `Table` objects without specifying their columns, just their names, using `autoload=True`: + + {python} + >>> messages = Table('messages', meta, autoload = True) </ins><span class="cx"> >>> [c.name for c in messages.columns] </span><span class="cx"> ['message_id', 'message_name', 'date'] </span><del>- </del><ins>+ +At the moment the Table is constructed, it will query the database for the columns and constraints of the `mytable` table. + </ins><span class="cx"> Note that if a reflected table has a foreign key referencing another table, then the metadata for the related table will be loaded as well, even if it has not been defined by the application: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> shopping_cart_items = Table('shopping_cart_items', engine, autoload = True) </del><ins>+ >>> shopping_cart_items = Table('shopping_cart_items', meta, autoload = True) </ins><span class="cx"> >>> print shopping_cart_items.c.cart_id.table.name </span><span class="cx"> shopping_carts </span><span class="cx"> </span><span class="cx"> To get direct access to 'shopping_carts', simply instantiate it via the Table constructor. You'll get the same instance of the shopping cart Table as the one that is attached to shopping_cart_items: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> shopping_carts = Table('shopping_carts', engine) </del><ins>+ >>> shopping_carts = Table('shopping_carts', meta) </ins><span class="cx"> >>> shopping_carts is shopping_cart_items.c.cart_id.table.name </span><span class="cx"> True </span><span class="cx"> </span><del>-This works because when the Table constructor is called for a particular name and database engine, if the table has already been created then the instance returned will be the same as the original. This is a <b>singleton</b> constructor: </del><ins>+This works because when the Table constructor is called for a particular name and `MetaData` object, if the table has already been created then the instance returned will be the same as the original. This is a <b>singleton</b> constructor: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> news_articles = Table('news', engine, </del><ins>+ >>> news_articles = Table('news', meta, </ins><span class="cx"> ... Column('article_id', Integer, primary_key = True), </span><span class="cx"> ... Column('url', String(250), nullable = False) </span><span class="cx"> ... ) </span><del>- >>> othertable = Table('news', engine) </del><ins>+ >>> othertable = Table('news', meta) </ins><span class="cx"> >>> othertable is news_articles </span><span class="cx"> True </span><span class="cx"> </span><span class="cx"> ### Creating and Dropping Database Tables {@name=creating} </span><span class="cx"> </span><del>-Creating and dropping is easy, just use the `create()` and `drop()` methods: </del><ins>+Creating and dropping individual tables can be done via a `Connection`: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- employees = Table('employees', engine, </del><ins>+ employees = Table('employees', meta, </ins><span class="cx"> Column('employee_id', Integer, primary_key=True), </span><span class="cx"> Column('employee_name', String(60), nullable=False, key='name'), </span><span class="cx"> Column('employee_dept', Integer, ForeignKey("departments.department_id")) </span><span class="cx"> ) </span><ins>+ + conn = engine.connect() + {sql}conn.create(employees) + CREATE TABLE employees( + employee_id SERIAL NOT NULL PRIMARY KEY, + employee_name VARCHAR(60) NOT NULL, + employee_dept INTEGER REFERENCES departments(department_id) + ) + {} + +Or off the `create()` method of the `Table` itself; this method takes an optional `engine` parameter which references an `Engine` or a `Connection`. If not supplied, the `Engine` bound to the `MetaData` will be used, else an error is raised: + + {python} + meta = BoundMetaData('sqlite:///:memory:') + employees = Table('employees', meta, + Column('employee_id', Integer, primary_key=True), + Column('employee_name', String(60), nullable=False, key='name'), + Column('employee_dept', Integer, ForeignKey("departments.department_id")) + ) </ins><span class="cx"> {sql}employees.create() </span><span class="cx"> CREATE TABLE employees( </span><span class="cx"> employee_id SERIAL NOT NULL PRIMARY KEY, </span><span class="lines">@@ -125,11 +189,52 @@ </span><span class="cx"> employee_dept INTEGER REFERENCES departments(department_id) </span><span class="cx"> ) </span><span class="cx"> {} </span><ins>+ +Similarly, both `Connection` and `Table` have a `drop()` method: </ins><span class="cx"> </span><del>- {sql}employees.drop() </del><ins>+ {sql}employees.drop(engine=e) </ins><span class="cx"> DROP TABLE employees </span><span class="cx"> {} </span><ins>+ +Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`, each of which take an optional `engine` keyword argument which can reference an `Engine` or a `Connection`, else the underlying bound `Engine` is used: + + {python} + engine = create_engine('sqlite:///:memory:') </ins><span class="cx"> </span><ins>+ metadata = MetaData() + + users = Table('users', metadata, + Column('user_id', Integer, primary_key = True), + Column('user_name', String(16), nullable = False), + Column('email_address', String(60), key='email'), + Column('password', String(20), nullable = False) + ) + + user_prefs = Table('user_prefs', metadata, + Column('pref_id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey("users.user_id"), nullable=False), + Column('pref_name', String(40), nullable=False), + Column('pref_value', String(100)) + ) + + {sql}metadata.create_all(engine=engine) + PRAGMA table_info(users){} + CREATE TABLE users( + user_id INTEGER NOT NULL PRIMARY KEY, + user_name VARCHAR(16) NOT NULL, + email_address VARCHAR(60), + password VARCHAR(20) NOT NULL + ) + + PRAGMA table_info(user_prefs){} + CREATE TABLE user_prefs( + pref_id INTEGER NOT NULL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(user_id), + pref_name VARCHAR(40) NOT NULL, + pref_value VARCHAR(100) + ) + + </ins><span class="cx"> ### Column Defaults and OnUpdates {@name=defaults} </span><span class="cx"> </span><span class="cx"> SQLAlchemy includes flexible constructs in which to create default values for columns upon the insertion of rows, as well as upon update. These defaults can take several forms: a constant, a Python callable to be pre-executed before the SQL is executed, a SQL expression or function to be pre-executed before the SQL is executed, a pre-executed Sequence (for databases that support sequences), or a "passive" default, which is a default function triggered by the database itself upon insert, the value of which can then be post-fetched by the engine, provided the row provides a primary key in which to call upon. </span><span class="lines">@@ -146,7 +251,7 @@ </span><span class="cx"> i += 1 </span><span class="cx"> return i </span><span class="cx"> </span><del>- t = Table("mytable", db, </del><ins>+ t = Table("mytable", meta, </ins><span class="cx"> # function-based default </span><span class="cx"> Column('id', Integer, primary_key=True, default=mydefault), </span><span class="cx"> </span><span class="lines">@@ -157,7 +262,7 @@ </span><span class="cx"> The "default" keyword can also take SQL expressions, including select statements or direct function calls: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- t = Table("mytable", db, </del><ins>+ t = Table("mytable", meta, </ins><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="cx"> </span><span class="cx"> # define 'create_date' to default to now() </span><span class="lines">@@ -177,7 +282,7 @@ </span><span class="cx"> Similar to an on-insert default is an on-update default, which is most easily specified by the "onupdate" keyword to Column, which also can be a constant, plain Python function or SQL expression: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- t = Table("mytable", db, </del><ins>+ t = Table("mytable", meta, </ins><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="cx"> </span><span class="cx"> # define 'last_updated' to be populated with current_timestamp (the ANSI-SQL version of now()) </span><span class="lines">@@ -195,7 +300,7 @@ </span><span class="cx"> A PassiveDefault indicates a column default or on-update value that is executed automatically by the database. This construct is used to specify a SQL function that will be specified as "DEFAULT" when creating tables, and also to indicate the presence of new data that is available to be "post-fetched" after an insert or update execution. </span><span class="cx"> </span><span class="cx"> {python} </span><del>- t = Table('test', e, </del><ins>+ t = Table('test', meta, </ins><span class="cx"> Column('mycolumn', DateTime, PassiveDefault("sysdate")) </span><span class="cx"> ) </span><span class="cx"> </span><span class="lines">@@ -220,16 +325,16 @@ </span><span class="cx"> Column('data2', Integer, PassiveDefault("d2_func", for_update=True)) </span><span class="cx"> ) </span><span class="cx"> # insert a row </span><del>- mytable.insert().execute(name='fred') </del><ins>+ r = mytable.insert().execute(name='fred') </ins><span class="cx"> </span><del>- # ask the engine: were there defaults fired off on that row ? - if table.engine.lastrow_has_defaults(): </del><ins>+ # check the result: were there defaults fired off on that row ? + if r.lastrow_has_defaults(): </ins><span class="cx"> # postfetch the row based on primary key. </span><span class="cx"> # this only works for a table with primary key columns defined </span><del>- primary_key = table.engine.last_inserted_ids() </del><ins>+ primary_key = r.last_inserted_ids() </ins><span class="cx"> row = table.select(table.c.id == primary_key[0]) </span><span class="cx"> </span><del>-When Tables are reflected from the database using <code>autoload=True</code>, any DEFAULT values set on the columns will be reflected in the Table object as PassiveDefault instances. </del><ins>+When Tables are reflected from the database using `autoload=True`, any DEFAULT values set on the columns will be reflected in the Table object as PassiveDefault instances. </ins><span class="cx"> </span><span class="cx"> ##### The Catch: Postgres Primary Key Defaults always Pre-Execute {@name=postgres} </span><span class="cx"> </span><span class="lines">@@ -241,7 +346,7 @@ </span><span class="cx"> A table with a sequence looks like: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- table = Table("cartitems", db, </del><ins>+ table = Table("cartitems", meta, </ins><span class="cx"> Column("cart_id", Integer, Sequence('cart_id_seq'), primary_key=True), </span><span class="cx"> Column("description", String(40)), </span><span class="cx"> Column("createdate", DateTime()) </span><span class="lines">@@ -249,7 +354,7 @@ </span><span class="cx"> </span><span class="cx"> The Sequence is used with Postgres or Oracle to indicate the name of a Sequence that will be used to create default values for a column. When a table with a Sequence on a column is created by SQLAlchemy, the Sequence object is also created. Similarly, the Sequence is dropped when the table is dropped. Sequences are typically used with primary key columns. When using Postgres, if an integer primary key column defines no explicit Sequence or other default method, SQLAlchemy will create the column with the SERIAL keyword, and will pre-execute a sequence named "tablename_columnname_seq" in order to retrieve new primary key values. Oracle, which has no "auto-increment" keyword, requires that a Sequence be created for a table if automatic primary key generation is desired. Note that for all databases, primary key values can always be explicitly stated within the bind parameters for any insert statement as well, removing the need! for any kind of default generation function. </span><span class="cx"> </span><del>-A Sequence object can be defined on a Table that is then used for a non-sequence-supporting database. In that case, the Sequence object is simply ignored. Note that a Sequence object is <b>entirely optional for all databases except Oracle</b>, as other databases offer options for auto-creating primary key values, such as AUTOINCREMENT, SERIAL, etc. SQLAlchemy will use these default methods for creating primary key values if no Sequence is present on the table metadata. </del><ins>+A Sequence object can be defined on a Table that is then used for a non-sequence-supporting database. In that case, the Sequence object is simply ignored. Note that a Sequence object is **entirely optional for all databases except Oracle**, as other databases offer options for auto-creating primary key values, such as AUTOINCREMENT, SERIAL, etc. SQLAlchemy will use these default methods for creating primary key values if no Sequence is present on the table metadata. </ins><span class="cx"> </span><span class="cx"> A sequence can also be specified with `optional=True` which indicates the Sequence should only be used on a database that requires an explicit sequence, and not those that supply some other method of providing integer values. At the moment, it essentially means "use this sequence only with Oracle and not Postgres". </span><span class="cx"> </span><span class="lines">@@ -258,7 +363,8 @@ </span><span class="cx"> Indexes can be defined on table columns, including named indexes, non-unique or unique, multiple column. Indexes are included along with table create and drop statements. They are not used for any kind of run-time constraint checking; SQLAlchemy leaves that job to the expert on constraint checking, the database itself. </span><span class="cx"> </span><span class="cx"> {python} </span><del>- mytable = Table('mytable', engine, </del><ins>+ boundmeta = BoundMetaData('postgres:///scott:tiger@localhost/test') + mytable = Table('mytable', boundmeta, </ins><span class="cx"> # define a unique index </span><span class="cx"> Column('col1', Integer, unique=True), </span><span class="cx"> </span><span class="lines">@@ -287,50 +393,19 @@ </span><span class="cx"> # which can then be created separately (will also get created with table creates) </span><span class="cx"> i.create() </span><span class="cx"> </span><del>-### Adapting Tables to Alternate Engines {@name=adapting} </del><ins>+### Adapting Tables to Alternate Metadata {@name=adapting} </ins><span class="cx"> </span><del>-A Table object created against a specific engine can be re-created against a new engine using the `toengine` method: </del><ins>+A `Table` object created against a specific `MetaData` object can be re-created against a new engine using the `tometadata` method: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- # create two engines - sqlite_engine = create_engine('sqlite', {'filename':'querytest.db'}) - postgres_engine = create_engine('postgres', - {'database':'test', - 'host':'127.0.0.1', 'user':'scott', 'password':'tiger'}) </del><ins>+ # create two metadata + meta1 = BoundMetaData('sqlite:///querytest.db') + meta2 = MetaData() </ins><span class="cx"> </span><span class="cx"> # load 'users' from the sqlite engine </span><del>- users = Table('users', sqlite_engine, autoload=True) </del><ins>+ users_table = Table('users', meta1, autoload=True) </ins><span class="cx"> </span><del>- # create the same Table object for the other engine - pg_users = users.toengine(postgres_engine) </del><ins>+ # create the same Table object for the plain metadata + users_table_2 = users.tometadata(meta2) </ins><span class="cx"> </span><del>-Also available is the "database neutral" ansisql engine: - - {python} - import sqlalchemy.ansisql as ansisql - generic_engine = ansisql.engine() </del><span class="cx"> </span><del>- users = Table('users', generic_engine, - Column('user_id', Integer), - Column('user_name', String(50)) - ) - -Flexible "multi-engined" tables can also be achieved via the proxy engine, described in the section [dbengine_proxy](rel:dbengine_proxy). - -#### Non-engine primitives: TableClause/ColumnClause {@name=primitives} - -TableClause and ColumnClause are "primitive" versions of the Table and Column objects which dont use engines at all; applications that just want to generate SQL strings but not directly communicate with a database can use TableClause and ColumnClause objects (accessed via 'table' and 'column'), which are non-singleton and serve as the "lexical" base class of Table and Column: - - {python} - tab1 = table('table1', - column('id'), - column('name')) - - tab2 = table('table2', - column('id'), - column('email')) - - tab1.select(tab1.c.name == 'foo') - -TableClause and ColumnClause are strictly lexical. This means they are fully supported within the full range of SQL statement generation, but they don't support schema concepts like creates, drops, primary keys, defaults, nullable status, indexes, or foreign keys. - </del></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -13,7 +13,7 @@ </span><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"> * The sole interface to the unit of work is provided via the `Session` object. Transactional capability, which rides on top of the transactions provided by `Engine` objects, is provided by the `SessionTransaction` object. </span><span class="cx"> * Thread-locally scoped Session behavior is available as an option, which allows new objects to be automatically added to the Session corresponding to by the *default Session context*. Without a default Session context, an application must explicitly create a Session manually as well as add new objects to it. The default Session context, disabled by default, can also be plugged in with other user-defined schemes, which may also take into account the specific class being dealt with for a particular operation. </span><del>-* The Session object in SQLAlchemy 0.2 borrows conceptually from that of [hibernate](http://www.hibernate.org), which is a leading ORM for Java. SQLAlchemy is in general very different from Hibernate, providing a different paradigm for producing queries, a SQL API that is useable independently of the ORM, and of course Pythonic configuration as opposed to XML; however, Hibernate has covered the bases pretty well with its Session ideology. </del><ins>+* The Session object in SQLAlchemy 0.2 borrows conceptually from that of [Hibernate](http://www.hibernate.org), a leading ORM for Java that is largely based on [JSR-220](http://jcp.org/aboutJava/communityprocess/pfd/jsr220/index.html). SQLAlchemy, under no obligation to conform to EJB specifications, is in general very different from Hibernate, providing a different paradigm for producing queries, a SQL API that is useable independently of the ORM, and of course Pythonic configuration as opposed to XML; however, JSR-220/Hibernate makes some pretty good suggestions with regards to the mechanisms of persistence. </ins><span class="cx"> </span><span class="cx"> ### Object States {@name=states} </span><span class="cx"> </span><span class="lines">@@ -140,6 +140,18 @@ </span><span class="cx"> </span><span class="cx"> The `query()` function takes a class or `Mapper` as an argument, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. If a Mapper is passed, then the Query uses that mapper. Otherwise, if a class is sent, it will locate the primary mapper for that class which is used to construct the Query. </span><span class="cx"> </span><ins>+ {python} + # query from a class + session.query(User).select_by(name='ed') + + # query from a mapper + query = session.query(usermapper) + x = query.get(1) + + # query from a class mapped with entity name 'alt_users' + q = session.query(User, entity_name='alt_users') + y = q.options(eagerload('orders')).select() + </ins><span class="cx"> "entity_name" is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the "entity_name" argument, so that a given class can be properly matched to the desired primary mapper. </span><span class="cx"> </span><span class="cx"> All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating Session. </span><span class="lines">@@ -148,10 +160,25 @@ </span><span class="cx"> </span><span class="cx"> Given a class or mapper, a scalar or tuple-based identity, and an optional "entity_name" keyword argument, creates a `Query` corresponding to the given mapper or class/entity_name combination, and calls the `get()` method with the given identity value. If the object already exists within this Session, it is simply returned, else it is queried from the database. If the instance is not found, the method returns `None`. </span><span class="cx"> </span><ins>+ {python} + # get Employer primary key 5 + employer = session.get(Employer, 5) + + # get Report composite primary key 7,12, using mapper 'report_mapper_b' + report = session.get(Report, (7,12), entity_name='report_mapper_b') + + </ins><span class="cx"> #### load() {@name=load} </span><span class="cx"> </span><span class="cx"> load() is similar to get() except it will raise an exception if the instance does not exist in the database. It will also load the object's data from the database in all cases, and **overwrite** all changes on the object if it already exists in the session with the latest data from the database. </span><span class="cx"> </span><ins>+ {python} + # load Employer primary key 5 + employer = session.load(Employer, 5) + + # load Report composite primary key 7,12, using mapper 'report_mapper_b' + report = session.load(Report, (7,12), entity_name='report_mapper_b') + </ins><span class="cx"> #### save() {@name=save} </span><span class="cx"> </span><span class="cx"> save() is called with a single transient (unsaved, unattached) instance as an argument, which is then added to the Session and becomes pending. When the session is next `flush()`ed, the instance will be saved to the database uponwhich it becomes persistent (saved, attached). If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised. </span><span class="lines">@@ -162,6 +189,14 @@ </span><span class="cx"> </span><span class="cx"> The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). </span><span class="cx"> </span><ins>+ {python} + user1 = User(name='user1') + user2 = User(name='user2') + session.save(user1) + session.save(user2) + + session.flush() # write changes to the database + </ins><span class="cx"> #### flush() {@name=flush} </span><span class="cx"> </span><span class="cx"> 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: </span><span class="lines">@@ -321,6 +356,8 @@ </span><span class="cx"> </span><span class="cx"> `SessionTransaction`, like the `Transaction` off of `Connection` also supports "nested" behavior, and is safe to pass to other functions which then issue their own `begin()`/`commit()` pair; only the outermost `begin()`/`commit()` pair actually affects the transaction, and any call to `rollback()` within a particular call stack will issue a rollback. </span><span class="cx"> </span><ins>+Note that while SessionTransaction is capable of tracking multiple transactions across multiple databases, it currently is in no way a fully functioning two-phase commit engine; generally, when dealing with multiple databases simultaneously, there is the distinct possibility that a transaction can succeed on the first database and fail on the second, which for some applications may be an invalid state. If this is an issue, its best to either refrain from spanning transactions across databases, or to look into some of the available technologies in this area, such as [Zope](http://www.zope.org) which offers a two-phase commit engine; some users have already created their own SQLAlchemy/Zope hybrid implementations to deal with scenarios like these. + </ins><span class="cx"> #### Using SQL with SessionTransaction {@name=sql} </span><span class="cx"> </span><span class="cx"> The SessionTransaction can interact with direct SQL queries in two general ways. Either specific `Connection` objects can be associated with the `SessionTransaction`, which are then useable both for direct SQL as well as within `flush()` operations performed by the `SessionTransaction`, or via accessing the `Connection` object automatically referenced within the `SessionTransaction`. </span><span class="lines">@@ -339,7 +376,7 @@ </span><span class="cx"> trans.rollback() </span><span class="cx"> raise </span><span class="cx"> </span><del>-The `add()` method will key the `Connection`'s underlying `Engine` to this `SessionTransaction`. When mapper operations are performed against this `Engine`, the `Connection` explicitly added will be used. This **overrides** any other `Connection` objects that the Session was associated with, corresponding to the underlying `Engine` of that `Connection`. </del><ins>+The `add()` method will key the `Connection`'s underlying `Engine` to this `SessionTransaction`. When mapper operations are performed against this `Engine`, the `Connection` explicitly added will be used. This **overrides** any other `Connection` objects that the underlying Session was associated with, corresponding to the underlying `Engine` of that `Connection`. However, if the `SessionTransaction` itself is already associated with a `Connection`, then an exception is thrown. </ins><span class="cx"> </span><span class="cx"> The other way is just to use the `Connection` referenced by the `SessionTransaction`. This is performed via the `connection()` method, and requires passing in a `Mapper` which indicates which underlying `Connection` should be returned. If the `Mapper` argument is `None`, then the `Session` must be globally bound to a specific `Engine` when it was constructed, else the method returns `None`. </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemydatabasespostgrespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/databases/postgres.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/databases/postgres.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/databases/postgres.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -306,6 +306,20 @@ </span><span class="cx"> text += " OFFSET " + str(select.offset) </span><span class="cx"> return text </span><span class="cx"> </span><ins>+ def visit_select_precolumns(self, select): + if select.distinct: + if type(select.distinct) == bool: + return "DISTINCT " + if type(select.distinct) == list: + dist_set = "DISTINCT ON (" + for col in select.distinct: + dist_set += self.strings[col] + ", " + dist_set = dist_set[:-2] + ") " + return dist_set + return "DISTINCT ON (" + str(select.distinct) + ") " + else: + return "" + </ins><span class="cx"> def binary_operator_string(self, binary): </span><span class="cx"> if isinstance(binary.type, sqltypes.String) and binary.operator == '+': </span><span class="cx"> return '||' </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginebasepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/base.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -331,12 +331,10 @@ </span><span class="cx"> provide a default implementation of SchemaEngine. </span><span class="cx"> """ </span><span class="cx"> </span><del>- def __init__(self, connection_provider, dialect, echo=False, logger=None, echo_uow=False, **kwargs): </del><ins>+ def __init__(self, connection_provider, dialect, echo=False, logger=None, **kwargs): </ins><span class="cx"> self.connection_provider = connection_provider </span><span class="cx"> self.dialect=dialect </span><span class="cx"> self.echo = echo </span><del>- # TODO: echo_uow should be only in Session - self.echo_uow = echo_uow </del><span class="cx"> self.logger = logger or util.Logger(origin='engine') </span><span class="cx"> </span><span class="cx"> def _get_name(self): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginedefaultpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -39,8 +39,7 @@ </span><span class="cx"> </span><span class="cx"> class DefaultDialect(base.Dialect): </span><span class="cx"> """default implementation of Dialect""" </span><del>- def __init__(self, default_ordering=False, convert_unicode=False, encoding='utf-8', **kwargs): - self.default_ordering=default_ordering </del><ins>+ def __init__(self, convert_unicode=False, encoding='utf-8', **kwargs): </ins><span class="cx"> self.convert_unicode = convert_unicode </span><span class="cx"> self.supports_autoclose_results = True </span><span class="cx"> self.encoding = encoding </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -67,7 +67,7 @@ </span><span class="cx"> </span><span class="cx"> class Session(object): </span><span class="cx"> """encapsulates a set of objects being operated upon within an object-relational operation.""" </span><del>- def __init__(self, bind_to=None, hash_key=None, new_imap=True, import_session=None): </del><ins>+ def __init__(self, bind_to=None, hash_key=None, new_imap=True, import_session=None, echo_uow=False): </ins><span class="cx"> if import_session is not None: </span><span class="cx"> self.uow = unitofwork.UnitOfWork(identity_map=import_session.uow.identity_map) </span><span class="cx"> elif new_imap is False: </span><span class="lines">@@ -77,6 +77,7 @@ </span><span class="cx"> </span><span class="cx"> self.bind_to = bind_to </span><span class="cx"> self.binds = {} </span><ins>+ self.echo_uow = echo_uow </ins><span class="cx"> self.transaction = None </span><span class="cx"> if hash_key is None: </span><span class="cx"> self.hash_key = id(self) </span><span class="lines">@@ -229,7 +230,7 @@ </span><span class="cx"> def flush(self, objects=None): </span><span class="cx"> """flushes all the object modifications present in this session to the database. 'objects' </span><span class="cx"> is a list or tuple of objects specifically to be flushed.""" </span><del>- self.uow.flush(self, objects) </del><ins>+ self.uow.flush(self, objects, echo=self.echo_uow) </ins><span class="cx"> </span><span class="cx"> def get(self, class_, ident, **kwargs): </span><span class="cx"> """returns an instance of the object based on the given identifier, or None </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -214,7 +214,7 @@ </span><span class="cx"> except KeyError: </span><span class="cx"> pass </span><span class="cx"> </span><del>- def flush(self, session, objects=None): </del><ins>+ def flush(self, session, objects=None, echo=False): </ins><span class="cx"> flush_context = UOWTransaction(self, session) </span><span class="cx"> </span><span class="cx"> if objects is not None: </span><span class="lines">@@ -236,9 +236,8 @@ </span><span class="cx"> </span><span class="cx"> trans = session.create_transaction(autoflush=False) </span><span class="cx"> flush_context.transaction = trans </span><del>- echo_commit = False </del><span class="cx"> try: </span><del>- flush_context.execute(echo=echo_commit) </del><ins>+ flush_context.execute(echo=echo) </ins><span class="cx"> trans.commit() </span><span class="cx"> except: </span><span class="cx"> trans.rollback() </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/schema.py (1375 => 1376)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-02 21:30:41 UTC (rev 1375) +++ sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-02 21:32:21 UTC (rev 1376) </span><span class="lines">@@ -217,11 +217,17 @@ </span><span class="cx"> issue a SQL DROP statement.""" </span><span class="cx"> key = _get_table_key(self.name, self.schema) </span><span class="cx"> del self.metadata.tables[key] </span><del>- def create(self, **params): - self.engine.create(self) </del><ins>+ def create(self, engine=None): + if engine is not None: + engine.create(self) + else: + self.engine.create(self) </ins><span class="cx"> return self </span><del>- def drop(self, **params): - self.engine.drop(self) </del><ins>+ def drop(self, engine=None): + if engine is not None: + engine.drop(self) + else: + self.engine.drop(self) </ins><span class="cx"> def tometadata(self, metadata, schema=None): </span><span class="cx"> """returns a singleton instance of this Table with a different Schema""" </span><span class="cx"> try: </span><span class="lines">@@ -569,13 +575,17 @@ </span><span class="cx"> % (self.name, column)) </span><span class="cx"> self.columns.append(column) </span><span class="cx"> </span><del>- def create(self): - self.engine.create(self) - return self - def drop(self): - self.engine.drop(self) - def execute(self): - self.create() </del><ins>+ def create(self, engine=None): + if engine is not None: + engine.create(self) + else: + self.engine.create(self) + return self + def drop(self, engine=None): + if engine is not None: + engine.drop(self) + else: + self.engine.drop(self) </ins><span class="cx"> def accept_schema_visitor(self, visitor): </span><span class="cx"> visitor.visit_index(self) </span><span class="cx"> def __str__(self): </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 21:30:51
|
<!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>[1375] sqlalchemy/trunk/lib/sqlalchemy/databases/postgres.py: gambit's patch to add DISTINCT ON</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1375</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-02 16:30:41 -0500 (Tue, 02 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>gambit's patch to add DISTINCT ON</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemydatabasespostgrespy">sqlalchemy/trunk/lib/sqlalchemy/databases/postgres.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemydatabasespostgrespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/databases/postgres.py (1374 => 1375)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/databases/postgres.py 2006-05-02 01:04:04 UTC (rev 1374) +++ sqlalchemy/trunk/lib/sqlalchemy/databases/postgres.py 2006-05-02 21:30:41 UTC (rev 1375) </span><span class="lines">@@ -309,6 +309,20 @@ </span><span class="cx"> text += " OFFSET " + str(select.offset) </span><span class="cx"> return text </span><span class="cx"> </span><ins>+ def visit_select_precolumns(self, select): + if select.distinct: + if type(select.distinct) == bool: + return "DISTINCT " + if type(select.distinct) == list: + dist_set = "DISTINCT ON (" + for col in select.distinct: + dist_set += self.strings[col] + ", " + dist_set = dist_set[:-2] + ") " + return dist_set + return "DISTINCT ON (" + str(select.distinct) + ") " + else: + return "" + </ins><span class="cx"> def binary_operator_string(self, binary): </span><span class="cx"> if isinstance(binary.type, sqltypes.String) and binary.operator == '+': </span><span class="cx"> return '||' </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 01:04:18
|
<!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>[1374] sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py: ST.add() will raise an excpetion if connection already exists</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1374</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 20:04:04 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>ST.add() will raise an excpetion if connection already exists</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1373 => 1374)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-02 00:57:49 UTC (rev 1373) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-02 01:04:04 UTC (rev 1374) </span><span class="lines">@@ -21,10 +21,14 @@ </span><span class="cx"> if self.parent is not None: </span><span class="cx"> return self.parent.connection(mapper) </span><span class="cx"> engine = self.session.get_bind(mapper) </span><del>- return self.add(engine) </del><ins>+ return self.get_or_add(engine) </ins><span class="cx"> def _begin(self): </span><span class="cx"> return SessionTransaction(self.session, self) </span><span class="cx"> def add(self, connection_or_engine): </span><ins>+ if self.connections.has_key(connection_or_engine.engine): + raise InvalidRequestError("Session already has a Connection associated for the given Connection's Engine") + return self.get_or_add(connection_or_engine) + def get_or_add(self, connection_or_engine): </ins><span class="cx"> # we reference the 'engine' attribute on the given object, which in the case of </span><span class="cx"> # Connection, ProxyEngine, Engine, ComposedSQLEngine, whatever, should return the original </span><span class="cx"> # "Engine" object that is handling the connection. </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 00:58:15
|
<!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>[1373] sqlalchemy/branches/schema/doc/build/content/unitofwork.txt: dev</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1373</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 19:57:49 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>dev</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1372 => 1373)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 00:18:31 UTC (rev 1372) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 00:57:49 UTC (rev 1373) </span><span class="lines">@@ -317,10 +317,45 @@ </span><span class="cx"> trans.rollback() </span><span class="cx"> raise </span><span class="cx"> </span><del>-The `create_transaction()` method creates a new SessionTransaction object but does not declare any connection/transaction resources. At the point of the first `get()` call, a connection resource is opened off the engine that corresponds to the Item classes' mapper and is stored within the SessionTransaction with an open Transaction. When `trans.commit()` is called, the `flush()` method is called on the `Session` and the corresponding update statements are issued to the database within the scope of the transaction already opened; afterwards, the underying Transaction is committed, and connection resources are freed. </del><ins>+The `create_transaction()` method creates a new SessionTransaction object but does not declare any connection/transaction resources. At the point of the first `get()` call, a connection resource is opened off the engine that corresponds to the Item classes' mapper and is stored within the `SessionTransaction` with an open `Transaction`. When `trans.commit()` is called, the `flush()` method is called on the `Session` and the corresponding update statements are issued to the database within the scope of the transaction already opened; afterwards, the underying Transaction is committed, and connection resources are freed. </ins><span class="cx"> </span><span class="cx"> `SessionTransaction`, like the `Transaction` off of `Connection` also supports "nested" behavior, and is safe to pass to other functions which then issue their own `begin()`/`commit()` pair; only the outermost `begin()`/`commit()` pair actually affects the transaction, and any call to `rollback()` within a particular call stack will issue a rollback. </span><span class="cx"> </span><ins>+#### Using SQL with SessionTransaction {@name=sql} + +The SessionTransaction can interact with direct SQL queries in two general ways. Either specific `Connection` objects can be associated with the `SessionTransaction`, which are then useable both for direct SQL as well as within `flush()` operations performed by the `SessionTransaction`, or via accessing the `Connection` object automatically referenced within the `SessionTransaction`. + +To associate a specific `Connection` with the `SessionTransaction`, use the `add()` method: + + {python title="Associate a Connection with the SessionTransaction"} + connection = engine.connect() + trans = session.create_transaction() + try: + trans.add(connection) + connection.execute(mytable.update(), {'col1':4, 'col2':17}) + session.flush() # flush() operation will use the same connection + trans.commit() + except: + trans.rollback() + raise + +The `add()` method will key the `Connection`'s underlying `Engine` to this `SessionTransaction`. When mapper operations are performed against this `Engine`, the `Connection` explicitly added will be used. This **overrides** any other `Connection` objects that the Session was associated with, corresponding to the underlying `Engine` of that `Connection`. + +The other way is just to use the `Connection` referenced by the `SessionTransaction`. This is performed via the `connection()` method, and requires passing in a `Mapper` which indicates which underlying `Connection` should be returned. If the `Mapper` argument is `None`, then the `Session` must be globally bound to a specific `Engine` when it was constructed, else the method returns `None`. + + {python title="Get a Connection from the SessionTransaction"} + trans = session.create_transaction() + try: + usermapper = session.get_mapper(UserClass) # a convenience method to get a Mapper + connection = trans.connection(usermapper) # get the Connection used by the UserClass' Mapper + connection.execute(mytable.update(), {'col1':4, 'col2':17}) + trans.commit() + except: + trans.rollback() + raise + +The `connection()` method also exists on the `Session` object itself, and can be called regardless of whether or not a `SessionTransaction` is in progress. If an `Engine` is being used with `threadlocal` strategy, the `Connection` returned will correspond to the connection resources that are bound to the current thread, if any. + </ins><span class="cx"> ### Analyzing Object Flushes {@name=logging} </span><span class="cx"> </span><span class="cx"> The session module can log an extensive display of its "flush plans", which is a graph of its internal representation of objects before they are written to the database. To turn this logging on: </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-02 00:18:43
|
<!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>[1372] sqlalchemy/branches/schema/doc/build/content/unitofwork.txt: basic idea is completed....pending edits</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1372</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 19:18:31 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>basic idea is completed....pending edits</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1371 => 1372)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 23:56:37 UTC (rev 1371) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-02 00:18:31 UTC (rev 1372) </span><span class="lines">@@ -19,13 +19,13 @@ </span><span class="cx"> </span><span class="cx"> When dealing with mapped instances with regards to Sessions, an instance may be *attached* or *unattached* to a particular Session. An instance also may or may not correspond to an actual row in the database. The product of these two binary conditions yields us four general states a particular instance can have within the perspective of the Session: </span><span class="cx"> </span><del>-* Transient - a transient instance exists within memory only and is not associated with any Session. It also has no database identity and does not have a corresponding record in the database. When a new instance of a class is constructed, and no default session context exists with which to automatically attach the new instance, it is a transient instance. The instance can then be saved to a particular session in which case it becomes a *pending* instance. If a default session context exists, new instances are added to that Session by default and therefore become *pending* instances immediately. </del><ins>+* *Transient* - a transient instance exists within memory only and is not associated with any Session. It also has no database identity and does not have a corresponding record in the database. When a new instance of a class is constructed, and no default session context exists with which to automatically attach the new instance, it is a transient instance. The instance can then be saved to a particular session in which case it becomes a *pending* instance. If a default session context exists, new instances are added to that Session by default and therefore become *pending* instances immediately. </ins><span class="cx"> </span><del>-* Pending - a pending instance is a Session-attached object that has not yet been assigned a database identity. When the Session is flushed (i.e. changes are persisted to the database), a pending instance becomes persistent. </del><ins>+* *Pending* - a pending instance is a Session-attached object that has not yet been assigned a database identity. When the Session is flushed (i.e. changes are persisted to the database), a pending instance becomes persistent. </ins><span class="cx"> </span><del>-* Persistent - a persistent instance has a database identity and a corresponding record in the database, and is also associated with a particular Session. By "database identity" we mean the object is associated with a table or relational concept in the database combined with a particular primary key in that table. Objects that are loaded by SQLAlchemy in the context of a particular session are automatically considered persistent, as are formerly pending instances which have been subject to a session `flush()`. </del><ins>+* *Persistent* - a persistent instance has a database identity and a corresponding record in the database, and is also associated with a particular Session. By "database identity" we mean the object is associated with a table or relational concept in the database combined with a particular primary key in that table. Objects that are loaded by SQLAlchemy in the context of a particular session are automatically considered persistent, as are formerly pending instances which have been subject to a session `flush()`. </ins><span class="cx"> </span><del>-* Detached - a detached instance is an instance which has a database identity and corresponding row in the database, but is not attached to any Session. This occurs when an instance has been removed from a Session, either because the session itself was cleared or closed, or the instance was explicitly removed from the Session. The object can be re-attached with a session again in which case it becomes Persistent again. Detached instances are useful when an application needs to represent a long-running operation across multiple Sessions, needs to store an object in a serialized state and then restore it later (such as within an HTTP "session" object), or in some cases where code needs to load instances locally which will later be associated with some other Session. </del><ins>+* *Detached* - a detached instance is an instance which has a database identity and corresponding row in the database, but is not attached to any Session. This occurs when an instance has been removed from a Session, either because the session itself was cleared or closed, or the instance was explicitly removed from the Session. The object can be re-attached with a session again in which case it becomes Persistent again. Detached instances are useful when an application needs to represent a long-running operation across multiple Sessions, needs to store an object in a serialized state and then restore it later (such as within an HTTP "session" object), or in some cases where code needs to load instances locally which will later be associated with some other Session. </ins><span class="cx"> </span><span class="cx"> ### Acquiring a Session {@name=getting} </span><span class="cx"> </span><span class="lines">@@ -51,7 +51,7 @@ </span><span class="cx"> session = create_session(bind_to=conn) </span><span class="cx"> </span><span class="cx"> </span><del>-The session to which an object is attached can be acquired via the `object_session()` method, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: </del><ins>+The session to which an object is attached can be acquired via the `object_session()` function, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> session = object_session(obj) </span><span class="lines">@@ -238,7 +238,7 @@ </span><span class="cx"> </span><span class="cx"> 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. </span><span class="cx"> </span><del>-#### bind_mapper()/bind_table() {@name=bind} </del><ins>+#### bind\_mapper() / bind\_table() {@name=bind} </ins><span class="cx"> </span><span class="cx"> Both of these methods receive two arguments; in the case of `bind_mapper()`, it is a `Mapper` and an `Engine` or `Connection` instance; in the case of `bind_table()`, it is a `Table` instance or other `Selectable` (such as an `Alias`, `Select`, etc.), and an `Engine` or `Connection` instance. </span><span class="cx"> </span><span class="lines">@@ -254,7 +254,7 @@ </span><span class="cx"> </span><span class="cx"> The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). </span><span class="cx"> </span><del>-#### save_or_update() {@name=save_or_update} </del><ins>+#### save\_or\_update() {@name=saveorupdate} </ins><span class="cx"> </span><span class="cx"> This method is a combination of the `save()` and `update()` methods, which will examine the given instance for a database identity (i.e. if it is transient or detached), and will call the implementation of `save()` or `update()` as appropriate. Use `save_or_update()` to add unattached instances to a session when you're not sure if they were newly created or not. </span><span class="cx"> </span><span class="lines">@@ -296,6 +296,8 @@ </span><span class="cx"> </span><span class="cx"> Note that cascading doesn't do anything that isn't possible by manually calling Session methods on individual instances within a hierarchy, it merely automates common operations on a group of associated instances. </span><span class="cx"> </span><ins>+The default value for `cascade` on `relation()`s is `save-update`, and the `private=True` keyword argument is a synonym for `cascade="all, delete-orphan"`. + </ins><span class="cx"> ### SessionTransaction {@name=transaction} </span><span class="cx"> </span><span class="cx"> SessionTransaction is a multi-engine transaction manager, which aggregates one or more Engine/Connection pairs and keeps track of a Transaction object for each one. As the Session receives requests to execute SQL statements, it uses the Connection that is referenced by the SessionTransaction. At commit time, the underyling Session is flushed, and each Transaction is the committed. </span><span class="lines">@@ -319,15 +321,15 @@ </span><span class="cx"> </span><span class="cx"> `SessionTransaction`, like the `Transaction` off of `Connection` also supports "nested" behavior, and is safe to pass to other functions which then issue their own `begin()`/`commit()` pair; only the outermost `begin()`/`commit()` pair actually affects the transaction, and any call to `rollback()` within a particular call stack will issue a rollback. </span><span class="cx"> </span><del>-#### Analyzing Object Commits {@name=logging} </del><ins>+### Analyzing Object Flushes {@name=logging} </ins><span class="cx"> </span><del>-The objectstore module can log an extensive display of its "commit plans", which is a graph of its internal representation of objects before they are committed to the database. To turn this logging on: </del><ins>+The session module can log an extensive display of its "flush plans", which is a graph of its internal representation of objects before they are written to the database. To turn this logging on: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # make an engine with echo_uow </span><span class="cx"> engine = create_engine('myengine...', echo_uow=True) </span><span class="cx"> </span><del>-Commits will then dump to the standard output displays like the following: </del><ins>+The `flush()` operation will then dump to the standard output displays like the following: </ins><span class="cx"> </span><span class="cx"> {code} </span><span class="cx"> Task dump: </span><span class="lines">@@ -355,4 +357,8 @@ </span><span class="cx"> |---- </span><span class="cx"> </span><span class="cx"> The above graph can be read straight downwards to determine the order of operations. It indicates "save User 6016624, process each element in the 'addresses' list on User 6016624, save Address 6034384, Address 6034256". </span><ins>+ +Of course, one can also get a good idea of the order of operations just by logging the actual SQL statements executed. + +The format of the above display is definitely a work in progress and amazingly, is far simpler to read than it was in earlier releases. It will hopefully be further refined in future releases to be more intuitive (while not losing any information). </ins><span class="cx"> </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-01 23:56:49
|
<!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>[1371] sqlalchemy/branches/schema/doc/build/content: doc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1371</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 18:56:37 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormutilpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1370 => 1371)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 23:07:48 UTC (rev 1370) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 23:56:37 UTC (rev 1371) </span><span class="lines">@@ -138,12 +138,30 @@ </span><span class="cx"> </span><span class="cx"> #### query() {@name=query} </span><span class="cx"> </span><ins>+The `query()` function takes a class or `Mapper` as an argument, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. If a Mapper is passed, then the Query uses that mapper. Otherwise, if a class is sent, it will locate the primary mapper for that class which is used to construct the Query. + +"entity_name" is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the "entity_name" argument, so that a given class can be properly matched to the desired primary mapper. + +All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating Session. + </ins><span class="cx"> #### get() {@name=get} </span><span class="cx"> </span><ins>+Given a class or mapper, a scalar or tuple-based identity, and an optional "entity_name" keyword argument, creates a `Query` corresponding to the given mapper or class/entity_name combination, and calls the `get()` method with the given identity value. If the object already exists within this Session, it is simply returned, else it is queried from the database. If the instance is not found, the method returns `None`. + </ins><span class="cx"> #### load() {@name=load} </span><span class="cx"> </span><ins>+load() is similar to get() except it will raise an exception if the instance does not exist in the database. It will also load the object's data from the database in all cases, and **overwrite** all changes on the object if it already exists in the session with the latest data from the database. + </ins><span class="cx"> #### save() {@name=save} </span><span class="cx"> </span><ins>+save() is called with a single transient (unsaved, unattached) instance as an argument, which is then added to the Session and becomes pending. When the session is next `flush()`ed, the instance will be saved to the database uponwhich it becomes persistent (saved, attached). If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised. + +save() is called automatically for new instances by the classes' associated mapper, if a default Session context is in effect (such as a thread-local session), which means that newly created instances automatically become pending. If there is no default session available, then the instance remains transient (unattached) until it is explicitly added to a Session via the save() method. + +A transient instance also can be automatically `save()`ed if it is associated with a parent object which specifies `save-update` within its *cascade* rules, and that parent is already attached or becomes attached to a Session. For more information on cascade, see the next section. + +The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). + </ins><span class="cx"> #### flush() {@name=flush} </span><span class="cx"> </span><span class="cx"> 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: </span><span class="lines">@@ -170,6 +188,8 @@ </span><span class="cx"> </span><span class="cx"> #### close() {@name=close} </span><span class="cx"> </span><ins>+This method first calls `clear()`, removing all objects from this Session, and then insures that any transactional resources are closed. + </ins><span class="cx"> #### delete() {@name=delete} </span><span class="cx"> </span><span class="cx"> The delete call places an instance into the Unit of Work's list of objects to be marked as deleted: </span><span class="lines">@@ -182,7 +202,7 @@ </span><span class="cx"> # flush </span><span class="cx"> session.flush() </span><span class="cx"> </span><del>-The delete operation will have an effect on instances that are attached to the deleted instance according to the `cascade` style of the relationship. By default, associated instances may need to be updated upon flush in order to reflect that they no longer are associated with the parent object, before the parent is deleted. If the relationship specifies `cascade="delete"`, then the associated instance will also be deleted upon flush, assuming it is still attached to the parent. If the relationship additionally includes the `delete-orphan` cascade style, the associated instance will be deleted if it is still attached to the parent, or is unattached to any other parent. </del><ins>+The delete operation will have an effect on instances that are attached to the deleted instance according to the `cascade` style of the relationship; cascade rules are described further in the following section. By default, associated instances may need to be updated in the database to reflect that they no longer are associated with the parent object, before the parent is deleted. If the relationship specifies `cascade="delete"`, then the associated instance will also be deleted upon flush, assuming it is still attached to the parent. If the relationship additionally includes the `delete-orphan` cascade style, the associated instance will be deleted if it is still attached to the parent, or is unattached to any other parent. </ins><span class="cx"> </span><span class="cx"> The `delete()` operation has no relationship to the in-memory status of the instance, including usage of the `del` Python statement. An instance marked as deleted and flushed will still exist within memory until references to it are freed; similarly, removing an instance from memory via the `del` statement will have no effect, since the persistent instance will still be referenced by its Session. Obviously, if the instance is removed from the Session and then totally dereferenced, it will no longer exist in memory, but also won't exist in any Session and is therefore not deleted from the database. </span><span class="cx"> </span><span class="lines">@@ -220,26 +240,85 @@ </span><span class="cx"> </span><span class="cx"> #### bind_mapper()/bind_table() {@name=bind} </span><span class="cx"> </span><ins>+Both of these methods receive two arguments; in the case of `bind_mapper()`, it is a `Mapper` and an `Engine` or `Connection` instance; in the case of `bind_table()`, it is a `Table` instance or other `Selectable` (such as an `Alias`, `Select`, etc.), and an `Engine` or `Connection` instance. + +Normally, when a Session is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retreieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raiased. A second form of `create_session()` takes the argument `bind_to=engine_or_connection`, where all operations performed by this Session are done via the single Engine or Connection passed to the constructor. + +The point of these methods is to bind individual mapper and/or table operations to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the constructor. Configurations which interact with multiple explicit database connections at one time must use either or both of these methods in order to associate Session operations with the appropriate connection resource. + +Binding a `Mapper` to a resource takes precedence over a `Table` bind, meaning if mapper A is associated with table B, and the Session binds mapper A to connection X and table B to connection Y, an opertaion with mapper A will use connection X, not connection Y. + </ins><span class="cx"> #### update() {@name=update} </span><span class="cx"> </span><ins>+The update() method is used *only* with detached instances. A detached instance only exists if its `Session` was cleared or closed, or the instance was `expunge()`d from its session. `update()` will re-attach the detached instance with this Session, bringing it back to the persistent state. If the instance is already attached to an existing Session, an exception is raised. + +The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). + </ins><span class="cx"> #### save_or_update() {@name=save_or_update} </span><span class="cx"> </span><ins>+This method is a combination of the `save()` and `update()` methods, which will examine the given instance for a database identity (i.e. if it is transient or detached), and will call the implementation of `save()` or `update()` as appropriate. Use `save_or_update()` to add unattached instances to a session when you're not sure if they were newly created or not. + </ins><span class="cx"> #### merge() {@name=merge} </span><span class="cx"> </span><del>-The _instance_key attribute placed on object instances is designed to work with objects that are serialized into strings and brought back again. As it contains no references to internal structures or database connections, applications that use caches or session storage which require serialization (i.e. pickling) can store SQLAlchemy-loaded objects. However, as mentioned earlier, an object with a particular database identity is only allowed to exist uniquely within the current unit-of-work scope. So, upon deserializing such an object, it has to "check in" with the current Session. This is achieved via the `import_instance()` method: </del><ins>+`merge()` is used to return the persistent version of an instance that is not attached to this Session. When passed an instance, if an instance with its database identity already exists within this Session, it is returned. If the instance does not exist in this Session, it is loaded from the database and then returned. </ins><span class="cx"> </span><ins>+A future version of `merge()` will also update the Session's instance with the state of the given instance (hence the name "merge"). + +This method is useful for bringing in objects which may have been restored from a serialization, such as those stored in an HTTP session: + </ins><span class="cx"> {python} </span><span class="cx"> # deserialize an object </span><span class="cx"> myobj = pickle.loads(mystring) </span><span class="cx"> </span><del>- # "import" it. if the objectstore already had this object in the </del><ins>+ # "merge" it. if the session already had this object in the </ins><span class="cx"> # identity map, then you get back the one from the current session. </span><del>- myobj = session.import_instance(myobj) </del><ins>+ myobj = session.merge(myobj) + +Note that `merge()` *does not* associate the given instance with the Session; it remains detached (or attached to whatever Session it was already attached to). </ins><span class="cx"> </span><del>-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. </del><ins>+### Cascade rules {@name=cascade} </ins><span class="cx"> </span><ins>+Mappers support the concept of configurable *cascade* behavior on `relation()`s. This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session. Cascade is indicated as a comma-separated list of string keywords, with the possible values `all`, `delete`, `save-update`, `refresh-expire`, `merge`, `expunge`, and `delete-orphan`. </ins><span class="cx"> </span><ins>+Cascading is configured by setting the `cascade` keyword argument on a `relation()`: </ins><span class="cx"> </span><ins>+ {python} + mapper(Order, order_table, properties={ + 'items' : relation(Item, items_table, cascade="all, delete-orphan"), + 'customer' : relation(User, users_table, user_orders_table, cascade="save-update"), + }) + +The above mapper specifies two relations, `items` and `customer`. The `items` relationship specifies "all, delete-orphan" as its `cascade` value, indicating that all `save`, `update`, `merge`, `expunge`, `refresh` `delete` and `expire` operations performed on a parent `Order` instance should also be performed on the child `Item` instances attached to it (`save` and `update` are cascaded using the `save_or_update()` method, so that the database identity of the instance doesn't matter). The `delete-orphan` cascade value additionally indicates that if an `Item` instance is no longer associated with an `Order`, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an `Order` and an `Item` object. + +The `customer` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent `Order` instance to a child `User` instance, except for if the `Order` is attached with a particular session, either via the `save()`, `update()`, or `save-update()` method. + +Additionally, when a child item is attached to a parent item that specifies the "save-update" cascade value on the relationship, the child is automatically passed to `save_or_update()` (and the operation is further cascaded to the child item). + +Note that cascading doesn't do anything that isn't possible by manually calling Session methods on individual instances within a hierarchy, it merely automates common operations on a group of associated instances. + +### SessionTransaction {@name=transaction} + +SessionTransaction is a multi-engine transaction manager, which aggregates one or more Engine/Connection pairs and keeps track of a Transaction object for each one. As the Session receives requests to execute SQL statements, it uses the Connection that is referenced by the SessionTransaction. At commit time, the underyling Session is flushed, and each Transaction is the committed. + +Example usage is as follows: + + {python} + sess = create_session() + trans = sess.create_transaction() + try: + item1 = sess.query(Item).get(1) + item2 = sess.query(Item).get(2) + item1.foo = 'bar' + item2.bar = 'foo' + trans.commit() + except: + trans.rollback() + raise + +The `create_transaction()` method creates a new SessionTransaction object but does not declare any connection/transaction resources. At the point of the first `get()` call, a connection resource is opened off the engine that corresponds to the Item classes' mapper and is stored within the SessionTransaction with an open Transaction. When `trans.commit()` is called, the `flush()` method is called on the `Session` and the corresponding update statements are issued to the database within the scope of the transaction already opened; afterwards, the underying Transaction is committed, and connection resources are freed. + +`SessionTransaction`, like the `Transaction` off of `Connection` also supports "nested" behavior, and is safe to pass to other functions which then issue their own `begin()`/`commit()` pair; only the outermost `begin()`/`commit()` pair actually affects the transaction, and any call to `rollback()` within a particular call stack will issue a rollback. + </ins><span class="cx"> #### Analyzing Object Commits {@name=logging} </span><span class="cx"> </span><span class="cx"> The objectstore module can log an extensive display of its "commit plans", which is a graph of its internal representation of objects before they are committed to the database. To turn this logging on: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormutilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py (1370 => 1371)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py 2006-05-01 23:07:48 UTC (rev 1370) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py 2006-05-01 23:56:37 UTC (rev 1371) </span><span class="lines">@@ -15,6 +15,7 @@ </span><span class="cx"> self.save_update = "save-update" in values or "all" in values </span><span class="cx"> self.merge = "merge" in values or "all" in values </span><span class="cx"> self.expunge = "expunge" in values or "all" in values </span><ins>+ self.refresh_expire = "refresh-expire" in values or "all" in values </ins><span class="cx"> def __contains__(self, item): </span><span class="cx"> return getattr(self, item.replace("-", "_"), False) </span><span class="cx"> </span><span class="cx">\ No newline at end of file </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-01 23:08:06
|
<!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>[1370] sqlalchemy/branches/schema/test: took out get(*ident) style of argument, its now a scalar or tuple</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1370</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 18:07:48 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>took out get(*ident) style of argument, its now a scalar or tuple</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormmapperpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormquerypy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschematesttablespy">sqlalchemy/branches/schema/test/tables.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/mapper.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -274,9 +274,9 @@ </span><span class="cx"> self._query = querylib.Query(self) </span><span class="cx"> return self._query </span><span class="cx"> </span><del>- def get(self, *ident, **kwargs): </del><ins>+ def get(self, ident, **kwargs): </ins><span class="cx"> """calls get() on this mapper's default Query object.""" </span><del>- return self.query().get(*ident, **kwargs) </del><ins>+ return self.query().get(ident, **kwargs) </ins><span class="cx"> </span><span class="cx"> def _get(self, key, ident=None, reload=False): </span><span class="cx"> return self.query()._get(key, ident=ident, reload=reload) </span><span class="lines">@@ -464,13 +464,13 @@ </span><span class="cx"> result = [result] + otherresults </span><span class="cx"> return result </span><span class="cx"> </span><del>- def identity_key(self, *primary_key): </del><ins>+ def identity_key(self, primary_key): </ins><span class="cx"> """returns the instance key for the given identity value. this is a global tracking object used by the Session, and is usually available off a mapped object as instance._instance_key.""" </span><del>- return sessionlib.get_id_key(tuple(primary_key), self.class_, self.entity_name) </del><ins>+ return sessionlib.get_id_key(util.to_list(primary_key), self.class_, self.entity_name) </ins><span class="cx"> </span><span class="cx"> def instance_key(self, instance): </span><span class="cx"> """returns the instance key for the given instance. this is a global tracking object used by the Session, and is usually available off a mapped object as instance._instance_key.""" </span><del>- return self.identity_key(*self.identity(instance)) </del><ins>+ return self.identity_key(self.identity(instance)) </ins><span class="cx"> </span><span class="cx"> def identity(self, instance): </span><span class="cx"> """returns the identity (list of primary key values) for the given instance. The list of values can be fed directly into the get() method as mapper.get(*key).""" </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -391,7 +391,7 @@ </span><span class="cx"> for primary_key in self.mapper.pks_by_table[self.mapper.select_table]: </span><span class="cx"> bind = self.lazyreverse[primary_key] </span><span class="cx"> ident.append(params[bind.key]) </span><del>- return self.mapper.using(session).get(*ident) </del><ins>+ return self.mapper.using(session).get(ident) </ins><span class="cx"> elif self.order_by is not False: </span><span class="cx"> order_by = self.order_by </span><span class="cx"> elif self.secondary is not None and self.secondary.default_order_by() is not None: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormquerypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -35,19 +35,19 @@ </span><span class="cx"> props = property(lambda s:s.mapper.props) </span><span class="cx"> session = property(_get_session) </span><span class="cx"> </span><del>- def get(self, *ident, **kwargs): </del><ins>+ def get(self, ident, **kwargs): </ins><span class="cx"> """returns an instance of the object based on the given identifier, or None </span><del>- if not found. The *ident argument is a - list of primary key columns in the order of the table def's primary key columns.""" - key = self.mapper.identity_key(*ident) </del><ins>+ if not found. The ident argument is a scalar or tuple of primary key column values + in the order of the table def's primary key columns.""" + key = self.mapper.identity_key(ident) </ins><span class="cx"> return self._get(key, ident, **kwargs) </span><span class="cx"> </span><del>- def load(self, *ident, **kwargs): </del><ins>+ def load(self, ident, **kwargs): </ins><span class="cx"> """returns an instance of the object based on the given identifier. If not found, </span><span class="cx"> raises an exception. The method will *remove all pending changes* to the object </span><del>- already existing in the Session. The *ident argument is a - list of primary key columns in the order of the table def's primary key columns.""" - key = self.mapper.identity_key(*ident) </del><ins>+ already existing in the Session. The ident argument is a scalar or tuple of primary + key column values in the order of the table def's primary key columns.""" + key = self.mapper.identity_key(ident) </ins><span class="cx"> instance = self._get(key, ident, reload=True, **kwargs) </span><span class="cx"> if instance is None: </span><span class="cx"> raise exceptions.InvalidRequestError("No instance found for identity %s" % repr(ident)) </span><span class="lines">@@ -215,6 +215,8 @@ </span><span class="cx"> </span><span class="cx"> if ident is None: </span><span class="cx"> ident = key[1] </span><ins>+ else: + ident = util.to_list(ident) </ins><span class="cx"> i = 0 </span><span class="cx"> params = {} </span><span class="cx"> for primary_key in self.mapper.pks_by_table[self.table]: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -227,26 +227,26 @@ </span><span class="cx"> is a list or tuple of objects specifically to be flushed.""" </span><span class="cx"> self.uow.flush(self, objects) </span><span class="cx"> </span><del>- def get(self, class_, *ident, **kwargs): </del><ins>+ def get(self, class_, ident, **kwargs): </ins><span class="cx"> """returns an instance of the object based on the given identifier, or None </span><del>- if not found. The *ident argument is a list of primary key columns in the order of the </del><ins>+ if not found. The ident argument is a scalar or tuple of primary key column values in the order of the </ins><span class="cx"> table def's primary key columns. </span><span class="cx"> </span><span class="cx"> the entity_name keyword argument may also be specified which further qualifies the underlying </span><span class="cx"> Mapper used to perform the query.""" </span><span class="cx"> entity_name = kwargs.get('entity_name', None) </span><del>- return self.query(class_, entity_name=entity_name).get(*ident) </del><ins>+ return self.query(class_, entity_name=entity_name).get(ident) </ins><span class="cx"> </span><del>- def load(self, class_, *ident, **kwargs): </del><ins>+ def load(self, class_, ident, **kwargs): </ins><span class="cx"> """returns an instance of the object based on the given identifier. If not found, </span><span class="cx"> raises an exception. The method will *remove all pending changes* to the object </span><del>- already existing in the Session. The *ident argument is a - list of primary key columns in the order of the table def's primary key columns. </del><ins>+ already existing in the Session. The ident argument is a scalar or tuple of + primary key columns in the order of the table def's primary key columns. </ins><span class="cx"> </span><span class="cx"> the entity_name keyword argument may also be specified which further qualifies the underlying </span><span class="cx"> Mapper used to perform the query.""" </span><span class="cx"> entity_name = kwargs.get('entity_name', None) </span><del>- return self.query(class_, entity_name=entity_name).load(*ident) </del><ins>+ return self.query(class_, entity_name=entity_name).load(ident) </ins><span class="cx"> </span><span class="cx"> def refresh(self, object): </span><span class="cx"> """reloads the attributes for the given object from the database, clears </span><span class="lines">@@ -310,7 +310,7 @@ </span><span class="cx"> for k in ident: </span><span class="cx"> if k is None: </span><span class="cx"> raise InvalidRequestError("Instance '%s' does not have a full set of identity values, and does not represent a saved entity in the database. Use the add() method to add unsaved instances to this Session." % repr(obj)) </span><del>- key = mapper.identity_key(*ident) </del><ins>+ key = mapper.identity_key(ident) </ins><span class="cx"> u = self.uow </span><span class="cx"> if u.identity_map.has_key(key): </span><span class="cx"> # TODO: copy the state of the given object into this one. tricky ! </span></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -218,7 +218,7 @@ </span><span class="cx"> e.multi_rev = 2 </span><span class="cx"> objectstore.flush() </span><span class="cx"> objectstore.clear() </span><del>- e2 = Entry.mapper.get(e.multi_id, 2) </del><ins>+ e2 = Entry.mapper.get((e.multi_id, 2)) </ins><span class="cx"> self.assert_(e is not e2 and e._instance_key == e2._instance_key) </span><span class="cx"> def testmanualpk(self): </span><span class="cx"> class Entry(object): </span></span></pre></div> <a id="sqlalchemybranchesschematesttablespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/tables.py (1369 => 1370)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/tables.py 2006-05-01 21:53:27 UTC (rev 1369) +++ sqlalchemy/branches/schema/test/tables.py 2006-05-01 23:07:48 UTC (rev 1370) </span><span class="lines">@@ -56,7 +56,7 @@ </span><span class="cx"> def drop(): </span><span class="cx"> metadata.drop_all() </span><span class="cx"> def delete(): </span><del>- for t in metadata.table_iterator(): </del><ins>+ for t in metadata.table_iterator(reverse=False): </ins><span class="cx"> t.delete().execute() </span><span class="cx"> def user_data(): </span><span class="cx"> users.insert().execute( </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-01 21:53:44
|
<!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>[1369] sqlalchemy/branches/schema/test: de-threadlocalified the 'mapper' test suite, other work with test suites, some fixes to session/sqlite</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1369</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 16:53:27 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>de-threadlocalified the 'mapper' test suite, other work with test suites, some fixes to session/sqlite</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemydatabasessqlitepy">sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymodsthreadlocalpy">sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyschemapy">sqlalchemy/branches/schema/lib/sqlalchemy/schema.py</a></li> <li><a href="#sqlalchemybranchesschematestdependencypy">sqlalchemy/branches/schema/test/dependency.py</a></li> <li><a href="#sqlalchemybranchesschematesteagertest1py">sqlalchemy/branches/schema/test/eagertest1.py</a></li> <li><a href="#sqlalchemybranchesschematestindexespy">sqlalchemy/branches/schema/test/indexes.py</a></li> <li><a href="#sqlalchemybranchesschematestmapperpy">sqlalchemy/branches/schema/test/mapper.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschematestrelationshipspy">sqlalchemy/branches/schema/test/relationships.py</a></li> <li><a href="#sqlalchemybranchesschematesttablespy">sqlalchemy/branches/schema/test/tables.py</a></li> <li><a href="#sqlalchemybranchesschematesttestbasepy">sqlalchemy/branches/schema/test/testbase.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemydatabasessqlitepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/lib/sqlalchemy/databases/sqlite.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -156,7 +156,8 @@ </span><span class="cx"> </span><span class="cx"> def has_table(self, connection, table_name): </span><span class="cx"> cursor = connection.execute("PRAGMA table_info(" + table_name + ")", {}) </span><del>- return bool(cursor.rowcount>0) </del><ins>+ row = cursor.fetchone() + return (row is not None) </ins><span class="cx"> </span><span class="cx"> def reflecttable(self, connection, table): </span><span class="cx"> c = connection.execute("PRAGMA table_info(" + table.name + ")", {}) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymodsthreadlocalpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mods/threadlocal.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -111,11 +111,16 @@ </span><span class="cx"> class_.expire = expire </span><span class="cx"> class_.refresh = refresh </span><span class="cx"> class_.expunge = expunge </span><ins>+ </ins><span class="cx"> def install_plugin(): </span><span class="cx"> reg = util.ScopedRegistry(session.Session) </span><span class="cx"> session.register_default_session(lambda *args, **kwargs: reg()) </span><span class="cx"> engine.default_strategy = 'threadlocal' </span><span class="cx"> sqlalchemy.objectstore = Objectstore() </span><span class="cx"> sqlalchemy.assign_mapper = assign_mapper </span><ins>+ +def uninstall_plugin(): + session.register_default_session(lambda *args, **kwargs:None) + engine.default_strategy = 'plain' </ins><span class="cx"> </span><span class="cx"> install_plugin() </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -25,13 +25,20 @@ </span><span class="cx"> def _begin(self): </span><span class="cx"> return SessionTransaction(self.session, self) </span><span class="cx"> def add(self, connection_or_engine): </span><del>- if self.connections.has_key(connection_or_engine): - return self.connections[connection_or_engine][0] - c = connection_or_engine.contextual_connect() - e = c.engine - if not self.connections.has_key(e): - self.connections[e] = (c, c.begin()) - return self.connections[e][0] </del><ins>+ # we reference the 'engine' attribute on the given object, which in the case of + # Connection, ProxyEngine, Engine, ComposedSQLEngine, whatever, should return the original + # "Engine" object that is handling the connection. + if self.connections.has_key(connection_or_engine.engine): + return self.connections[connection_or_engine.engine][0] + if isinstance(connection_or_engine, sqlalchemy.engine.base.Connection): + e = connection_or_engine.engine + c = connection_or_engine + else: + e = connection_or_engine + c = connection_or_engine.contextual_connect() + if not self.connections.has_key(e.engine): + self.connections[e.engine] = (c, c.begin()) + return self.connections[e.engine][0] </ins><span class="cx"> def commit(self): </span><span class="cx"> if self.parent is not None: </span><span class="cx"> return </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyschemapy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/schema.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/lib/sqlalchemy/schema.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -594,7 +594,11 @@ </span><span class="cx"> self.name = name </span><span class="cx"> def is_bound(self): </span><span class="cx"> return False </span><del>- </del><ins>+ def clear(self): + self.tables.clear() + def table_iterator(self, reverse=True): + return self._sort_tables(self.tables.values(), reverse=reverse) + </ins><span class="cx"> def create_all(self, tables=None, engine=None): </span><span class="cx"> if not tables: </span><span class="cx"> tables = self.tables.values() </span></span></pre></div> <a id="sqlalchemybranchesschematestdependencypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/dependency.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/dependency.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/dependency.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -1,5 +1,5 @@ </span><span class="cx"> from testbase import PersistTest </span><del>-import sqlalchemy.mapping.topological as topological </del><ins>+import sqlalchemy.orm.topological as topological </ins><span class="cx"> import unittest, sys, os </span><span class="cx"> </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschematesteagertest1py"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/eagertest1.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/eagertest1.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/eagertest1.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -7,7 +7,6 @@ </span><span class="cx"> class EagerTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><span class="cx"> global designType, design, part, inheritedPart </span><del>- </del><span class="cx"> designType = Table('design_types', testbase.metadata, </span><span class="cx"> Column('design_type_id', Integer, primary_key=True), </span><span class="cx"> ) </span><span class="lines">@@ -27,45 +26,41 @@ </span><span class="cx"> Column('design_id', Integer, ForeignKey('design.design_id')), </span><span class="cx"> ) </span><span class="cx"> </span><del>- designType.create() - design.create() - part.create() - inheritedPart.create() </del><ins>+ testbase.metadata.create_all() </ins><span class="cx"> def tearDownAll(self): </span><del>- inheritedPart.drop() - part.drop() - design.drop() - designType.drop() - </del><ins>+ testbase.metadata.drop_all() + testbase.metadata.clear() </ins><span class="cx"> def testone(self): </span><span class="cx"> class Part(object):pass </span><span class="cx"> class Design(object):pass </span><span class="cx"> class DesignType(object):pass </span><span class="cx"> class InheritedPart(object):pass </span><span class="cx"> </span><del>- assign_mapper(Part, part) </del><ins>+ mapper(Part, part) </ins><span class="cx"> </span><del>- assign_mapper(InheritedPart, inheritedPart, properties=dict( </del><ins>+ mapper(InheritedPart, inheritedPart, properties=dict( </ins><span class="cx"> part=relation(Part, lazy=False) </span><span class="cx"> )) </span><span class="cx"> </span><del>- assign_mapper(Design, design, properties=dict( </del><ins>+ mapper(Design, design, properties=dict( </ins><span class="cx"> parts=relation(Part, private=True, backref="design"), </span><span class="cx"> inheritedParts=relation(InheritedPart, private=True, backref="design"), </span><span class="cx"> )) </span><span class="cx"> </span><del>- assign_mapper(DesignType, designType, properties=dict( </del><ins>+ mapper(DesignType, designType, properties=dict( </ins><span class="cx"> # designs=relation(Design, private=True, backref="type"), </span><span class="cx"> )) </span><span class="cx"> </span><del>- Design.mapper.add_property("type", relation(DesignType, lazy=False, backref="designs")) - Part.mapper.add_property("design", relation(Design, lazy=False, backref="parts")) </del><ins>+ class_mapper(Design).add_property("type", relation(DesignType, lazy=False, backref="designs")) + class_mapper(Part).add_property("design", relation(Design, lazy=False, backref="parts")) </ins><span class="cx"> #Part.mapper.add_property("designType", relation(DesignType)) </span><span class="cx"> </span><span class="cx"> d = Design() </span><del>- objectstore.flush() - objectstore.clear() - x = Design.get(1) </del><ins>+ sess = create_session() + sess.save(d) + sess.flush() + sess.clear() + x = sess.query(Design).get(1) </ins><span class="cx"> x.inheritedParts </span><span class="cx"> </span><span class="cx"> if __name__ == "__main__": </span></span></pre></div> <a id="sqlalchemybranchesschematestindexespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/indexes.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/indexes.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/indexes.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -18,7 +18,7 @@ </span><span class="cx"> entity.drop() </span><span class="cx"> </span><span class="cx"> def test_index_create(self): </span><del>- employees = Table('employees', testbase.db, </del><ins>+ employees = Table('employees', testbase.metadata, </ins><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="cx"> Column('first_name', String(30)), </span><span class="cx"> Column('last_name', String(30)), </span><span class="lines">@@ -40,7 +40,7 @@ </span><span class="cx"> </span><span class="cx"> def test_index_create_camelcase(self): </span><span class="cx"> """test that mixed-case index identifiers are legal""" </span><del>- employees = Table('companyEmployees', testbase.db, </del><ins>+ employees = Table('companyEmployees', testbase.metadata, </ins><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="cx"> Column('firstName', String(30)), </span><span class="cx"> Column('lastName', String(30)), </span><span class="lines">@@ -76,7 +76,7 @@ </span><span class="cx"> stream.write = capt.append </span><span class="cx"> testbase.db.logger = testbase.db.engine.logger = stream </span><span class="cx"> </span><del>- events = Table('events', testbase.db, </del><ins>+ events = Table('events', testbase.metadata, </ins><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="cx"> Column('name', String(30), unique=True), </span><span class="cx"> Column('location', String(30), index=True), </span></span></pre></div> <a id="sqlalchemybranchesschematestmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/mapper.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/mapper.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/mapper.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -68,7 +68,6 @@ </span><span class="cx"> tables.drop() </span><span class="cx"> db.echo = testbase.echo </span><span class="cx"> def tearDown(self): </span><del>- objectstore.clear() </del><span class="cx"> clear_mappers() </span><span class="cx"> def setUp(self): </span><span class="cx"> pass </span><span class="lines">@@ -127,13 +126,6 @@ </span><span class="cx"> s.refresh(u) </span><span class="cx"> self.assert_sql_count(db, go, 1) </span><span class="cx"> </span><del>- def testsessionpropigation(self): - sess = create_session() - mapper(User, users, properties={'addresses':relation(mapper(Address, addresses), lazy=True)}) - u = sess.get(User, 7) - assert get_session(u) is sess - assert get_session(u.addresses[0]) is sess - </del><span class="cx"> def testexpire(self): </span><span class="cx"> s = create_session() </span><span class="cx"> mapper(User, users, properties={'addresses':relation(mapper(Address, addresses), lazy=False)}) </span><span class="lines">@@ -187,20 +179,21 @@ </span><span class="cx"> s.refresh(u) #hangs </span><span class="cx"> </span><span class="cx"> def testmagic(self): </span><del>- m = mapper(User, users, properties = { </del><ins>+ mapper(User, users, properties = { </ins><span class="cx"> 'addresses' : relation(mapper(Address, addresses)) </span><span class="cx"> }) </span><del>- l = m.select_by(user_name='fred') </del><ins>+ sess = create_session() + l = sess.query(User).select_by(user_name='fred') </ins><span class="cx"> self.assert_result(l, User, *[{'user_id':9}]) </span><span class="cx"> u = l[0] </span><span class="cx"> </span><del>- u2 = m.get_by_user_name('fred') </del><ins>+ u2 = sess.query(User).get_by_user_name('fred') </ins><span class="cx"> self.assert_(u is u2) </span><span class="cx"> </span><del>- l = m.select_by(email_address='ed...@be...') </del><ins>+ l = sess.query(User).select_by(email_address='ed...@be...') </ins><span class="cx"> self.assert_result(l, User, *[{'user_id':8}]) </span><span class="cx"> </span><del>- l = m.select_by(User.c.user_name=='fred', addresses.c.email_address!='ed...@be...', user_id=9) </del><ins>+ l = sess.query(User).select_by(User.c.user_name=='fred', addresses.c.email_address!='ed...@be...', user_id=9) </ins><span class="cx"> </span><span class="cx"> def testprops(self): </span><span class="cx"> """tests the various attributes of the properties attached to classes""" </span><span class="lines">@@ -211,46 +204,48 @@ </span><span class="cx"> </span><span class="cx"> def testload(self): </span><span class="cx"> """tests loading rows with a mapper and producing object instances""" </span><del>- m = mapper(User, users) - l = m.select() </del><ins>+ mapper(User, users) + l = create_session().query(User).select() </ins><span class="cx"> self.assert_result(l, User, *user_result) </span><del>- l = m.select(users.c.user_name.endswith('ed')) </del><ins>+ l = create_session().query(User).select(users.c.user_name.endswith('ed')) </ins><span class="cx"> self.assert_result(l, User, *user_result[1:3]) </span><span class="cx"> </span><span class="cx"> def testorderby(self): </span><span class="cx"> # TODO: make a unit test out of these various combinations </span><span class="cx"> # m = mapper(User, users, order_by=desc(users.c.user_name)) </span><del>- m = mapper(User, users, order_by=None) -# m = mapper(User, users) </del><ins>+ mapper(User, users, order_by=None) +# mapper(User, users) </ins><span class="cx"> </span><del>-# l = m.select(order_by=[desc(users.c.user_name), asc(users.c.user_id)]) - l = m.select() -# l = m.select(order_by=[]) -# l = m.select(order_by=None) </del><ins>+# l = create_session().query(User).select(order_by=[desc(users.c.user_name), asc(users.c.user_id)]) + l = create_session().query(User).select() +# l = create_session().query(User).select(order_by=[]) +# l = create_session().query(User).select(order_by=None) </ins><span class="cx"> </span><span class="cx"> </span><span class="cx"> def testfunction(self): </span><span class="cx"> """tests mapping to a SELECT statement that has functions in it.""" </span><span class="cx"> s = select([users, (users.c.user_id * 2).label('concat'), func.count(addresses.c.address_id).label('count')], </span><span class="cx"> users.c.user_id==addresses.c.user_id, group_by=[c for c in users.c]).alias('myselect') </span><del>- m = mapper(User, s, primarytable=users) - print [c.key for c in m.c] - l = m.select() </del><ins>+ mapper(User, s) + sess = create_session() + l = sess.query(User).select() </ins><span class="cx"> for u in l: </span><span class="cx"> print "User", u.user_id, u.user_name, u.concat, u.count </span><del>- #l[1].user_name='asdf' - #objectstore.flush() - </del><ins>+ assert l[0].concat == l[0].user_id * 2 == 14 + assert l[1].concat == l[1].user_id * 2 == 16 + </ins><span class="cx"> def testcount(self): </span><del>- m = mapper(User, users) - self.assert_(m.count()==3) - self.assert_(m.count(users.c.user_id.in_(8,9))==2) - self.assert_(m.count_by(user_name='fred')==1) </del><ins>+ mapper(User, users) + q = create_session().query(User) + self.assert_(q.count()==3) + self.assert_(q.count(users.c.user_id.in_(8,9))==2) + self.assert_(q.count_by(user_name='fred')==1) </ins><span class="cx"> </span><span class="cx"> def testmultitable(self): </span><span class="cx"> usersaddresses = sql.join(users, addresses, users.c.user_id == addresses.c.user_id) </span><del>- m = mapper(User, usersaddresses, primarytable = users, primary_key=[users.c.user_id]) - l = m.select() </del><ins>+ m = mapper(User, usersaddresses, primary_key=[users.c.user_id]) + q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, *user_result[0:2]) </span><span class="cx"> </span><span class="cx"> def testoverride(self): </span><span class="lines">@@ -278,10 +273,11 @@ </span><span class="cx"> </span><span class="cx"> def testeageroptions(self): </span><span class="cx"> """tests that a lazy relation can be upgraded to an eager relation via the options method""" </span><del>- m = mapper(User, users, properties = dict( </del><ins>+ sess = create_session() + mapper(User, users, properties = dict( </ins><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = True) </span><span class="cx"> )) </span><del>- l = m.options(eagerload('addresses')).select() </del><ins>+ l = sess.query(User).options(eagerload('addresses')).select() </ins><span class="cx"> </span><span class="cx"> def go(): </span><span class="cx"> self.assert_result(l, User, *user_address_result) </span><span class="lines">@@ -289,16 +285,17 @@ </span><span class="cx"> </span><span class="cx"> def testlazyoptions(self): </span><span class="cx"> """tests that an eager relation can be upgraded to a lazy relation via the options method""" </span><del>- m = mapper(User, users, properties = dict( </del><ins>+ sess = create_session() + mapper(User, users, properties = dict( </ins><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = False) </span><span class="cx"> )) </span><del>- l = m.options(lazyload('addresses')).select() </del><ins>+ l = sess.query(User).options(lazyload('addresses')).select() </ins><span class="cx"> def go(): </span><span class="cx"> self.assert_result(l, User, *user_address_result) </span><span class="cx"> self.assert_sql_count(db, go, 3) </span><span class="cx"> </span><span class="cx"> def testdeepoptions(self): </span><del>- m = mapper(User, users, </del><ins>+ mapper(User, users, </ins><span class="cx"> properties = { </span><span class="cx"> 'orders': relation(mapper(Order, orders, properties = { </span><span class="cx"> 'items' : relation(mapper(Item, orderitems, properties = { </span><span class="lines">@@ -307,55 +304,43 @@ </span><span class="cx"> })) </span><span class="cx"> }) </span><span class="cx"> </span><del>- m2 = m.options(eagerload('orders.items.keywords')) - u = m.select() </del><ins>+ sess = create_session() + q2 = sess.query(User).options(eagerload('orders.items.keywords')) + u = sess.query(User).select() </ins><span class="cx"> def go(): </span><span class="cx"> print u[0].orders[1].items[0].keywords[1] </span><span class="cx"> self.assert_sql_count(db, go, 3) </span><del>- objectstore.clear() - u = m2.select() </del><ins>+ sess.clear() + u = q2.select() </ins><span class="cx"> self.assert_sql_count(db, go, 2) </span><span class="cx"> </span><del>-class PropertyTest(MapperSuperTest): - def testbasic(self): - """tests that you can create mappers inline with class definitions""" - class _Address(object): - pass - assign_mapper(_Address, addresses) - - class _User(object): - pass - assign_mapper(_User, users, properties = dict( - addresses = relation(_Address.mapper, lazy = False) - ), is_primary = True) - - l = _User.mapper.select(_User.c.user_name == 'fred') - self.echo(repr(l)) - </del><ins>+class InheritanceTest(MapperSuperTest): </ins><span class="cx"> </span><span class="cx"> def testinherits(self): </span><span class="cx"> class _Order(object): </span><span class="cx"> pass </span><del>- assign_mapper(_Order, orders) </del><ins>+ ordermapper = mapper(_Order, orders) </ins><span class="cx"> </span><span class="cx"> class _User(object): </span><span class="cx"> pass </span><del>- assign_mapper(_User, users, properties = dict( - orders = relation(_Order.mapper, lazy = False) </del><ins>+ usermapper = mapper(_User, users, properties = dict( + orders = relation(ordermapper, lazy = False) </ins><span class="cx"> )) </span><span class="cx"> </span><span class="cx"> class AddressUser(_User): </span><span class="cx"> pass </span><del>- assign_mapper(AddressUser, addresses, inherits = _User.mapper) - - l = AddressUser.mapper.select() </del><ins>+ assign_mapper(AddressUser, addresses, inherits = usermapper) </ins><span class="cx"> </span><ins>+ sess = create_session() + q = sess.query(AddressUser) + l = q.select() + </ins><span class="cx"> jack = l[0] </span><span class="cx"> self.assert_(jack.user_name=='jack') </span><span class="cx"> jack.email_address = 'ja...@gm...' </span><del>- objectstore.flush() - objectstore.clear() - au = AddressUser.mapper.get_by(user_name='jack') </del><ins>+ sess.flush() + sess.clear() + au = q.get_by(user_name='jack') </ins><span class="cx"> self.assert_(au.email_address == 'ja...@gm...') </span><span class="cx"> </span><span class="cx"> def testinherits2(self): </span><span class="lines">@@ -365,19 +350,20 @@ </span><span class="cx"> pass </span><span class="cx"> class AddressUser(_Address): </span><span class="cx"> pass </span><del>- assign_mapper(_Order, orders) - assign_mapper(_Address, addresses) - assign_mapper(AddressUser, users, inherits = _Address.mapper, </del><ins>+ ordermapper = mapper(_Order, orders) + addressmapper = mapper(_Address, addresses) + usermapper = mapper(AddressUser, users, inherits = addressmapper, </ins><span class="cx"> properties = { </span><del>- 'orders' : relation(_Order.mapper, lazy=False) </del><ins>+ 'orders' : relation(ordermapper, lazy=False) </ins><span class="cx"> }) </span><del>- l = AddressUser.mapper.select() </del><ins>+ sess = create_session() + l = sess.query(usermapper).select() </ins><span class="cx"> jack = l[0] </span><span class="cx"> self.assert_(jack.user_name=='jack') </span><span class="cx"> jack.email_address = 'ja...@gm...' </span><del>- objectstore.flush() - objectstore.clear() - au = AddressUser.mapper.get_by(user_name='jack') </del><ins>+ sess.flush() + sess.clear() + au = sess.query(usermapper).get_by(user_name='jack') </ins><span class="cx"> self.assert_(au.email_address == 'ja...@gm...') </span><span class="cx"> </span><span class="cx"> </span><span class="lines">@@ -393,8 +379,9 @@ </span><span class="cx"> o = Order() </span><span class="cx"> self.assert_(o.description is None) </span><span class="cx"> </span><ins>+ q = create_session().query(m) </ins><span class="cx"> def go(): </span><del>- l = m.select() </del><ins>+ l = q.select() </ins><span class="cx"> o2 = l[2] </span><span class="cx"> print o2.description </span><span class="cx"> </span><span class="lines">@@ -409,10 +396,12 @@ </span><span class="cx"> 'description':deferred(orders.c.description) </span><span class="cx"> }) </span><span class="cx"> </span><del>- l = m.select() </del><ins>+ sess = create_session() + q = sess.query(m) + l = q.select() </ins><span class="cx"> o2 = l[2] </span><span class="cx"> o2.isopen = 1 </span><del>- objectstore.flush() </del><ins>+ sess.flush() </ins><span class="cx"> </span><span class="cx"> def testgroup(self): </span><span class="cx"> """tests deferred load with a group""" </span><span class="lines">@@ -422,9 +411,9 @@ </span><span class="cx"> 'description':deferred(orders.c.description, group='primary'), </span><span class="cx"> 'opened':deferred(orders.c.isopen, group='primary') </span><span class="cx"> }) </span><del>- </del><ins>+ q = create_session().query(m) </ins><span class="cx"> def go(): </span><del>- l = m.select() </del><ins>+ l = q.select() </ins><span class="cx"> o2 = l[2] </span><span class="cx"> print o2.opened, o2.description, o2.userident </span><span class="cx"> </span><span class="lines">@@ -437,9 +426,11 @@ </span><span class="cx"> def testoptions(self): </span><span class="cx"> """tests using options on a mapper to create deferred and undeferred columns""" </span><span class="cx"> m = mapper(Order, orders) </span><del>- m2 = m.options(defer('user_id')) </del><ins>+ sess = create_session() + q = sess.query(m) + q2 = q.options(defer('user_id')) </ins><span class="cx"> def go(): </span><del>- l = m2.select() </del><ins>+ l = q2.select() </ins><span class="cx"> print l[2].user_id </span><span class="cx"> </span><span class="cx"> orderby = str(orders.default_order_by()[0].compile(db)) </span><span class="lines">@@ -447,10 +438,10 @@ </span><span class="cx"> ("SELECT orders.order_id AS orders_order_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY %s" % orderby, {}), </span><span class="cx"> ("SELECT orders.user_id AS orders_user_id FROM orders WHERE orders.order_id = :orders_order_id", {'orders_order_id':3}) </span><span class="cx"> ]) </span><del>- objectstore.clear() - m3 = m2.options(undefer('user_id')) </del><ins>+ sess.clear() + q3 = q2.options(undefer('user_id')) </ins><span class="cx"> def go(): </span><del>- l = m3.select() </del><ins>+ l = q3.select() </ins><span class="cx"> print l[3].user_id </span><span class="cx"> self.assert_sql(db, go, [ </span><span class="cx"> ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY %s" % orderby, {}), </span><span class="lines">@@ -464,15 +455,17 @@ </span><span class="cx"> })) </span><span class="cx"> })) </span><span class="cx"> }) </span><del>- l = m.select() </del><ins>+ sess = create_session() + q = sess.query(m) + l = q.select() </ins><span class="cx"> item = l[0].orders[1].items[1] </span><span class="cx"> def go(): </span><span class="cx"> print item.item_name </span><span class="cx"> self.assert_sql_count(db, go, 1) </span><span class="cx"> self.assert_(item.item_name == 'item 4') </span><del>- objectstore.clear() - m2 = m.options(undefer('orders.items.item_name')) - l = m2.select() </del><ins>+ sess.clear() + q2 = q.options(undefer('orders.items.item_name')) + l = q2.select() </ins><span class="cx"> item = l[0].orders[1].items[1] </span><span class="cx"> def go(): </span><span class="cx"> print item.item_name </span><span class="lines">@@ -487,7 +480,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = True) </span><span class="cx"> )) </span><del>- l = m.select(users.c.user_id == 7) </del><ins>+ q = create_session().query(m) + l = q.select(users.c.user_id == 7) </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'addresses' : (Address, [{'address_id' : 1}])}, </span><span class="cx"> ) </span><span class="lines">@@ -498,7 +492,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(m, lazy = True, order_by=addresses.c.email_address), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> </span><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'addresses' : (Address, [{'email_address' : 'ja...@be...'}])}, </span><span class="lines">@@ -512,7 +507,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(m, lazy = True, order_by=[desc(addresses.c.email_address)]), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> </span><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'addresses' : (Address, [{'email_address' : 'ja...@be...'}])}, </span><span class="lines">@@ -529,35 +525,39 @@ </span><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = True), </span><span class="cx"> orders = relation(ordermapper, primaryjoin = users.c.user_id==orders.c.user_id, lazy = True), </span><span class="cx"> )) </span><del>- l = m.select(limit=2, offset=1) </del><ins>+ sess= create_session() + q = sess.query(m) + l = q.select(limit=2, offset=1) </ins><span class="cx"> self.assert_result(l, User, *user_all_result[1:3]) </span><span class="cx"> # use a union all to get a lot of rows to join against </span><span class="cx"> u2 = users.alias('u2') </span><span class="cx"> s = union_all(u2.select(use_labels=True), u2.select(use_labels=True), u2.select(use_labels=True)).alias('u') </span><span class="cx"> print [key for key in s.c.keys()] </span><del>- l = m.select(s.c.u2_user_id==User.c.user_id, distinct=True) </del><ins>+ l = q.select(s.c.u2_user_id==User.c.user_id, distinct=True) </ins><span class="cx"> self.assert_result(l, User, *user_all_result) </span><span class="cx"> </span><del>- objectstore.clear() </del><ins>+ sess.clear() </ins><span class="cx"> m = mapper(Item, orderitems, is_primary=True, properties = dict( </span><span class="cx"> keywords = relation(mapper(Keyword, keywords), itemkeywords, lazy = True), </span><span class="cx"> )) </span><del>- l = m.select((Item.c.item_name=='item 2') | (Item.c.item_name=='item 5') | (Item.c.item_name=='item 3'), order_by=[Item.c.item_id], limit=2) </del><ins>+ + l = sess.query(m).select((Item.c.item_name=='item 2') | (Item.c.item_name=='item 5') | (Item.c.item_name=='item 3'), order_by=[Item.c.item_id], limit=2) </ins><span class="cx"> self.assert_result(l, Item, *[item_keyword_result[1], item_keyword_result[2]]) </span><span class="cx"> </span><span class="cx"> def testonetoone(self): </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> address = relation(mapper(Address, addresses), lazy = True, uselist = False) </span><span class="cx"> )) </span><del>- l = m.select(users.c.user_id == 7) - self.echo(repr(l)) - self.echo(repr(l[0].address)) </del><ins>+ q = create_session().query(m) + l = q.select(users.c.user_id == 7) + self.assert_result(l, User, {'user_id':7, 'address' : (Address, {'address_id':1})}) </ins><span class="cx"> </span><span class="cx"> def testbackwardsonetoone(self): </span><span class="cx"> m = mapper(Address, addresses, properties = dict( </span><span class="cx"> user = relation(mapper(User, users), lazy = True) </span><span class="cx"> )) </span><del>- l = m.select(addresses.c.address_id == 1) </del><ins>+ q = create_session().query(m) + l = q.select(addresses.c.address_id == 1) </ins><span class="cx"> self.echo(repr(l)) </span><span class="cx"> print repr(l[0].__dict__) </span><span class="cx"> self.echo(repr(l[0].user)) </span><span class="lines">@@ -573,7 +573,8 @@ </span><span class="cx"> open_orders = relation(mapper(Order, openorders, entity_name='open'), primaryjoin = and_(openorders.c.isopen == 1, users.c.user_id==openorders.c.user_id), lazy = True), </span><span class="cx"> closed_orders = relation(mapper(Order, closedorders,entity_name='closed'), primaryjoin = and_(closedorders.c.isopen == 0, users.c.user_id==closedorders.c.user_id), lazy = True) </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, </span><span class="cx"> 'addresses' : (Address, [{'address_id' : 1}]), </span><span class="lines">@@ -594,10 +595,11 @@ </span><span class="cx"> </span><span class="cx"> def testmanytomany(self): </span><span class="cx"> """tests a many-to-many lazy load""" </span><del>- assign_mapper(Item, orderitems, properties = dict( </del><ins>+ mapper(Item, orderitems, properties = dict( </ins><span class="cx"> keywords = relation(mapper(Keyword, keywords), itemkeywords, lazy = True), </span><span class="cx"> )) </span><del>- l = Item.mapper.select() </del><ins>+ q = create_session().query(Item) + l = q.select() </ins><span class="cx"> self.assert_result(l, Item, </span><span class="cx"> {'item_id' : 1, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 4}, {'keyword_id' : 6}])}, </span><span class="cx"> {'item_id' : 2, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 5}, {'keyword_id' : 7}])}, </span><span class="lines">@@ -605,7 +607,7 @@ </span><span class="cx"> {'item_id' : 4, 'keywords' : (Keyword, [])}, </span><span class="cx"> {'item_id' : 5, 'keywords' : (Keyword, [])} </span><span class="cx"> ) </span><del>- l = Item.mapper.select(and_(keywords.c.name == 'red', keywords.c.keyword_id == itemkeywords.c.keyword_id, Item.c.item_id==itemkeywords.c.item_id)) </del><ins>+ l = q.select(and_(keywords.c.name == 'red', keywords.c.keyword_id == itemkeywords.c.keyword_id, Item.c.item_id==itemkeywords.c.item_id)) </ins><span class="cx"> self.assert_result(l, Item, </span><span class="cx"> {'item_id' : 1, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 4}, {'keyword_id' : 6}])}, </span><span class="cx"> {'item_id' : 2, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 5}, {'keyword_id' : 7}])}, </span><span class="lines">@@ -619,7 +621,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(m, lazy = False), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, *user_address_result) </span><span class="cx"> </span><span class="cx"> def testorderby(self): </span><span class="lines">@@ -628,7 +631,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(m, lazy = False, order_by=addresses.c.email_address), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'addresses' : (Address, [{'email_address' : 'ja...@be...'}])}, </span><span class="cx"> {'user_id' : 8, 'addresses' : (Address, [{'email_address':'ed...@be...'}, {'email_address':'ed...@la...'}, {'email_address':'ed...@wo...'}])}, </span><span class="lines">@@ -641,7 +645,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(m, lazy = False, order_by=[desc(addresses.c.email_address)]), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> </span><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'addresses' : (Address, [{'email_address' : 'ja...@be...'}])}, </span><span class="lines">@@ -658,20 +663,24 @@ </span><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = False), </span><span class="cx"> orders = relation(ordermapper, primaryjoin = users.c.user_id==orders.c.user_id, lazy = False), </span><span class="cx"> )) </span><del>- l = m.select(limit=2, offset=1) </del><ins>+ sess = create_session() + q = sess.query(m) + + l = q.select(limit=2, offset=1) </ins><span class="cx"> self.assert_result(l, User, *user_all_result[1:3]) </span><span class="cx"> # this is an involved 3x union of the users table to get a lot of rows. </span><span class="cx"> # then see if the "distinct" works its way out. you actually get the same </span><span class="cx"> # result with or without the distinct, just via less or more rows. </span><span class="cx"> u2 = users.alias('u2') </span><span class="cx"> s = union_all(u2.select(use_labels=True), u2.select(use_labels=True), u2.select(use_labels=True)).alias('u') </span><del>- l = m.select(s.c.u2_user_id==User.c.user_id, distinct=True) </del><ins>+ l = q.select(s.c.u2_user_id==User.c.user_id, distinct=True) </ins><span class="cx"> self.assert_result(l, User, *user_all_result) </span><del>- objectstore.clear() </del><ins>+ sess.clear() </ins><span class="cx"> m = mapper(Item, orderitems, is_primary=True, properties = dict( </span><span class="cx"> keywords = relation(mapper(Keyword, keywords), itemkeywords, lazy = False, order_by=[keywords.c.keyword_id]), </span><span class="cx"> )) </span><del>- l = m.select((Item.c.item_name=='item 2') | (Item.c.item_name=='item 5') | (Item.c.item_name=='item 3'), order_by=[Item.c.item_id], limit=2) </del><ins>+ q = sess.query(m) + l = q.select((Item.c.item_name=='item 2') | (Item.c.item_name=='item 5') | (Item.c.item_name=='item 3'), order_by=[Item.c.item_id], limit=2) </ins><span class="cx"> self.assert_result(l, Item, *[item_keyword_result[1], item_keyword_result[2]]) </span><span class="cx"> </span><span class="cx"> </span><span class="lines">@@ -680,7 +689,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> address = relation(mapper(Address, addresses), lazy = False, uselist = False) </span><span class="cx"> )) </span><del>- l = m.select(users.c.user_id == 7) </del><ins>+ q = create_session().query(m) + l = q.select(users.c.user_id == 7) </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, 'address' : (Address, {'address_id' : 1, 'email_address': 'ja...@be...'})}, </span><span class="cx"> ) </span><span class="lines">@@ -690,7 +700,8 @@ </span><span class="cx"> user = relation(mapper(User, users), lazy = False) </span><span class="cx"> )) </span><span class="cx"> self.echo(repr(m.props['user'].uselist)) </span><del>- l = m.select(addresses.c.address_id == 1) </del><ins>+ q = create_session().query(m) + l = q.select(addresses.c.address_id == 1) </ins><span class="cx"> self.assert_result(l, Address, </span><span class="cx"> {'address_id' : 1, 'email_address' : 'ja...@be...', </span><span class="cx"> 'user' : (User, {'user_id' : 7, 'user_name' : 'jack'}) </span><span class="lines">@@ -704,7 +715,8 @@ </span><span class="cx"> m = mapper(User, users, properties = dict( </span><span class="cx"> addresses = relation(mapper(Address, addresses), primaryjoin = users.c.user_id==addresses.c.user_id, lazy = False) </span><span class="cx"> )) </span><del>- l = m.select(and_(addresses.c.email_address == 'ed...@la...', addresses.c.user_id==users.c.user_id)) </del><ins>+ q = create_session().query(m) + l = q.select(and_(addresses.c.email_address == 'ed...@la...', addresses.c.user_id==users.c.user_id)) </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 8, 'addresses' : (Address, [{'address_id' : 2, 'email_address':'ed...@wo...'}, {'address_id':3, 'email_address':'ed...@be...'}, {'address_id':4, 'email_address':'ed...@la...'}])}, </span><span class="cx"> ) </span><span class="lines">@@ -729,7 +741,8 @@ </span><span class="cx"> addresses = relation(mapper(Address, addresses), primaryjoin = users.c.user_id==addresses.c.user_id, lazy = False), </span><span class="cx"> orders = relation(mapper(Order, orders), lazy = False), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, </span><span class="cx"> 'addresses' : (Address, [{'address_id' : 1}]), </span><span class="lines">@@ -755,7 +768,8 @@ </span><span class="cx"> open_orders = relation(mapper(Order, openorders, non_primary=True), primaryjoin = and_(openorders.c.isopen == 1, users.c.user_id==openorders.c.user_id), lazy = False), </span><span class="cx"> closed_orders = relation(mapper(Order, closedorders, non_primary=True), primaryjoin = and_(closedorders.c.isopen == 0, users.c.user_id==closedorders.c.user_id), lazy = False) </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, </span><span class="cx"> {'user_id' : 7, </span><span class="cx"> 'addresses' : (Address, [{'address_id' : 1}]), </span><span class="lines">@@ -785,7 +799,8 @@ </span><span class="cx"> addresses = relation(mapper(Address, addresses), lazy = False), </span><span class="cx"> orders = relation(ordermapper, primaryjoin = users.c.user_id==orders.c.user_id, lazy = False), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, User, *user_all_result) </span><span class="cx"> </span><span class="cx"> def testmanytomany(self): </span><span class="lines">@@ -794,11 +809,11 @@ </span><span class="cx"> m = mapper(Item, items, properties = dict( </span><span class="cx"> keywords = relation(mapper(Keyword, keywords), itemkeywords, lazy=False, order_by=[keywords.c.keyword_id]), </span><span class="cx"> )) </span><del>- l = m.select() </del><ins>+ q = create_session().query(m) + l = q.select() </ins><span class="cx"> self.assert_result(l, Item, *item_keyword_result) </span><span class="cx"> </span><del>-# l = m.select() - l = m.select(and_(keywords.c.name == 'red', keywords.c.keyword_id == itemkeywords.c.keyword_id, items.c.item_id==itemkeywords.c.item_id)) </del><ins>+ l = q.select(and_(keywords.c.name == 'red', keywords.c.keyword_id == itemkeywords.c.keyword_id, items.c.item_id==itemkeywords.c.item_id)) </ins><span class="cx"> self.assert_result(l, Item, </span><span class="cx"> {'item_id' : 1, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 4}, {'keyword_id' : 6}])}, </span><span class="cx"> {'item_id' : 2, 'keywords' : (Keyword, [{'keyword_id' : 2}, {'keyword_id' : 5}, {'keyword_id' : 7}])}, </span><span class="lines">@@ -817,7 +832,8 @@ </span><span class="cx"> m = mapper(Order, orders, properties = dict( </span><span class="cx"> items = relation(m, lazy = False) </span><span class="cx"> )) </span><del>- l = m.select("orders.order_id in (1,2,3)") </del><ins>+ q = create_session().query(m) + l = q.select("orders.order_id in (1,2,3)") </ins><span class="cx"> self.assert_result(l, Order, </span><span class="cx"> {'order_id' : 1, 'items': (Item, [])}, </span><span class="cx"> {'order_id' : 2, 'items': (Item, [ </span></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -9,6 +9,7 @@ </span><span class="cx"> </span><span class="cx"> class HistoryTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> db.echo = False </span><span class="cx"> users.create() </span><span class="cx"> addresses.create() </span><span class="lines">@@ -18,6 +19,7 @@ </span><span class="cx"> addresses.drop() </span><span class="cx"> users.drop() </span><span class="cx"> db.echo = testbase.echo </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> def setUp(self): </span><span class="cx"> objectstore.clear() </span><span class="cx"> clear_mappers() </span><span class="lines">@@ -77,6 +79,7 @@ </span><span class="cx"> </span><span class="cx"> class VersioningTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> objectstore.clear() </span><span class="cx"> global version_table </span><span class="cx"> version_table = Table('version_test', db, </span><span class="lines">@@ -86,6 +89,7 @@ </span><span class="cx"> ).create() </span><span class="cx"> def tearDownAll(self): </span><span class="cx"> version_table.drop() </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> def tearDown(self): </span><span class="cx"> version_table.delete().execute() </span><span class="cx"> objectstore.clear() </span><span class="lines">@@ -136,6 +140,7 @@ </span><span class="cx"> </span><span class="cx"> class UnicodeTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> objectstore.clear() </span><span class="cx"> global uni_table </span><span class="cx"> uni_table = Table('uni_test', db, </span><span class="lines">@@ -145,6 +150,7 @@ </span><span class="cx"> def tearDownAll(self): </span><span class="cx"> uni_table.drop() </span><span class="cx"> uni_table.deregister() </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> </span><span class="cx"> def testbasic(self): </span><span class="cx"> class Test(object): </span><span class="lines">@@ -162,6 +168,7 @@ </span><span class="cx"> </span><span class="cx"> class PKTest(AssertMixin): </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> db.echo = False </span><span class="cx"> global table </span><span class="cx"> global table2 </span><span class="lines">@@ -195,6 +202,7 @@ </span><span class="cx"> table2.drop() </span><span class="cx"> table3.drop() </span><span class="cx"> db.echo = testbase.echo </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> def setUp(self): </span><span class="cx"> objectstore.clear() </span><span class="cx"> clear_mappers() </span><span class="lines">@@ -236,6 +244,7 @@ </span><span class="cx"> class PrivateAttrTest(AssertMixin): </span><span class="cx"> """tests various things to do with private=True mappers""" </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> global a_table, b_table </span><span class="cx"> a_table = Table('a',testbase.db, </span><span class="cx"> Column('a_id', Integer, Sequence('next_a_id'), primary_key=True), </span><span class="lines">@@ -249,6 +258,7 @@ </span><span class="cx"> def tearDownAll(self): </span><span class="cx"> b_table.drop() </span><span class="cx"> a_table.drop() </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> def setUp(self): </span><span class="cx"> objectstore.clear() </span><span class="cx"> clear_mappers() </span><span class="lines">@@ -311,6 +321,7 @@ </span><span class="cx"> the newly saved instances receive all the default values either through a post-fetch or getting the pre-exec'ed </span><span class="cx"> defaults back from the engine.""" </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> #db.echo = 'debug' </span><span class="cx"> use_string_defaults = db.engine.__module__.endswith('postgres') or db.engine.__module__.endswith('oracle') or db.engine.__module__.endswith('sqlite') </span><span class="cx"> </span><span class="lines">@@ -331,6 +342,7 @@ </span><span class="cx"> self.table.create() </span><span class="cx"> def tearDownAll(self): </span><span class="cx"> self.table.drop() </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> def setUp(self): </span><span class="cx"> self.table = Table('default_test', db) </span><span class="cx"> def testinsert(self): </span><span class="lines">@@ -383,6 +395,7 @@ </span><span class="cx"> class SaveTest(AssertMixin): </span><span class="cx"> </span><span class="cx"> def setUpAll(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> db.echo = False </span><span class="cx"> tables.create() </span><span class="cx"> db.echo = testbase.echo </span><span class="lines">@@ -390,6 +403,7 @@ </span><span class="cx"> db.echo = False </span><span class="cx"> tables.drop() </span><span class="cx"> db.echo = testbase.echo </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> </span><span class="cx"> def setUp(self): </span><span class="cx"> db.echo = False </span><span class="lines">@@ -1101,6 +1115,7 @@ </span><span class="cx"> class SaveTest2(AssertMixin): </span><span class="cx"> </span><span class="cx"> def setUp(self): </span><ins>+ self.install_threadlocal() </ins><span class="cx"> db.echo = False </span><span class="cx"> objectstore.clear() </span><span class="cx"> clear_mappers() </span><span class="lines">@@ -1128,6 +1143,7 @@ </span><span class="cx"> self.addresses.drop() </span><span class="cx"> self.users.drop() </span><span class="cx"> db.echo = testbase.echo </span><ins>+ self.uninstall_threadlocal() </ins><span class="cx"> </span><span class="cx"> def testbackwardsnonmatch(self): </span><span class="cx"> m = mapper(Address, self.addresses, properties = dict( </span></span></pre></div> <a id="sqlalchemybranchesschematestrelationshipspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/relationships.py (1368 => 1369)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/relationships.py 2006-05-01 01:49:10 UTC (rev 1368) +++ sqlalchemy/branches/schema/test/relationships.py 2006-05-01 21:53:27 UTC (rev 1369) </span><span class="lines">@@ -44,7 +44,7 @@ </span><span class="cx"> ) </span><span class="cx"> def setUp(self): </span><span class="cx"> global session </span><del>- session = objectstore.Session(bind_to=testbase.db) </del><ins>+ session = create_session(bind_to=testbase.db) </ins><span class="cx"> conn = session.connect() </span><span class="cx"> conn.create(tbl_a) </span><span class="cx"> conn.create(tbl_b) </span><span class="lines">@@ -79,7 +79,8 @@ </span><span class="cx"> d1 = D(); d1.name = "d1"; d1.b_row = b; d1.c_row = c... [truncated message content] |
From: <co...@sq...> - 2006-05-01 01:49:25
|
<!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>[1368] sqlalchemy/branches/schema/test: ok save-update is default cascade....</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1368</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 20:49:10 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>ok save-update is default cascade....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -34,6 +34,23 @@ </span><span class="cx"> {python} </span><span class="cx"> session = create_session() </span><span class="cx"> </span><ins>+A common option used with `create_session()` is to specify a specific `Engine` or `Connection` to be used for all operations performed by this Session: + + {python} + # create an engine + e = create_engine('postgres://some/url') + + # create a Session that will use this engine for all operations. + # it will open and close Connections as needed. + session = create_session(bind_to=e) + + # open a Connection + conn = e.connect() + + # create a Session that will use this specific Connection for all operations + session = create_session(bind_to=conn) + + </ins><span class="cx"> The session to which an object is attached can be acquired via the `object_session()` method, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -119,8 +136,16 @@ </span><span class="cx"> </span><span class="cx"> ### The Session API {@name=api} </span><span class="cx"> </span><del>-#### Flush {@name=flush} </del><ins>+#### query() {@name=query} </ins><span class="cx"> </span><ins>+#### get() {@name=get} + +#### load() {@name=load} + +#### save() {@name=save} + +#### flush() {@name=flush} + </ins><span class="cx"> 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: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -137,73 +162,70 @@ </span><span class="cx"> </span><span class="cx"> ##### Notes on Flush {@name=whatis} </span><span class="cx"> </span><del>-A common misconception about the `flush()` operation is that once performed, the newly persisted instances will automatically have related objects attached to them, based on the values of primary key identities that have been assigned to the instances before they were persisted. -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. </del><ins>+A common misconception about the `flush()` operation is that once performed, the newly persisted instances will automatically have related objects attached to them, based on the values of primary key identities that have been assigned to the instances before they were persisted. An example would be, you create a new `Address` object, set `address.user_id` to 5, and then `flush()` the session. The erroneous assumption would be that there is now a `User` object of identity "5" attached to the `Address` object, but in fact this is not the case. If you were to `refresh()` the `Address`, invalidating its current state and re-loading, *then* it would have the appropriate `User` object present. </ins><span class="cx"> </span><del>-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: </del><ins>+This misunderstanding is related to the observed behavior of backreferences ([datamapping_relations_backreferences](rel:datamapping_relations_backreferences)), which automatically associates an instance "A" with another instance "B", in response to the manual association of instance "B" to instance "A" by the user. The backreference operation occurs completely externally to the `flush()` operation, and is pretty much the only example of a SQLAlchemy feature that manipulates the relationships of persistent objects. </ins><span class="cx"> </span><del>-* 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. -* It will not set or remove any scalar references to other objects, even if the corresponding database identifier columns have been flushed. - -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. - -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. </del><ins>+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><del>-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, independent of a! ny kind of database operation. It does not change the golden rule, that the developer is reponsible for maintaining in-memory object relationships. </del><ins>+#### close() {@name=close} </ins><span class="cx"> </span><del>-`*` there is an internal relationship between two `relations` that have a backreference, which state that a change operation is only logged once to the unit of work instead of two separate changes since the two changes are "equivalent", so a backreference does affect the information that is sent to the Unit of Work. But the Unit of Work itself has no knowledge of this arrangement and has no ability to affect it. </del><ins>+#### delete() {@name=delete} </ins><span class="cx"> </span><del>-#### Delete {@name=delete} </del><ins>+The delete call places an instance into the Unit of Work's list of objects to be marked as deleted: </ins><span class="cx"> </span><del>-The delete call places an object or objects into the Unit of Work's list of objects to be marked as deleted: - </del><span class="cx"> {python} </span><del>- # mark three objects to be deleted - objectstore.get_session().delete(obj1, obj2, obj3) </del><ins>+ # mark two objects to be deleted + session.delete(obj1) + session.delete(obj2) </ins><span class="cx"> </span><span class="cx"> # flush </span><del>- objectstore.get_session().flush() - -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*. </del><ins>+ session.flush() </ins><span class="cx"> </span><del>-As stated before, the purpose of delete is strictly to issue DELETE statements to the database. It does not affect the in-memory structure of objects, other than changing the identifying attributes on objects, such as setting foreign key identifiers on updated rows to None. It has no effect on the status of references between object instances, nor any effect on the Python garbage-collection status of objects. </del><ins>+The delete operation will have an effect on instances that are attached to the deleted instance according to the `cascade` style of the relationship. By default, associated instances may need to be updated upon flush in order to reflect that they no longer are associated with the parent object, before the parent is deleted. If the relationship specifies `cascade="delete"`, then the associated instance will also be deleted upon flush, assuming it is still attached to the parent. If the relationship additionally includes the `delete-orphan` cascade style, the associated instance will be deleted if it is still attached to the parent, or is unattached to any other parent. </ins><span class="cx"> </span><del>-#### Clear {@name=clear} </del><ins>+The `delete()` operation has no relationship to the in-memory status of the instance, including usage of the `del` Python statement. An instance marked as deleted and flushed will still exist within memory until references to it are freed; similarly, removing an instance from memory via the `del` statement will have no effect, since the persistent instance will still be referenced by its Session. Obviously, if the instance is removed from the Session and then totally dereferenced, it will no longer exist in memory, but also won't exist in any Session and is therefore not deleted from the database. </ins><span class="cx"> </span><del>-To clear out the current thread's UnitOfWork, which has the effect of discarding the Identity Map and the lists of all objects that have been modified, just issue a clear: </del><ins>+#### clear() {@name=clear} + +This method detaches all instances from the Session, sending them to the detached or transient state as applicable, and replaces the underlying UnitOfWork with a new one. </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- # via module - objectstore.clear() - - # or via Session - objectstore.get_session().clear() </del><ins>+ session.clear() </ins><span class="cx"> </span><del>-This is the easiest way to "start fresh", as in a web application that wants to have a newly loaded graph of objects on each request. Any object instances created before the clear operation should either be discarded or at least not used with any Mapper or Unit Of Work operations (with the exception of `import_instance()`), as they no longer have any relationship to the current Unit of Work, and their behavior with regards to the current session is undefined. </del><ins>+The `clear()` method is particularly useful with a "default context" session such as a thread-local session, which can stay attached to the current thread to handle a new field of objects without having to re-attach a new Session. </ins><span class="cx"> </span><del>-#### Refresh / Expire {@name=refreshexpire} </del><ins>+#### refresh() / expire() {@name=refreshexpire} </ins><span class="cx"> </span><span class="cx"> To assist with the Unit of Work's "sticky" behavior, individual objects can have all of their attributes immediately re-loaded from the database, or marked as "expired" which will cause a re-load to occur upon the next access of any of the object's mapped attributes. This includes all relationships, so lazy-loaders will be re-initialized, eager relationships will be repopulated. Any changes marked on the object are discarded: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # immediately re-load attributes on obj1, obj2 </span><del>- session.refresh(obj1, obj2) </del><ins>+ session.refresh(obj1) + session.refresh(obj2) </ins><span class="cx"> </span><span class="cx"> # expire objects obj1, obj2, attributes will be reloaded </span><span class="cx"> # on the next access: </span><del>- session.expire(obj1, obj2, obj3) </del><ins>+ session.expire(obj1) + session.expire(obj2) </ins><span class="cx"> </span><del>-#### Expunge {@name=expunge} </del><ins>+#### expunge() {@name=expunge} </ins><span class="cx"> </span><del>-Expunge simply removes all record of an object from the current Session. This includes the identity map, and all history-tracking lists: </del><ins>+Expunge removes an object from the Session, sending persistent instances to the detached state, and pending instances to the transient state: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> session.expunge(obj1) </span><span class="cx"> </span><span class="cx"> 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. </span><span class="cx"> </span><del>-#### Import Instance {@name=import} </del><ins>+#### bind_mapper()/bind_table() {@name=bind} </ins><span class="cx"> </span><ins>+#### update() {@name=update} + +#### save_or_update() {@name=save_or_update} + +#### merge() {@name=merge} + </ins><span class="cx"> The _instance_key attribute placed on object instances is designed to work with objects that are serialized into strings and brought back again. As it contains no references to internal structures or database connections, applications that use caches or session storage which require serialization (i.e. pickling) can store SQLAlchemy-loaded objects. However, as mentioned earlier, an object with a particular database identity is only allowed to exist uniquely within the current unit-of-work scope. So, upon deserializing such an object, it has to "check in" with the current Session. This is achieved via the `import_instance()` method: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -217,6 +239,7 @@ </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><span class="cx"> </span><ins>+ </ins><span class="cx"> #### Analyzing Object Commits {@name=logging} </span><span class="cx"> </span><span class="cx"> The objectstore module can log an extensive display of its "commit plans", which is a graph of its internal representation of objects before they are committed to the database. To turn this logging on: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -146,7 +146,7 @@ </span><span class="cx"> if private: </span><span class="cx"> self.cascade = mapperutil.CascadeOptions("all, delete-orphan") </span><span class="cx"> else: </span><del>- self.cascade = mapperutil.CascadeOptions("all") </del><ins>+ self.cascade = mapperutil.CascadeOptions("save-update") </ins><span class="cx"> </span><span class="cx"> self.association = association </span><span class="cx"> self.order_by = order_by </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -276,7 +276,7 @@ </span><span class="cx"> This operation cascades the "save_or_update" method to associated instances if the relation is mapped </span><span class="cx"> with cascade="save-update".""" </span><span class="cx"> for c in object_mapper(object, entity_name=entity_name).cascade_iterator('save-update', object): </span><del>- if c is o: </del><ins>+ if c is object: </ins><span class="cx"> self._update_impl(c, entity_name=entity_name) </span><span class="cx"> else: </span><span class="cx"> self.save_or_update(c, entity_name=entity_name) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -65,7 +65,7 @@ </span><span class="cx"> sess = object_session(obj) </span><span class="cx"> if sess is not None: </span><span class="cx"> sess._register_dirty(obj) </span><del>- if self.cascade is not None: </del><ins>+ if newvalue is not None and self.cascade is not None: </ins><span class="cx"> if self.cascade.save_update: </span><span class="cx"> sess.save_or_update(newvalue) </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -443,6 +443,7 @@ </span><span class="cx"> self.assert_(u is not nu and u.user_id == nu.user_id and nu.user_name == 'savetester') </span><span class="cx"> </span><span class="cx"> # change first users name and save </span><ins>+ objectstore.get_session().update(u) </ins><span class="cx"> u.user_name = 'modifiedname' </span><span class="cx"> assert u in objectstore.get_session().dirty </span><span class="cx"> objectstore.get_session().flush() </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-01 00:54:30
|
<!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>[1367] sqlalchemy/branches/schema/doc/build/content/unitofwork.txt: dev</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1367</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 19:54:05 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>dev</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1366 => 1367)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:43:22 UTC (rev 1366) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:54:05 UTC (rev 1367) </span><span class="lines">@@ -114,84 +114,30 @@ </span><span class="cx"> </span><span class="cx"> # persistent objects that have been marked as deleted via session.delete(obj) </span><span class="cx"> session.deleted </span><del>- -Heres an interactive example, assuming the `User` and `Address` mapper setup first outlined in [datamapping_relations](rel:datamapping_relations): - - {python} - >>> # create a session - >>> session = create_session() - - >>> # create a new object, with a list-based attribute - >>> # containing two more new objects - >>> u = User(user_name='Fred') - >>> u.addresses.append(Address(city='New York')) - >>> u.addresses.append(Address(city='Boston')) - >>> session.save(u) </del><span class="cx"> </span><del>- >>> # objects are in the "new" list - >>> session.new - [<__main__.User object at 0x713630>, - <__main__.Address object at 0x713a70>, - <__main__.Address object at 0x713b30>] - - >>> # lets view what the class/ID is for the list object - >>> ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] - ['sqlalchemy.mapping.unitofwork.UOWListElement 7391872'] - - >>> # now flush - >>> session.flush() - - >>> # the "new" list is now empty - >>> session.new - [] - - >>> # now lets modify an object - >>> u.user_name='Ed' - - >>> # it gets placed in the "dirty" list - >>> session.dirty - [<__main__.User object at 0x713630>] - - >>> # delete one of the addresses - >>> session.delete(u.addresses[0]) - - >>> # and also delete it off the User object, note that - >>> # this is *not automatic* when using session.delete() - >>> del u.addresses[0] - >>> session.deleted - [<__main__.Address object at 0x713a70>] - - >>> # flush - >>> session.flush() - - >>> # all lists are cleared out - >>> session.new, session.dirty, session.modified_lists, session.deleted - ([], [], [], []) - - >>> # identity map has the User and the one remaining Address - >>> session.identity_map.values() - [<__main__.Address object at 0x713b30>, <__main__.User object at 0x713630>] - -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. </del><ins>+Unlike the identity map, the `new`, `dirty`, 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><ins>+### The Session API {@name=api} + </ins><span class="cx"> #### Flush {@name=flush} </span><span class="cx"> </span><span class="cx"> 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: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- objectstore.get_session().flush() </del><ins>+ session.flush() </ins><span class="cx"> </span><span class="cx"> 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: </span><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().flush(user1, address2) </del><ins>+ session.flush(user1, address2) </ins><span class="cx"> </span><span class="cx"> 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. </span><span class="cx"> </span><del>-##### What Flush is, and Isn't {@name=whatis} </del><ins>+##### Notes on Flush {@name=whatis} </ins><span class="cx"> </span><ins>+A common misconception about the `flush()` operation is that once performed, the newly persisted instances will automatically have related objects attached to them, based on the values of primary key identities that have been assigned to the instances before they were persisted. </ins><span class="cx"> 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. </span><span class="cx"> </span><span class="cx"> 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: </span><span class="lines">@@ -270,112 +216,7 @@ </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>-### Advanced UnitOfWork Management {@name=advscope} </del><span class="cx"> </span><del>-#### Nesting UnitOfWork in a Database Transaction {@name=transactionnesting} - -The UOW flush operation places its INSERT/UPDATE/DELETE operations within the scope of a database transaction controlled by a SQLEngine: - - {python} - engine.begin() - try: - # run objectstore update operations - except: - engine.rollback() - raise - engine.flush() - -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: - - {python} - engine.begin() - try: - # perform custom SQL operations - objectstore.flush() - # perform custom SQL operations - except: - engine.rollback() - raise - engine.commit() - -#### Per-Object Sessions {@name=object} - -Sessions can be created on an ad-hoc basis and used for individual groups of objects and operations. This has the effect of bypassing the normal thread-local Session and explicitly using a particular Session: - - {python} - # make a new Session with a global UnitOfWork - s = objectstore.Session() - - # make objects bound to this Session - x = MyObj(_sa_session=s) - - # perform mapper operations bound to this Session - # (this function coming soon) - r = MyObj.mapper.using(s).select_by(id=12) - - # get the session that corresponds to an instance - s = objectstore.get_session(x) - - # flush - s.flush() - - # perform a block of operations with this session set within the current scope - objectstore.push_session(s) - try: - r = mapper.select_by(id=12) - x = new MyObj() - objectstore.flush() - finally: - objectstore.pop_session() - -##### Nested Transaction Sessions {@name=nested} - -Sessions also now support a "nested transaction" feature whereby a second Session can use a different database connection. This can be used inside of a larger database transaction to issue commits to the database that will be committed independently of the larger transaction's status: - - {python} - engine.begin() - try: - a = MyObj() - b = MyObj() - - sess = Session(nest_on=engine) - objectstore.push_session(sess) - try: - c = MyObj() - objectstore.commit() # will commit "c" to the database, - # even if the external transaction rolls back - finally: - objectstore.pop_session() - - objectstore.commit() # commit "a" and "b" to the database - engine.commit() - except: - engine.rollback() - raise - -#### Custom Session Objects/Custom Scopes {@name=scope} - -For users who want to make their own Session subclass, or replace the algorithm used to return scoped Session objects (i.e. the objectstore.get_session() method): - - {python title="Create a Session"} - # make a new Session - s = objectstore.Session() - - # set it as the current thread-local session - objectstore.session_registry.set(s) - - {python title="Create a custom Registry Algorithm"} - # set the objectstore's session registry to a different algorithm - - def create_session(): - """creates new sessions""" - return objectstore.Session() - def mykey(): - """creates contextual keys to store scoped sessions""" - return "mykey" - - objectstore.session_registry = sqlalchemy.util.ScopedRegistry(createfunc=create_session, scopefunc=mykey) - </del><span class="cx"> #### Analyzing Object Commits {@name=logging} </span><span class="cx"> </span><span class="cx"> The objectstore module can log an extensive display of its "commit plans", which is a graph of its internal representation of objects before they are committed to the database. To turn this logging on: </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-05-01 00:43:37
|
<!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>[1366] sqlalchemy/branches/schema/doc/build/content: uow docs.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1366</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 19:43:22 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>uow docs. going to go with cascade='all' as default for now....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -5,7 +5,7 @@ </span><span class="cx"> </span><span class="cx"> * **URL** - represents the identifier for a particular database. URL objects are usually created automatically based on a given connect string passed to the `create_engine()` function. </span><span class="cx"> * **Engine** - Combines a connection-providing resource with implementation-provided objects that know how to generate, execute, and gather information about SQL statements. It also provides the primary interface by which Connections are obtained, as well as a context for constructed SQL objects and schema constructs to "implicitly execute" themselves, which is an optional feature of SA 0.2. The Engine object that is normally dealt with is an instance of `sqlalchemy.engine.base.ComposedSQLEngine`. </span><del>-* **Connection** - represents a connection to the database. The underlying connection object returned by a DBAPI's connect() method is referenced internally by the Connection object. Connection provides methods that handle the execution of SQLAlchemy's own SQL construct objects, as well as literal string-based statements. </del><ins>+* **Connection** - represents a connection to the database. The underlying connection object returned by a DBAPI's connect() method is referenced internally by the Connection object. Connection provides methods that handle the execution of SQLAlchemy's own SQL constructs, as well as literal string-based statements. </ins><span class="cx"> * **Transaction** - represents a transaction on a single Connection. Includes `begin()`, `commit()` and `rollback()` methods that support basic "nestable" behavior, meaning an outermost transaction is maintained against multiple nested calls to begin/commit. </span><span class="cx"> * **ResultProxy** - Represents the results of an execution, and is most analgous to the cursor object in DBAPI. It primarily allows iteration over result sets, but also provides an interface to information about inserts/updates/deletes, such as the count of rows affected, last inserted IDs, etc. </span><span class="cx"> * **RowProxy** - Represents a single row returned by the fetchone() method on ResultProxy. </span><span class="lines">@@ -13,8 +13,8 @@ </span><span class="cx"> Underneath the public-facing API of `ComposedSQLEngine`, several components are provided by database implementations to provide the full behavior, including: </span><span class="cx"> </span><span class="cx"> * **Dialect** - this object is provided by database implementations to describe the behavior of a particular database. It acts as a repository for metadata about a database's characteristics, and provides factory methods for other objects that deal with generating SQL strings and objects that handle some of the details of statement execution. </span><del>-* **ConnectionProvider** - this object knows how to return a DBAPI connection object. It typically talks to a connection pool. -* **ExecutionContext** - this object is created for each execution of a single SQL statement, and stores information such as the last primary keys inserted, the total count of rows affected, etc. It also may implement any special logic that various DBAPI modules may require before or after a statement execution. </del><ins>+* **ConnectionProvider** - this object knows how to return a DBAPI connection object. It typically talks to a connection pool which maintains one or more connections in memory for quick re-use. +* **ExecutionContext** - this object is created for each execution of a single SQL statement, and tracks information about its execution such as primary keys inserted, the total count of rows affected, etc. It also may implement any special logic that various DBAPI implementations may require before or after a statement execution. </ins><span class="cx"> * **Compiler** - receives SQL expression objects and assembles them into strings that are suitable for direct execution, as well as collecting bind parameters into a dictionary or list to be sent along with the statement. </span><span class="cx"> * **SchemaGenerator** - receives collections of Schema objects and knows how to generate the appropriate SQL for `CREATE` and `DROP` statements. </span><span class="cx"> </span><span class="lines">@@ -153,11 +153,10 @@ </span><span class="cx"> # are returned to the pool. </span><span class="cx"> r2 = None </span><span class="cx"> </span><del>-While the `close()` method is still available with the "threadlocal" strategy, it should be used carefully. Above, if we issued a `close()` call on `r1`, and then tried to further work with results from `r2`, `r2` would be in an invalid state since its connection was already returned to the pool. By relying on __del__() to automatically clean up resources, this condition will never occur. </del><ins>+While the `close()` method is still available with the "threadlocal" strategy, it should be used carefully. Above, if we issued a `close()` call on `r1`, and then tried to further work with results from `r2`, `r2` would be in an invalid state since its connection was already returned to the pool. By relying on `__del__()` to automatically clean up resources, this condition will never occur. </ins><span class="cx"> </span><del>-At this point, you're probably saying, "wow, why would anyone *ever* want to use the [insert name here] strategy ??" Advantages to `plain` include that connection resources are immediately returned to the connection pool, without any reliance upon the __del__() method; there is no chance of resources being left around by a Python implementation that doesn't necessarily call __del__() immediately. Advantages to `threadlocal` include that resources can be left to clean up after themselves, application code can be more minimal, its guaranteed that only one connection is used per thread, and there is no chance of a "connection pool block", which is when an execution hangs because the current thread has already checked out all remaining resources. </del><ins>+At this point, you're probably saying, "wow, why would anyone *ever* want to use the [insert name here] strategy ??" Advantages to `plain` include that connection resources are immediately returned to the connection pool, without any reliance upon the `__del__()` method; there is no chance of resources being left around by a Python implementation that doesn't necessarily call `__del__()` immediately. Advantages to `threadlocal` include that resources can be left to clean up after themselves, application code can be more minimal, its guaranteed that only one connection is used per thread, and there is no chance of a "connection pool block", which is when an execution hangs because the current thread has already checked out all remaining resources. </ins><span class="cx"> </span><del>- </del><span class="cx"> ### Transactions {@name=transactions} </span><span class="cx"> </span><span class="cx"> The `Connection` object provides a `begin()` method which returns a `Transaction` object. This object is usually used within a try/except clause so that it is guaranteed to `rollback()` or `commit()`: </span><span class="lines">@@ -172,78 +171,36 @@ </span><span class="cx"> trans.rollback() </span><span class="cx"> raise </span><span class="cx"> </span><del>- -A SQLEngine also provides an interface to the transactional capabilities of the underlying DBAPI connection object, as well as the connection object itself. Note that when using the object-relational-mapping package, described in a later section, basic transactional operation is handled for you automatically by its "Unit of Work" system; the methods described here will usually apply just to literal SQL update/delete/insert operations or those performed via the SQL construction library. - -Typically, a connection is opened with `autocommit=False`. So to perform SQL operations and just commit as you go, you can simply pull out a connection from the connection pool, keep it in the local scope, and call commit() on it as needed. As long as the connection remains referenced, all other SQL operations within the same thread will use this same connection, including those used by the SQL construction system as well as the object-relational mapper, both described in later sections: </del><ins>+The `Transaction` object also handles "nested" behavior by keeping track of the outermost begin/commit pair. In this example, two functions both issue a transaction on a Connection, but only the outermost Transaction object actually takes effect when it is committed. </ins><span class="cx"> </span><del>- {python}conn = engine.connection() - - # execute SQL via the engine - engine.execute("insert into mytable values ('foo', 'bar')") - conn.commit() - - # execute SQL via the SQL construction library - mytable.insert().execute(col1='bat', col2='lala') - conn.commit() - -There is a more automated way to do transactions, and that is to use the engine's begin()/commit() functionality. When the begin() method is called off the engine, a connection is checked out from the pool and stored in a thread-local context. That way, all subsequent SQL operations within the same thread will use that same connection. Subsequent commit() or rollback() operations are performed against that same connection. In effect, its a more automated way to perform the "commit as you go" example above. - - {python}engine.begin() - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - engine.commit() - - -A traditional "rollback on exception" pattern looks like this: - - {python}engine.begin() - try: - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - except: - engine.rollback() - raise - engine.commit() - - -An shortcut which is equivalent to the above is provided by the `transaction` method: - - {python}def do_stuff(): - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - - engine.transaction(do_stuff) - -An added bonus to the engine's transaction methods is "reentrant" functionality; once you call begin(), subsequent calls to begin() will increment a counter that must be decremented corresponding to each commit() statement before an actual commit can happen. This way, any number of methods that want to insure a transaction can call begin/commit, and be nested arbitrarily: - - {python}# method_a starts a transaction and calls method_b - def method_a(): - engine.begin() </del><ins>+ {python} + # method_a starts a transaction and calls method_b + def method_a(connection): + trans = connection.begin() </ins><span class="cx"> try: </span><del>- method_b() </del><ins>+ method_b(connection) + trans.commit() # transaction is committed here </ins><span class="cx"> except: </span><del>- engine.rollback() </del><ins>+ trans.rollback() # this rolls back the transaction unconditionally </ins><span class="cx"> raise </span><del>- engine.commit() </del><span class="cx"> </span><del>- # method_b starts a transaction, or joins the one already in progress, - # and does some SQL - def method_b(): - engine.begin() </del><ins>+ # method_b also starts a transaction + def method_b(connection): + trans.begin() </ins><span class="cx"> try: </span><del>- engine.execute("insert into mytable values ('bat', 'lala')") - mytable.insert().execute(col1='bat', col2='lala') </del><ins>+ connection.execute("insert into mytable values ('bat', 'lala')") + connection.execute(mytable.insert(), dict(col1='bat', col2='lala')) + trans.commit() # transaction is not committed yet </ins><span class="cx"> except: </span><del>- engine.rollback() </del><ins>+ trans.rollback() # this rolls back the transaction unconditionally </ins><span class="cx"> raise </span><del>- engine.commit() </del><span class="cx"> </span><del>- # call method_a - method_a() </del><ins>+ # open a Connection and call method_a + conn = engine.connect() + method_a(conn) + conn.close() </ins><span class="cx"> </span><del>-Above, `method_a` is called first, which calls `engine.begin()`. Then it calls `method_b`. When `method_b` calls `engine.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. </del><ins>+Above, `method_a` is called first, which calls `connection.begin()`. Then it calls `method_b`. When `method_b` calls `connection.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. </ins><span class="cx"> </span><del>-The object-relational-mapper capability of SQLAlchemy includes its own `commit()` method that gathers SQL statements into a batch and runs them within one transaction. That transaction is also invokved within the scope of the "reentrant" methodology above; so multiple objectstore.commit() operations can also be bundled into a larger database transaction via the above methodology. - </del><ins>+Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in [unitofwork_transactions](rel:unitofwork_transactions). </ins><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -1,4 +1,4 @@ </span><del>-Unit of Work {@name=unitofwork} </del><ins>+Session / Unit of Work {@name=unitofwork} </ins><span class="cx"> ============ </span><span class="cx"> </span><span class="cx"> ### Overview {@name=overview} </span><span class="lines">@@ -11,45 +11,79 @@ </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><span class="cx"> * 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. </span><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><del>-* 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. </del><ins>+* The sole interface to the unit of work is provided via the `Session` object. Transactional capability, which rides on top of the transactions provided by `Engine` objects, is provided by the `SessionTransaction` object. +* Thread-locally scoped Session behavior is available as an option, which allows new objects to be automatically added to the Session corresponding to by the *default Session context*. Without a default Session context, an application must explicitly create a Session manually as well as add new objects to it. The default Session context, disabled by default, can also be plugged in with other user-defined schemes, which may also take into account the specific class being dealt with for a particular operation. +* The Session object in SQLAlchemy 0.2 borrows conceptually from that of [hibernate](http://www.hibernate.org), which is a leading ORM for Java. SQLAlchemy is in general very different from Hibernate, providing a different paradigm for producing queries, a SQL API that is useable independently of the ORM, and of course Pythonic configuration as opposed to XML; however, Hibernate has covered the bases pretty well with its Session ideology. </ins><span class="cx"> </span><del>-### The Session Interface {@name=session} </del><ins>+### Object States {@name=states} </ins><span class="cx"> </span><del>-The current unit of work is accessed via a Session object. The Session is available in a thread-local context from the objectstore module as follows: </del><ins>+When dealing with mapped instances with regards to Sessions, an instance may be *attached* or *unattached* to a particular Session. An instance also may or may not correspond to an actual row in the database. The product of these two binary conditions yields us four general states a particular instance can have within the perspective of the Session: </ins><span class="cx"> </span><ins>+* Transient - a transient instance exists within memory only and is not associated with any Session. It also has no database identity and does not have a corresponding record in the database. When a new instance of a class is constructed, and no default session context exists with which to automatically attach the new instance, it is a transient instance. The instance can then be saved to a particular session in which case it becomes a *pending* instance. If a default session context exists, new instances are added to that Session by default and therefore become *pending* instances immediately. + +* Pending - a pending instance is a Session-attached object that has not yet been assigned a database identity. When the Session is flushed (i.e. changes are persisted to the database), a pending instance becomes persistent. + +* Persistent - a persistent instance has a database identity and a corresponding record in the database, and is also associated with a particular Session. By "database identity" we mean the object is associated with a table or relational concept in the database combined with a particular primary key in that table. Objects that are loaded by SQLAlchemy in the context of a particular session are automatically considered persistent, as are formerly pending instances which have been subject to a session `flush()`. + +* Detached - a detached instance is an instance which has a database identity and corresponding row in the database, but is not attached to any Session. This occurs when an instance has been removed from a Session, either because the session itself was cleared or closed, or the instance was explicitly removed from the Session. The object can be re-attached with a session again in which case it becomes Persistent again. Detached instances are useful when an application needs to represent a long-running operation across multiple Sessions, needs to store an object in a serialized state and then restore it later (such as within an HTTP "session" object), or in some cases where code needs to load instances locally which will later be associated with some other Session. + +### Acquiring a Session {@name=getting} + +A new Session object is constructed via the `create_session()` function: + </ins><span class="cx"> {python} </span><del>- # get the current thread's session - session = objectstore.get_session() - -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: </del><ins>+ session = create_session() + +The session to which an object is attached can be acquired via the `object_session()` method, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: + + {python} + session = object_session(obj) + +When default session contexts are enabled, the current contextual session can be acquired by the `current_session()` function. This function takes an optional instance argument, which allows session contexts that are specific to particular class hierarchies to return the correct session. When using the "threadlocal" session context, enabled via the *mod* `sqlalchemy.mods.threadlocal`, no instance argument is required: + + {python} + # enable the thread-local default session context (only need to call this once per application) + import sqlalchemy.mods.threadlocal </ins><span class="cx"> </span><del>- {python}# this... - objectstore.get_session().flush() </del><ins>+ # return the Session that is bound to the current thread + session = current_session() </ins><span class="cx"> </span><ins>+When using the `threadlocal` mod, a familiar SA 0.1 keyword `objectstore` is imported into the `sqlalchemy` namespace. Using `objectstore`, methods can be called which will automatically be *proxied* to the Session that corresponds to `current_session()`: + + {python} + # load the 'threadlocal' mod *first* + import sqlalchemy.mods.threadlocal + + # then 'objectstore' is available within the 'sqlalchemy' namespace + from sqlalchemy import * + + # and then this... + current_session().flush() + </ins><span class="cx"> # is the same as this: </span><span class="cx"> objectstore.flush() </span><span class="cx"> </span><del>-A description of the most important methods and concepts follows. </del><ins>+We will now cover some of the key concepts used by Sessions and its underlying Unit of Work. </ins><span class="cx"> </span><del>-#### Identity Map {@name=identitymap} </del><ins>+### Introduction to the Identity Map {@name=identitymap} </ins><span class="cx"> </span><del>-The first concept to understand about the Unit of Work is that it is keeping track of all mapped objects which have been loaded from the database, as well as all mapped objects which have been saved to the database in the current session. This means that everytime you issue a `select` call to a mapper which returns results, all of those objects are now installed within the current Session, mapped to their identity. </del><ins>+A primary concept of the Session's underlying Unit of Work is that it is keeping track of all persistent instances; recall that a persistent instance has a database identity and is attached to a Session. In particular, the Unit of Work must insure that only *one* copy of a particular persistent instance exists within the Session at any given time. The UOW accomplishes this task using a dictionary known as an *Identity Map*. When a `Query` is used to issue `select` or `get` requests to the database, it will in nearly all cases result in an actual SQL execution to the database, and a corresponding traversal of rows received from that execution. However, when the underlying mapper *instantiates* objects corresponding to the result set rows it receives, it will *check the session's identity map first* before instantating a new object, and return *the same instance* already present in the identiy map if it already exists, essentially *ignoring* the object state r! epresented by that row. There are several ways to override this behavior and truly refresh an already-loaded instance which are described later, but the main idea is that once your instance is loaded into a particular Session, it will *never change* its state without your explicit approval, regardless of what the database says about it. </ins><span class="cx"> </span><del>-In particular, it is insuring that only *one* instance of a particular object, corresponding to a particular database identity, exists within the Session at one time. By "database identity" we mean a table or relational concept in the database combined with a particular primary key in that table. The session accomplishes this task using a dictionary known as an *Identity Map*. When `select` or `get` calls on mappers issue queries to the database, they will in nearly all cases go out to the database on each call to fetch results. However, when the mapper *instantiates* objects corresponding to the result set rows it receives, it will *check the current identity map first* before instantating a new object, and return *the same instance* already present in the identiy map if it already exists. - -Example: </del><ins>+For example; below, two separate calls to load an instance with database identity "15" are issued, and the results assigned to two separate variables. However, since the same `Session` was used, the two instances are the same instance: </ins><span class="cx"> </span><del>- {python}mymapper = mapper(MyClass, mytable) </del><ins>+ {python} + mymapper = mapper(MyClass, mytable) </ins><span class="cx"> </span><del>- obj1 = mymapper.selectfirst(mytable.c.id==15) - obj2 = mymapper.selectfirst(mytable.c.id==15) </del><ins>+ session = create_session() + obj1 = session.query(MyClass).selectfirst(mytable.c.id==15) + obj2 = session.query(MyClass).selectfirst(mytable.c.id==15) </ins><span class="cx"> </span><span class="cx"> >>> obj1 is obj2 </span><span class="cx"> True </span><span class="cx"> </span><del>-The Identity Map is an instance of `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. The current SA attributes implementation places some circular refs upon objects, although this may change in the future. There are other ways to remove object instances from the current session, as well as to clear the current session entirely, which are described later in this section. </del><ins>+The Identity Map is an instance of `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. To guarantee that an instance is removed from the identity map before removing references to it, use the `expunge()` method, described later, to remove it. </ins><span class="cx"> </span><del>-To view the Session's identity map, it is accessible via the `identity_map` accessor, and is an instance of `weakref.WeakValueDictionary`: </del><ins>+The Session's identity map is accessible via the `identity_map` accessor: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> objectstore.get_session().identity_map.values() </span><span class="lines">@@ -61,72 +95,38 @@ </span><span class="cx"> >>> obj._instance_key </span><span class="cx"> (<class 'test.tables.User'>, (7,)) </span><span class="cx"> </span><del>-At the moment that an object is assigned this key, it is also added to the current thread's unit-of-work's identity map. </del><ins>+At the moment that an object is assigned this key within a `flush()` operation, it is also added to the session's identity map. </ins><span class="cx"> </span><del>-The get() method on a mapper, which retrieves an object based on primary key identity, also checks in the current identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the get() method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. - -Methods on mappers and the objectstore module, which are relevant to identity include the following: </del><ins>+The get() method on `Query`, which retrieves an object based on primary key identity, also checks in the Session's identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the get() method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. </ins><span class="cx"> </span><del>- {python} - # assume 'm' is a mapper - m = mapper(User, users) </del><ins>+### Whats Changed ? {@name=changed} </ins><span class="cx"> </span><del>- # get the identity key corresponding to a primary key - key = m.identity_key(7) - - # for composite key, list out the values in the order they - # appear in the table - key = m.identity_key(12, 'rev2') - - # get the identity key given a primary key - # value as a tuple and a class - key = objectstore.get_id_key((12, 'rev2'), User) - - # get the identity key for an object, whether or not it actually - # has one attached to it (m is the mapper for obj's class) - key = m.instance_key(obj) </del><ins>+The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores lists of all *newly created* (i.e. pending) objects, lists of all persistent objects whose attributes have been *modified*, and lists of all persistent objects that have been marked as *deleted*. 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><del>- # is this key in the current identity map? - session.has_key(key) </del><ins>+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: </ins><span class="cx"> </span><del>- # is this object in the current identity map? - session.has_instance(obj) - - # get this object from the current identity map based on - # singular/composite primary key, or if not go - # and load from the database - obj = m.get(12, 'rev2') - -#### Whats Changed ? {@name=changed} - -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. - -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: - </del><span class="cx"> {python} </span><del>- # new objects that were just constructed </del><ins>+ # pending objects recently added to the Session </ins><span class="cx"> session.new </span><span class="cx"> </span><del>- # objects that exist in the database, that were modified </del><ins>+ # persistent objects with modifications </ins><span class="cx"> session.dirty </span><span class="cx"> </span><del>- # objects that have been marked as deleted via session.delete(obj) </del><ins>+ # persistent objects that have been marked as deleted via session.delete(obj) </ins><span class="cx"> session.deleted </span><span class="cx"> </span><del>- # list-based attributes thave been appended - session.modified_lists - </del><span class="cx"> Heres an interactive example, assuming the `User` and `Address` mapper setup first outlined in [datamapping_relations](rel:datamapping_relations): </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> # get the current thread's session - >>> session = objectstore.get_session() </del><ins>+ >>> # create a session + >>> session = create_session() </ins><span class="cx"> </span><span class="cx"> >>> # create a new object, with a list-based attribute </span><span class="cx"> >>> # containing two more new objects </span><span class="cx"> >>> u = User(user_name='Fred') </span><span class="cx"> >>> u.addresses.append(Address(city='New York')) </span><span class="cx"> >>> u.addresses.append(Address(city='Boston')) </span><ins>+ >>> session.save(u) </ins><span class="cx"> </span><span class="cx"> >>> # objects are in the "new" list </span><span class="cx"> >>> session.new </span><span class="lines">@@ -134,11 +134,6 @@ </span><span class="cx"> <__main__.Address object at 0x713a70>, </span><span class="cx"> <__main__.Address object at 0x713b30>] </span><span class="cx"> </span><del>- >>> # view the "modified lists" member, - >>> # reveals our two Address objects as well, inside of a list - >>> session.modified_lists - [[<__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>]] - </del><span class="cx"> >>> # lets view what the class/ID is for the list object </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="lines">@@ -150,10 +145,6 @@ </span><span class="cx"> >>> session.new </span><span class="cx"> [] </span><span class="cx"> </span><del>- >>> # the "modified lists" list is now empty - >>> session.modified_lists - [] - </del><span class="cx"> >>> # now lets modify an object </span><span class="cx"> >>> u.user_name='Ed' </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -144,9 +144,9 @@ </span><span class="cx"> self.cascade = mapperutil.CascadeOptions(cascade) </span><span class="cx"> else: </span><span class="cx"> if private: </span><del>- self.cascade = mapperutil.CascadeOptions("save-update, delete-orphan, delete") </del><ins>+ self.cascade = mapperutil.CascadeOptions("all, delete-orphan") </ins><span class="cx"> else: </span><del>- self.cascade = mapperutil.CascadeOptions() </del><ins>+ self.cascade = mapperutil.CascadeOptions("all") </ins><span class="cx"> </span><span class="cx"> self.association = association </span><span class="cx"> self.order_by = order_by </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-04-30 16:01:16
|
<!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>[1365] sqlalchemy/branches/schema/doc/build/content/dbengine.txt: doc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1365</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 11:01:08 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1364 => 1365)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 15:31:23 UTC (rev 1364) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 16:01:08 UTC (rev 1365) </span><span class="lines">@@ -39,23 +39,26 @@ </span><span class="cx"> </span><span class="cx"> Keyword options can also be specified to `create_engine()`, following the string URL as follows: </span><span class="cx"> </span><ins>+ {python} </ins><span class="cx"> db = create_engine('postgres://...', encoding='latin1', echo=True, module=psycopg1) </span><span class="cx"> </span><ins>+Options that can be specified include the following: + </ins><span class="cx"> * strategy='plain' : the Strategy describes the general configuration used to create this Engine. The two available values are `plain`, which is the default, and `threadlocal`, which applies a "thread-local context" to implicit executions performed by the Engine. This context is further described in the sections below. </span><span class="cx"> * pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in [pool](rel:pool)). If None, a default `Pool` (usually `QueuePool`, or `SingletonThreadPool` in the case of SQLite) will be created using the engine's connect arguments. </span><span class="cx"> </span><span class="cx"> Example: </span><span class="cx"> </span><del>- {python} - from sqlalchemy import * - import sqlalchemy.pool as pool - import MySQLdb </del><ins>+ {python} + from sqlalchemy import * + import sqlalchemy.pool as pool + import MySQLdb + + def getconn(): + return MySQLdb.connect(user='ed', dbname='mydb') + + engine = create_engine('mysql', pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40)) </ins><span class="cx"> </span><del>- def getconn(): - return MySQLdb.connect(user='ed', dbname='mydb') - - engine = create_engine('mysql', pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40)) - </del><span class="cx"> * echo=False : if True, the Engine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' "echo" data member can be modified at any time to turn logging on and off. If set to the string 'debug', result rows will be printed to the standard output as well. </span><span class="cx"> * logger=None : a file-like object where logging output can be sent, if echo is set to True. This defaults to sys.stdout. </span><span class="cx"> * module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if 2 cannot be found. For Oracle, its cx_Oracle. </span><span class="lines">@@ -93,7 +96,7 @@ </span><span class="cx"> </span><span class="cx"> In both execution styles above, the `Connection` object will also automatically return its resources to the connection pool when the object is garbage collected, i.e. its `__del__()` method is called. When using the standard C implementation of Python, this method is usually called immediately as soon as the object is dereferenced. With other Python implementations such as Jython, this is not so guaranteed. </span><span class="cx"> </span><del>-The execute method on `Engine` and `Connection` can also receive SQL clause constructs as well, which are described in [sqlconstruction](rel:sqlconstruction): </del><ins>+The execute method on `Engine` and `Connection` can also receive SQL clause constructs as well, which are described in [sql](rel:sql): </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> connection = engine.connect() </span><span class="lines">@@ -106,7 +109,7 @@ </span><span class="cx"> </span><span class="cx"> "Implicit" connections refer to the example above when the `execute()` method is called directly off the `Engine` object, *without* the usage of a `Connection` object, and resources are released by calling the `close()` method on the result object. When using "implicit" connections, the user has two choices, determined when the Engine is first created, as to how the resources of this connection should be used in relation to other connections. This is determined by the `strategy` argument to `create_engine()`, which has two possible values: `plain` and `threadlocal`. In `plain`, every `execute` call uses a distinct connection from the database, which is only released when the `close()` method on the Result is called. In `threadlocal`, multiple calls to `execute()` within the same thread will use the already-checked out connection resource if one is available, or if none is available will request a connection resource. </span><span class="cx"> </span><del>-It is crucial to note that the `plain` and `threadlocal` contexts **do not impact the `connect()` method on the Engine.** If you are using explicit Connection objects returned by `connect()` method, you have full control over the connection resources used. </del><ins>+It is crucial to note that the `plain` and `threadlocal` contexts **do not impact the connect() method on the Engine.** If you are using explicit Connection objects returned by `connect()` method, you have full control over the connection resources used. </ins><span class="cx"> </span><span class="cx"> The `plain` strategy is better suited to an application that insures the explicit releasing of the resources used by each execution. This is because each execution uses its own distinct connection resource, and as those resources remain open, multiple connections can be checked out from the pool quickly. Since the connection pool will block further requests when too many connections have been checked out, not keeping track of this can impact an application's stability. </span><span class="cx"> </span><span class="lines">@@ -128,7 +131,7 @@ </span><span class="cx"> # release connection 2 </span><span class="cx"> r2.close() </span><span class="cx"> </span><del>-The `threadlocal` strategy is better suited to a programming style which relies upon the __del__() method of Connection objects in order to return them to the connection pool, rather than explicitly issuing a `close()` statement upon the `Result` object. This is because all of the executions within a single thread will share the same connection, if one has already been checked out in the current thread. Using this style, an application will use only one connection per thread at most within the scope of all implicit executions. </del><ins>+The `threadlocal` strategy is better suited to a programming style which relies upon the `__del__()` method of Connection objects in order to return them to the connection pool, rather than explicitly issuing a `close()` statement upon the `Result` object. This is because all of the executions within a single thread will share the same connection, if one has already been checked out in the current thread. Using this style, an application will use only one connection per thread at most within the scope of all implicit executions. </ins><span class="cx"> </span><span class="cx"> {python title="Threadlocal Strategy"} </span><span class="cx"> db = create_engine('mysql://localhost/test', strategy='threadlocal') </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-04-30 15:31: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>[1364] sqlalchemy/branches/schema/doc/build/content/dbengine.txt: doc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1364</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 10:31:23 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1363 => 1364)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 05:37:11 UTC (rev 1363) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 15:31:23 UTC (rev 1364) </span><span class="lines">@@ -30,9 +30,9 @@ </span><span class="cx"> </span><span class="cx"> Available drivernames are `sqlite`, `mysql`, `postgres`, `oracle`, `mssql`, and `firebird`. For sqlite, the database name is the filename to connect to, or the special name ":memory:" which indicates an in-memory database. The URL is typically sent as a string to the `create_engine()` function: </span><span class="cx"> </span><del>- db = create_engine('postgres://scott:tiger@localhost:5432/mydatabase') </del><ins>+ pg_db = create_engine('postgres://scott:tiger@localhost:5432/mydatabase') </ins><span class="cx"> sqlite_db = create_engine('sqlite:///mydb.txt') </span><del>- mysql_db = create_engine('sqlite://localhost/foo') </del><ins>+ mysql_db = create_engine('mysql://localhost/foo') </ins><span class="cx"> oracle_db = create_engine('oracle://scott:tiger@dsn') </span><span class="cx"> </span><span class="cx"> ### Database Engine Options {@name=options} </span><span class="lines">@@ -41,6 +41,7 @@ </span><span class="cx"> </span><span class="cx"> db = create_engine('postgres://...', encoding='latin1', echo=True, module=psycopg1) </span><span class="cx"> </span><ins>+* strategy='plain' : the Strategy describes the general configuration used to create this Engine. The two available values are `plain`, which is the default, and `threadlocal`, which applies a "thread-local context" to implicit executions performed by the Engine. This context is further described in the sections below. </ins><span class="cx"> * pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in [pool](rel:pool)). If None, a default `Pool` (usually `QueuePool`, or `SingletonThreadPool` in the case of SQLite) will be created using the engine's connect arguments. </span><span class="cx"> </span><span class="cx"> Example: </span><span class="lines">@@ -65,92 +66,110 @@ </span><span class="cx"> * encoding='utf-8' : the encoding to use for Unicode translations - passed to all encode/decode methods. </span><span class="cx"> * echo_uow=False : when True, logs unit of work commit plans to the standard output. </span><span class="cx"> </span><del>-### Database Engine Methods {@name=methods} </del><ins>+### Using Connections {@name=connections} </ins><span class="cx"> </span><del>-A few useful methods off the SQLEngine are described here: </del><ins>+In this section we describe the explicit interface available on Engine. Note that when using the Object Relational Mapper (ORM) as well as when dealing with only with "bound" metadata objects (described later), SQLAlchemy deals with the Engine for you and you generally don't need to know much about it; in those cases, you can skip this section and go to [metadata](rel:metadata). </ins><span class="cx"> </span><del>- {python}engine = create_engine('postgres://hostname=localhost&amp;user=scott&amp;password=tiger&amp;database=test') </del><ins>+The Engine provides methods by which literal SQL text as well as SQL clause constructs can be compiled and executed. </ins><span class="cx"> </span><del>- # get a pooled DBAPI connection - conn = engine.connection() </del><ins>+ {python title="Explicit Connection"} + engine = create_engine('sqlite:///:memory:') + connection = engine.connect() + result = connection.execute("select * from mytable where col1=:col1", {'col1':5}) + for row in result: + print row['col1'], row['col2'] + connection.close() </ins><span class="cx"> </span><del>- # create/drop tables based on table metadata objects - # (see the next section, Table Metadata, for info on table metadata) - engine.create(mytable) - engine.drop(mytable) </del><ins>+The `close` method on `Connection` does not actually remove the underlying connection to the database, but rather indicates that the underlying resources can be returned to the connection pool. When using the `connect()` method, the DBAPI connection referenced by the `Connection` object is not referenced anywhere else. + + {python title="Implicit Connection"} + engine = create_engine('sqlite:///:memory:') + result = engine.execute("select * from mytable where col1=:col1", {'col1':5}) + for row in result: + print row['col1'], row['col2'] + result.close() </ins><span class="cx"> </span><del>- # get the DBAPI module being used - dbapi = engine.dbapi() </del><ins>+When executing off the Engine directly, a Connection is created and used automatically. The returned `ResultProxy` then has a `close()` method, which will return the resources used by the `Connection`. This is a more abbreviated style of usage which is also the method used when dealing with "bound" schema and statement objects, which are described later. </ins><span class="cx"> </span><del>- # get the default schema name - name = engine.get_default_schema_name() </del><ins>+In both execution styles above, the `Connection` object will also automatically return its resources to the connection pool when the object is garbage collected, i.e. its `__del__()` method is called. When using the standard C implementation of Python, this method is usually called immediately as soon as the object is dereferenced. With other Python implementations such as Jython, this is not so guaranteed. + +The execute method on `Engine` and `Connection` can also receive SQL clause constructs as well, which are described in [sqlconstruction](rel:sqlconstruction): </ins><span class="cx"> </span><del>- # execute some SQL directly, returns a ResultProxy (see the SQL Construction section for details) - result = engine.execute("select * from table where col1=:col1", {'col1':'foo'}) </del><ins>+ {python} + connection = engine.connect() + result = connection.execute(select([table1], table1.c.col1==5)) + for row in result: + print row['col1'], row['col2'] + connection.close() </ins><span class="cx"> </span><del>- # log a message to the engine's log stream - engine.log('this is a message') - - -### Using the Proxy Engine {@name=proxy} </del><ins>+#### Implicit Connection Contexts {@name=context} </ins><span class="cx"> </span><del>-The ProxyEngine is useful for applications that need to swap engines -at runtime, or to create their tables and mappers before they know -what engine they will use. One use case is an application meant to be -pluggable into a mix of other applications, such as a WSGI -application. Well-behaved WSGI applications should be relocatable; and -since that means that two versions of the same application may be -running in the same process (or in the same thread at different -times), WSGI applications ought not to depend on module-level or -global configuration. Using the ProxyEngine allows a WSGI application -to define tables and mappers in a module, but keep the specific -database connection uri as an application instance or thread-local -value. </del><ins>+"Implicit" connections refer to the example above when the `execute()` method is called directly off the `Engine` object, *without* the usage of a `Connection` object, and resources are released by calling the `close()` method on the result object. When using "implicit" connections, the user has two choices, determined when the Engine is first created, as to how the resources of this connection should be used in relation to other connections. This is determined by the `strategy` argument to `create_engine()`, which has two possible values: `plain` and `threadlocal`. In `plain`, every `execute` call uses a distinct connection from the database, which is only released when the `close()` method on the Result is called. In `threadlocal`, multiple calls to `execute()` within the same thread will use the already-checked out connection resource if one is available, or if none is available will request a connection resource. </ins><span class="cx"> </span><del>-The ProxyEngine is used in the same way as any other engine, with one -additional method: </del><ins>+It is crucial to note that the `plain` and `threadlocal` contexts **do not impact the `connect()` method on the Engine.** If you are using explicit Connection objects returned by `connect()` method, you have full control over the connection resources used. + +The `plain` strategy is better suited to an application that insures the explicit releasing of the resources used by each execution. This is because each execution uses its own distinct connection resource, and as those resources remain open, multiple connections can be checked out from the pool quickly. Since the connection pool will block further requests when too many connections have been checked out, not keeping track of this can impact an application's stability. + + {python title="Plain Strategy"} + db = create_engine('mysql://localhost/test', strategy='plain') </ins><span class="cx"> </span><del>- {python}# define the tables and mappers - from sqlalchemy import * - from sqlalchemy.ext.proxy import ProxyEngine </del><ins>+ # execute one statement and receive results. r1 now references a DBAPI connection resource. + r1 = db.execute("select * from table1") </ins><span class="cx"> </span><del>- engine = ProxyEngine() </del><ins>+ # execute a second statement and receive results. r2 now references a *second* DBAPI connection resource. + r2 = db.execute("select * from table2") + for row in r1: + ... + for row in r2: + ... + # release connection 1 + r1.close() </ins><span class="cx"> </span><del>- users = Table('users', engine, ... ) - - class Users(object): - pass - - assign_mapper(Users, users) - - def app(environ, start_response): - # later, connect the proxy engine to a real engine via the connect() method - engine.connect(environ['db_uri']) - # now you have a real db connection and can select, insert, etc. - </del><ins>+ # release connection 2 + r2.close() </ins><span class="cx"> </span><del>-#### Using the Global Proxy {@name=defaultproxy} - -There is an instance of ProxyEngine available within the schema package as `default_engine`. You can construct Table objects and not specify the engine parameter, and they will connect to this engine by default. To connect the default_engine, use the `global_connect` function. </del><ins>+The `threadlocal` strategy is better suited to a programming style which relies upon the __del__() method of Connection objects in order to return them to the connection pool, rather than explicitly issuing a `close()` statement upon the `Result` object. This is because all of the executions within a single thread will share the same connection, if one has already been checked out in the current thread. Using this style, an application will use only one connection per thread at most within the scope of all implicit executions. </ins><span class="cx"> </span><del>- {python}# define the tables and mappers - from sqlalchemy import * </del><ins>+ {python title="Threadlocal Strategy"} + db = create_engine('mysql://localhost/test', strategy='threadlocal') </ins><span class="cx"> </span><del>- # specify a table with no explicit engine - users = Table('users', - Column('user_id', Integer, primary_key=True), - Column('user_name', String) - ) </del><ins>+ # execute one statement and receive results. r1 now references a DBAPI connection resource. + r1 = db.execute("select * from table1") </ins><span class="cx"> </span><del>- # connect the global proxy engine - global_connect('sqlite://filename=foo.db') </del><ins>+ # execute a second statement and receive results. r2 now references the *same* resource as r1 + r2 = db.execute("select * from table2") </ins><span class="cx"> </span><del>- # create the table in the selected database - users.create() </del><ins>+ for row in r1: + ... + for row in r2: + ... + # dereference r1. the connection is still held by r2. + r1 = None </ins><span class="cx"> </span><ins>+ # dereference r2. with no more references to the underlying connection resources, they + # are returned to the pool. + r2 = None </ins><span class="cx"> </span><ins>+While the `close()` method is still available with the "threadlocal" strategy, it should be used carefully. Above, if we issued a `close()` call on `r1`, and then tried to further work with results from `r2`, `r2` would be in an invalid state since its connection was already returned to the pool. By relying on __del__() to automatically clean up resources, this condition will never occur. + +At this point, you're probably saying, "wow, why would anyone *ever* want to use the [insert name here] strategy ??" Advantages to `plain` include that connection resources are immediately returned to the connection pool, without any reliance upon the __del__() method; there is no chance of resources being left around by a Python implementation that doesn't necessarily call __del__() immediately. Advantages to `threadlocal` include that resources can be left to clean up after themselves, application code can be more minimal, its guaranteed that only one connection is used per thread, and there is no chance of a "connection pool block", which is when an execution hangs because the current thread has already checked out all remaining resources. + + </ins><span class="cx"> ### Transactions {@name=transactions} </span><span class="cx"> </span><ins>+The `Connection` object provides a `begin()` method which returns a `Transaction` object. This object is usually used within a try/except clause so that it is guaranteed to `rollback()` or `commit()`: + + {python} + trans = connection.begin() + try: + r1 = connection.execute(table1.select()) + connection.execute(table1.insert().execute(col1=7, col2='this is some data)) + trans.commit() + except: + trans.rollback() + raise + + </ins><span class="cx"> A SQLEngine also provides an interface to the transactional capabilities of the underlying DBAPI connection object, as well as the connection object itself. Note that when using the object-relational-mapping package, described in a later section, basic transactional operation is handled for you automatically by its "Unit of Work" system; the methods described here will usually apply just to literal SQL update/delete/insert operations or those performed via the SQL construction library. </span><span class="cx"> </span><span class="cx"> Typically, a connection is opened with `autocommit=False`. So to perform SQL operations and just commit as you go, you can simply pull out a connection from the connection pool, keep it in the local scope, and call commit() on it as needed. As long as the connection remains referenced, all other SQL operations within the same thread will use this same connection, including those used by the SQL construction system as well as the object-relational mapper, both described in later sections: </span></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-04-30 05:37:25
|
<!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>[1363] sqlalchemy/branches/schema/doc/build/content: doc...</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1363</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 00:37:11 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc...</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentdocument_basemyt">sqlalchemy/branches/schema/doc/build/content/document_base.myt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontenttutorialtxt">sqlalchemy/branches/schema/doc/build/content/tutorial.txt</a></li> </ul> <h3>Removed Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontenttrailmapmyt">sqlalchemy/branches/schema/doc/build/content/trailmap.myt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1362 => 1363)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 00:23:42 UTC (rev 1362) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 05:37:11 UTC (rev 1363) </span><span class="lines">@@ -1,76 +1,70 @@ </span><span class="cx"> Database Engines {@name=dbengine} </span><span class="cx"> ============================ </span><span class="cx"> </span><del>-A database engine is a subclass of `sqlalchemy.engine.Engine`, and is the starting point for where SQLAlchemy provides a layer of abstraction on top of the various DBAPI2 database modules. It serves as an abstract factory for database-specific implementation objects as well as a layer of abstraction over the most essential tasks of a database connection, including connecting, executing queries, returning result sets, and managing transactions. - -The average developer doesn't need to know anything about the interface or workings of an Engine in order to use it. Simply creating one, and then specifying it when constructing tables and other SQL objects is all that's needed. - -An Engine is also a layer of abstraction on top of the connection pooling described in the previous section. Once you have an Engine, you can retrieve pooled connections directly from its underlying connection pool via its own `connection()` method. However, if you're exclusively using SQLALchemy's SQL construction objects and/or object-relational mappers, all the details of connecting can be handled automatically by *binding* those objects to the Engine. </del><ins>+A database engine is a subclass of `sqlalchemy.sql.Engine`, and is the starting point for where SQLAlchemy provides a layer of abstraction on top of the various DBAPI2 database modules. For all databases supported by SA, there is a specific "implementation" module, found in the `sqlalchemy.databases` package, that provides all the objects an `Engine` needs in order to perform its job. A typical user of SQLAlchemy never needs to deal with these modules directly. For many purposes, the only knowledge that's needed is how to create an Engine for a particular connection URL. When dealing with direct execution of SQL statements, one would also be aware of Result, Connection, and Transaction objects. The primary public facing objects are: </ins><span class="cx"> </span><del>-This binding process is also completely optional, and explicitly managed connections can be used as well. - </del><ins>+* **URL** - represents the identifier for a particular database. URL objects are usually created automatically based on a given connect string passed to the `create_engine()` function. +* **Engine** - Combines a connection-providing resource with implementation-provided objects that know how to generate, execute, and gather information about SQL statements. It also provides the primary interface by which Connections are obtained, as well as a context for constructed SQL objects and schema constructs to "implicitly execute" themselves, which is an optional feature of SA 0.2. The Engine object that is normally dealt with is an instance of `sqlalchemy.engine.base.ComposedSQLEngine`. +* **Connection** - represents a connection to the database. The underlying connection object returned by a DBAPI's connect() method is referenced internally by the Connection object. Connection provides methods that handle the execution of SQLAlchemy's own SQL construct objects, as well as literal string-based statements. +* **Transaction** - represents a transaction on a single Connection. Includes `begin()`, `commit()` and `rollback()` methods that support basic "nestable" behavior, meaning an outermost transaction is maintained against multiple nested calls to begin/commit. +* **ResultProxy** - Represents the results of an execution, and is most analgous to the cursor object in DBAPI. It primarily allows iteration over result sets, but also provides an interface to information about inserts/updates/deletes, such as the count of rows affected, last inserted IDs, etc. +* **RowProxy** - Represents a single row returned by the fetchone() method on ResultProxy. </ins><span class="cx"> </span><ins>+Underneath the public-facing API of `ComposedSQLEngine`, several components are provided by database implementations to provide the full behavior, including: + +* **Dialect** - this object is provided by database implementations to describe the behavior of a particular database. It acts as a repository for metadata about a database's characteristics, and provides factory methods for other objects that deal with generating SQL strings and objects that handle some of the details of statement execution. +* **ConnectionProvider** - this object knows how to return a DBAPI connection object. It typically talks to a connection pool. +* **ExecutionContext** - this object is created for each execution of a single SQL statement, and stores information such as the last primary keys inserted, the total count of rows affected, etc. It also may implement any special logic that various DBAPI modules may require before or after a statement execution. +* **Compiler** - receives SQL expression objects and assembles them into strings that are suitable for direct execution, as well as collecting bind parameters into a dictionary or list to be sent along with the statement. +* **SchemaGenerator** - receives collections of Schema objects and knows how to generate the appropriate SQL for `CREATE` and `DROP` statements. + +### Supported Databases {@name=supported} + +Engines exist for SQLite, Postgres, MySQL, MS-SQL, and Oracle, using the Pysqlite, Psycopg (1 or 2), MySQLDB, adodbapi or pymssql, and cx_Oracle modules. There is also not-well tested support for Firebird. For each engine, a distinct Python module exists in the `sqlalchemy.databases` package, which provides implementations of some of the objects mentioned in the previous section. + </ins><span class="cx"> ### Establishing a Database Engine {@name=establishing} </span><del>- -Engines exist for SQLite, Postgres, MySQL, MS-SQL, and Oracle, using the Pysqlite, Psycopg (1 or 2), MySQLDB, adodbapi or pymssql, and cx_Oracle modules (there is also experimental support for Firebird). Each engine imports its corresponding module which is required to be installed. For Postgres and Oracle, an alternate module may be specified at construction time as well. - -The string based argument names for connecting are translated to the appropriate names when the connection is made; argument names include "host" or "hostname" for database host, "database", "db", or "dbname" for the database name (also is dsn for Oracle), "user" or "username" for the user, and "password", "pw", or "passwd" for the password. SQLite expects "filename" or "file" for the filename, or if None it defaults to "":memory:". </del><span class="cx"> </span><del>-The connection arguments can be specified as a string + dictionary pair, or a single URL-encoded string, as follows: - - {python}from sqlalchemy import * </del><ins>+SQLAlchemy 0.2 indicates the source of an Engine strictly via [RFC-1738](http://rfc.net/rfc1738.html) style URLs, combined with optional keyword arguments to specify options for the Engine. The form of the URL is: </ins><span class="cx"> </span><del>- # sqlite in memory - sqlite_engine = create_engine('sqlite', {'filename':':memory:'}, **opts) </del><ins>+ driver://username:password@host:port/database </ins><span class="cx"> </span><del>- # via URL - sqlite_engine = create_engine('sqlite://', **opts) - - # sqlite using a file - sqlite_engine = create_engine('sqlite', {'filename':'querytest.db'}, **opts) </del><ins>+Available drivernames are `sqlite`, `mysql`, `postgres`, `oracle`, `mssql`, and `firebird`. For sqlite, the database name is the filename to connect to, or the special name ":memory:" which indicates an in-memory database. The URL is typically sent as a string to the `create_engine()` function: </ins><span class="cx"> </span><del>- # via URL - sqlite_engine = create_engine('sqlite://filename=querytest.db', **opts) </del><ins>+ db = create_engine('postgres://scott:tiger@localhost:5432/mydatabase') + sqlite_db = create_engine('sqlite:///mydb.txt') + mysql_db = create_engine('sqlite://localhost/foo') + oracle_db = create_engine('oracle://scott:tiger@dsn') </ins><span class="cx"> </span><del>- # postgres - postgres_engine = create_engine('postgres', - {'database':'test', - 'host':'127.0.0.1', - 'user':'scott', - 'password':'tiger'}, **opts) </del><ins>+### Database Engine Options {@name=options} </ins><span class="cx"> </span><del>- # via URL - postgres_engine = create_engine('postgres://database=test&amp;host=127.0.0.1&amp;user=scott&amp;password=tiger') - - # mysql - mysql_engine = create_engine('mysql', - { - 'db':'mydb', - 'user':'scott', - 'passwd':'tiger', - 'host':'127.0.0.1' - } - **opts) - # oracle - oracle_engine = create_engine('oracle', - {'dsn':'mydsn', - 'user':'scott', - 'password':'tiger'}, **opts) - </del><ins>+Keyword options can also be specified to `create_engine()`, following the string URL as follows: </ins><span class="cx"> </span><del>- -Note that the general form of connecting to an engine is: </del><ins>+ db = create_engine('postgres://...', encoding='latin1', echo=True, module=psycopg1) </ins><span class="cx"> </span><del>- {python}# separate arguments - engine = create_engine( - <enginename>, - {<named DBAPI arguments>}, - <sqlalchemy options> - ) </del><ins>+* pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in [pool](rel:pool)). If None, a default `Pool` (usually `QueuePool`, or `SingletonThreadPool` in the case of SQLite) will be created using the engine's connect arguments. </ins><span class="cx"> </span><del>- # url - engine = create_engine('&lt;enginename&gt;://&lt;named DBAPI arguments&gt;', <sqlalchemy options>) </del><ins>+Example: </ins><span class="cx"> </span><ins>+ {python} + from sqlalchemy import * + import sqlalchemy.pool as pool + import MySQLdb + + def getconn(): + return MySQLdb.connect(user='ed', dbname='mydb') + + engine = create_engine('mysql', pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40)) + +* echo=False : if True, the Engine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' "echo" data member can be modified at any time to turn logging on and off. If set to the string 'debug', result rows will be printed to the standard output as well. +* logger=None : a file-like object where logging output can be sent, if echo is set to True. This defaults to sys.stdout. +* module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if 2 cannot be found. For Oracle, its cx_Oracle. +* default_ordering=False : if True, table objects and associated joins and aliases will generate information used for ordering by primary keys (or OIDs, if the database supports OIDs). This information is used by the Mapper system to when it constructs select queries to supply a default ordering to mapped objects. +* use_ansi=True : used only by Oracle; when False, the Oracle driver attempts to support a particular "quirk" of some Oracle databases, that the LEFT OUTER JOIN SQL syntax is not supported, and the "Oracle join" syntax of using &lt;column1&gt;(+)=&lt;column2&gt; must be used in order to achieve a LEFT OUTER JOIN. Its advised that the Oracle database be configured to have full ANSI support instead of using this feature. +* use_oids=False : used only by Postgres, will enable the column name "oid" as the object ID column. Postgres as of 8.1 has object IDs disabled by default. +* convert_unicode=False : if set to True, all String/character based types will convert Unicode values to raw byte values going into the database, and all raw byte values to Python Unicode coming out in result sets. This is an engine-wide method to provide unicode across the board. For unicode conversion on a column-by-column level, use the Unicode column type instead. +* encoding='utf-8' : the encoding to use for Unicode translations - passed to all encode/decode methods. +* echo_uow=False : when True, logs unit of work commit plans to the standard output. + </ins><span class="cx"> ### Database Engine Methods {@name=methods} </span><span class="cx"> </span><span class="cx"> A few useful methods off the SQLEngine are described here: </span><span class="lines">@@ -97,33 +91,7 @@ </span><span class="cx"> # log a message to the engine's log stream </span><span class="cx"> engine.log('this is a message') </span><span class="cx"> </span><del>-### Database Engine Options {@name=options} - -The remaining arguments to `create_engine` are keyword arguments that are passed to the specific subclass of `sqlalchemy.engine.SQLEngine` being used, as well as the underlying `sqlalchemy.pool.Pool` instance. All of the options described in the previous section [pooling_configuration](rel:pooling_configuration) can be specified, as well as engine-specific options: </del><span class="cx"> </span><del>-* pool=None : an instance of `sqlalchemy.pool.Pool` to be used as the underlying source for connections, overriding the engine's connect arguments (pooling is described in the previous section). If None, a default Pool (QueuePool or SingletonThreadPool as appropriate) will be created using the engine's connect arguments. - -Example: - - {python}from sqlalchemy import * - import sqlalchemy.pool as pool - import MySQLdb - - def getconn(): - return MySQLdb.connect(user='ed', dbname='mydb') - - engine = create_engine('mysql', pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40)) - -* echo=False : if True, the SQLEngine will log all statements as well as a repr() of their parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' "echo" data member can be modified at any time to turn logging on and off. If set to the string 'debug', result rows will be printed to the standard output as well. -* logger=None : a file-like object where logging output can be sent, if echo is set to True. This defaults to sys.stdout. -* module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if 2 cannot be found. For Oracle, its cx_Oracle. -* default_ordering=False : if True, table objects and associated joins and aliases will generate information used for ordering by primary keys (or OIDs, if the database supports OIDs). This information is used by the Mapper system to when it constructs select queries to supply a default ordering to mapped objects. -* use_ansi=True : used only by Oracle; when False, the Oracle driver attempts to support a particular "quirk" of some Oracle databases, that the LEFT OUTER JOIN SQL syntax is not supported, and the "Oracle join" syntax of using &lt;column1&gt;(+)=&lt;column2&gt; must be used in order to achieve a LEFT OUTER JOIN. Its advised that the Oracle database be configured to have full ANSI support instead of using this feature. -* use_oids=False : used only by Postgres, will enable the column name "oid" as the object ID column. Postgres as of 8.1 has object IDs disabled by default. -* convert_unicode=False : if set to True, all String/character based types will convert Unicode values to raw byte values going into the database, and all raw byte values to Python Unicode coming out in result sets. This is an engine-wide method to provide unicode across the board. For unicode conversion on a column-by-column level, use the Unicode column type instead. -* encoding='utf-8' : the encoding to use for Unicode translations - passed to all encode/decode methods. -* echo_uow=False : when True, logs unit of work commit plans to the standard output. - </del><span class="cx"> ### Using the Proxy Engine {@name=proxy} </span><span class="cx"> </span><span class="cx"> The ProxyEngine is useful for applications that need to swap engines </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentdocument_basemyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/document_base.myt (1362 => 1363)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/document_base.myt 2006-04-30 00:23:42 UTC (rev 1362) +++ sqlalchemy/branches/schema/doc/build/content/document_base.myt 2006-04-30 05:37:11 UTC (rev 1363) </span><span class="lines">@@ -4,8 +4,6 @@ </span><span class="cx"> </span><span class="cx"> files = [ </span><span class="cx"> 'tutorial', </span><del>- 'trailmap', - 'pooling', </del><span class="cx"> 'dbengine', </span><span class="cx"> 'metadata', </span><span class="cx"> 'sqlconstruction', </span><span class="lines">@@ -13,6 +11,7 @@ </span><span class="cx"> 'unitofwork', </span><span class="cx"> 'adv_datamapping', </span><span class="cx"> 'types', </span><ins>+ 'pooling', </ins><span class="cx"> 'docstrings', </span><span class="cx"> ] </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontenttrailmapmyt"></a> <div class="delfile"><h4>Deleted: sqlalchemy/branches/schema/doc/build/content/trailmap.myt (1362 => 1363)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/trailmap.myt 2006-04-30 00:23:42 UTC (rev 1362) +++ sqlalchemy/branches/schema/doc/build/content/trailmap.myt 2006-04-30 05:37:11 UTC (rev 1363) </span><span class="lines">@@ -1,53 +0,0 @@ </span><del>-<%flags>inherit='document_base.myt'</%flags> -<%attr>title='How to Read this Manual'</%attr> -<&|doclib.myt:item, name="howtoread", description="How to Read this Manual" &> - -<p>SQLAlchemy features a lot of tools and patterns to help in every area of writing applications that talk to relational databases. To achieve this, it has a lot of areas of functionality which work together to provide a cohesive package. Ultimately, just a little bit of familiarity with each concept is all that's needed to get off the ground.</p> - -<p>That said, here's two quick links that summarize the two most prominent features of SQLAlchemy: -<ul> - <li><&formatting.myt:link, path="datamapping", class_="trailbold"&> - a synopsis of how to map objects to database tables (Object Relational Mapping)</li> - <li><&formatting.myt:link, path="sql", class_="trailbold"&> - SQLAlchemy's own domain-oriented approach to constructing and executing SQL statements.</li> -</ul> -</p> - -<&|doclib.myt:item, name="trailmap", description="Trail Map" &> -<p>For a comprehensive tour through all of SQLAlchemy's components, below is a "Trail Map" of the knowledge dependencies between these components indicating the order in which concepts may be learned. Concepts marked in bold indicate features that are useful on their own. -</p> -<pre> -Start - | - | - |--- <&formatting.myt:link, class_="trailbold", path="pooling" &> - | | - | | - | |------ <&formatting.myt:link, path="pooling_configuration" &> - | | - | | - +--- <&formatting.myt:link, path="dbengine_establishing" &> | - | | - | | - |--------- <&formatting.myt:link, path="dbengine_options" &> - | - | - +---- <&formatting.myt:link, path="metadata_tables" &> - | - | - |---- <&formatting.myt:link, path="metadata_creating" &> - | - | - |---- <&formatting.myt:link, path="sql", class_="trailbold" &> - | | - | | - +---- <&formatting.myt:link, path="datamapping", class_="trailbold"&> | - | | | - | | | - | <&formatting.myt:link, path="unitofwork"&> | - | | | - | | | - | +----------- <&formatting.myt:link, path="adv_datamapping"&> - | - +----- <&formatting.myt:link, path="types"&> -</pre> -</&> -</&> </del></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontenttutorialtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/tutorial.txt (1362 => 1363)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-30 00:23:42 UTC (rev 1362) +++ sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-30 05:37:11 UTC (rev 1363) </span><span class="lines">@@ -491,7 +491,10 @@ </span><span class="cx"> ['fre...@fr...', 7] </span><span class="cx"> COMMIT </span><span class="cx"> </span><del>-Main documentation: [unitofwork](rel:unitofwork), [dbengine_transactions](rel:dbengine_transactions). </del><ins>+Main documentation: [unitofwork](rel:unitofwork) </ins><span class="cx"> </span><del>-Conclusion </del><ins>+Next Steps </ins><span class="cx"> ---------- </span><ins>+ +That covers a quick tour through the basic idea of SQLAlchemy, in its simplest form. Beyond that, one should familiarize oneself with the basics of Sessions, the various patterns that can be used to define different kinds of Mappers and relations among them, the rudimentary SQL types that are available when constructing Tables, and the basics of Engines, SQL statements, and database Connections. + </ins></span></pre> </div> </div> </body> </html> |
From: <co...@sq...> - 2006-04-30 00:23:53
|
<!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>[1362] sqlalchemy/branches/schema/doc/build/content: first pass completed</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1362</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-29 19:23:42 -0500 (Sat, 29 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>first pass completed</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdocstringsmyt">sqlalchemy/branches/schema/doc/build/content/docstrings.myt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontenttutorialtxt">sqlalchemy/branches/schema/doc/build/content/tutorial.txt</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdocstringsmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/docstrings.myt (1361 => 1362)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/docstrings.myt 2006-04-29 19:42:15 UTC (rev 1361) +++ sqlalchemy/branches/schema/doc/build/content/docstrings.myt 2006-04-30 00:23:42 UTC (rev 1362) </span><span class="lines">@@ -7,7 +7,7 @@ </span><span class="cx"> import sqlalchemy.engine.strategies as strategies </span><span class="cx"> import sqlalchemy.sql as sql </span><span class="cx"> import sqlalchemy.pool as pool </span><del>- import sqlalchemy.mapping as mapping </del><ins>+ import sqlalchemy.orm as orm </ins><span class="cx"> import sqlalchemy.exceptions as exceptions </span><span class="cx"> import sqlalchemy.ext.proxy as proxy </span><span class="cx"> import sqlalchemy.mods.threadlocal as threadlocal </span><span class="lines">@@ -18,9 +18,9 @@ </span><span class="cx"> <& pydoc.myt:obj_doc, obj=schema &> </span><span class="cx"> <& pydoc.myt:obj_doc, obj=engine, classes=[engine.ComposedSQLEngine, engine.Connection, engine.Transaction, engine.Dialect, engine.ConnectionProvider, engine.ExecutionContext, engine.ResultProxy, engine.RowProxy] &> </span><span class="cx"> <& pydoc.myt:obj_doc, obj=strategies &> </span><del>-<& pydoc.myt:obj_doc, obj=mapping, classes=[mapping.Mapper, mapping.MapperExtension] &> -<& pydoc.myt:obj_doc, obj=mapping.query, classes=[mapping.query.Query] &> -<& pydoc.myt:obj_doc, obj=mapping.objectstore, classes=[mapping.objectstore.Session, mapping.objectstore.SessionTransaction] &> </del><ins>+<& pydoc.myt:obj_doc, obj=orm, classes=[orm.Mapper, orm.MapperExtension] &> +<& pydoc.myt:obj_doc, obj=orm.query, classes=[orm.query.Query] &> +<& pydoc.myt:obj_doc, obj=orm.session, classes=[orm.session.Session, orm.session.SessionTransaction] &> </ins><span class="cx"> <& pydoc.myt:obj_doc, obj=threadlocal &> </span><span class="cx"> <& pydoc.myt:obj_doc, obj=exceptions &> </span><span class="cx"> <& pydoc.myt:obj_doc, obj=pool, classes=[pool.DBProxy, pool.Pool, pool.QueuePool, pool.SingletonThreadPool] &> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontenttutorialtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/tutorial.txt (1361 => 1362)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-29 19:42:15 UTC (rev 1361) +++ sqlalchemy/branches/schema/doc/build/content/tutorial.txt 2006-04-30 00:23:42 UTC (rev 1362) </span><span class="lines">@@ -1,7 +1,8 @@ </span><span class="cx"> Tutorial </span><span class="cx"> ======== </span><del>-This tutorial provides a relatively simple walking tour through the basic concepts of SQLAlchemy. You may wish to skip it and dive into the [main manual][manual] which is more reference-oriented. </del><ins>+This tutorial provides a relatively simple walking tour through the basic concepts of SQLAlchemy. You may wish to skip it and dive into the [main manual][manual] which is more reference-oriented. The examples in this tutorial comprise a fully working interactive Python session, and are guaranteed to be functioning courtesy of [doctest][]. </ins><span class="cx"> </span><ins>+[doctest]: http://www.python.org/doc/lib/module-doctest.html </ins><span class="cx"> [manual]: rel:howtoread </span><span class="cx"> </span><span class="cx"> Installation </span><span class="lines">@@ -98,29 +99,29 @@ </span><span class="cx"> With `metadata` as our established home for tables, lets make a Table for it: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> users = Table('users', metadata, </del><ins>+ >>> users_table = Table('users', metadata, </ins><span class="cx"> ... Column('user_id', Integer, primary_key=True), </span><span class="cx"> ... Column('user_name', String(40)), </span><del>- ... Column('password', String(80)) </del><ins>+ ... Column('password', String(10)) </ins><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 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`: </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 our 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><del>- >>> users.create() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </del><ins>+ >>> users_table.create() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </ins><span class="cx"> CREATE TABLE users( </span><span class="cx"> user_id INTEGER NOT NULL PRIMARY KEY, </span><span class="cx"> user_name VARCHAR(40), </span><del>- password VARCHAR(80) </del><ins>+ password VARCHAR(10) </ins><span class="cx"> ) </span><span class="cx"> ... </span><span class="cx"> </span><span class="cx"> Alternatively, the `users` table might already exist (such as, if you're running examples from this tutorial for the second time), in which case you can just skip the `create()` method call. You can even skip defining the individual columns in the `users` table and ask SQLAlchemy to load its definition from the database: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> users = Table('users', metadata, autoload=True) - >>> list(users.columns)[0].name </del><ins>+ >>> users_table = Table('users', metadata, autoload=True) + >>> list(users_table.columns)[0].name </ins><span class="cx"> 'user_id' </span><span class="cx"> </span><span class="cx"> Documentation on table metadata is available in [metadata](rel:metadata). </span><span class="lines">@@ -130,7 +131,7 @@ </span><span class="cx"> Inserting is achieved via the `insert()` method, which defines a *clause object* (known as a `ClauseElement`) representing an INSERT statement: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> i = users.insert() </del><ins>+ >>> i = users_table.insert() </ins><span class="cx"> >>> i # doctest:+ELLIPSIS </span><span class="cx"> <sqlalchemy.sql.Insert object at 0x...> </span><span class="cx"> >>> print i </span><span class="lines">@@ -161,7 +162,7 @@ </span><span class="cx"> Let's check that the data we have put into `users` table is actually there. The procedure is analogous to the insert example above, except you now call the `select()` method off the `users` table: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> s = users.select() </del><ins>+ >>> s = users_table.select() </ins><span class="cx"> >>> print s </span><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users </span><span class="lines">@@ -170,7 +171,7 @@ </span><span class="cx"> FROM users </span><span class="cx"> [] </span><span class="cx"> </span><del>-This time, we won't ignore the return value of `execute()`: </del><ins>+This time, we won't ignore the return value of `execute()`. Its an instance of `ResultProxy`, which is a result-holding object that behaves very similarly to the `cursor` object one deals with directly with a database API: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> r # doctest:+ELLIPSIS </span><span class="lines">@@ -180,10 +181,10 @@ </span><span class="cx"> >>> r.fetchall() </span><span class="cx"> [(2, u'Tom', None), (3, u'Fred', None), (4, u'Harry', None)] </span><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. All expressions constructed from `Column` objects are themselves instances of `ClauseElements`, just like the `Select`, `Insert`, and `Table` objects themselves. </del><ins>+Query criterion for the select is specified using 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><del>- >>> r = users.select(users.c.user_name=='Harry').execute() </del><ins>+ >>> r = users_table.select(users_table.c.user_name=='Harry').execute() </ins><span class="cx"> SELECT users.user_id, users.user_name, users.password </span><span class="cx"> FROM users </span><span class="cx"> WHERE users.user_name = ? </span><span class="lines">@@ -196,20 +197,20 @@ </span><span class="cx"> </span><span class="cx"> ### Working with Rows </span><span class="cx"> </span><del>-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: </del><ins>+You can see that when we print out the rows returned by an execution result, it prints the rows as tuples. These rows in fact support both the list and dictionary interfaces. The dictionary interface allows the addressing of columns by string column name, or even the original `Column` object: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> row.keys() </span><span class="cx"> ['user_id', 'user_name', 'password'] </span><del>- >>> row['user_id'], row[1], row[users.c.password] </del><ins>+ >>> row['user_id'], row[1], row[users_table.c.password] </ins><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><del>-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: </del><ins>+Result sets also support iteration. We'll show this with a slightly different form of `select` that allows you to specify the specific columns to be selected: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> for row in select([users.c.user_id, users.c.user_name]).execute(): # doctest:+NORMALIZE_WHITESPACE </del><ins>+ >>> for row in select([users_table.c.user_id, users_table.c.user_name]).execute(): # doctest:+NORMALIZE_WHITESPACE </ins><span class="cx"> ... print row </span><span class="cx"> SELECT users.user_id, users.user_name </span><span class="cx"> FROM users </span><span class="lines">@@ -224,7 +225,7 @@ </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="cx"> </span><span class="cx"> {python} </span><del>- >>> email_addresses = Table('email_addresses', metadata, </del><ins>+ >>> email_addresses_table = Table('email_addresses', metadata, </ins><span class="cx"> ... Column('address_id', Integer, primary_key=True), </span><span class="cx"> ... Column('email_address', String(100), nullable=False), </span><span class="cx"> ... Column('user_id', Integer, ForeignKey('users.user_id'))).create() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE </span><span class="lines">@@ -240,15 +241,17 @@ </span><span class="cx"> Next, lets put a few rows in: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> email_addresses.insert().execute({'email_address':'to...@to...', 'user_id':2},{'email_address':'ma...@ma...', 'user_id':1}) #doctest:+ELLIPSIS </del><ins>+ >>> email_addresses_table.insert().execute( + ... {'email_address':'to...@to...', 'user_id':2}, + ... {'email_address':'ma...@ma...', 'user_id':1}) #doctest:+ELLIPSIS </ins><span class="cx"> INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) </span><span class="cx"> [['to...@to...', 2], ['ma...@ma...', 1]] </span><span class="cx"> <sqlalchemy.engine.base.ResultProxy instance at 0x...> </span><span class="cx"> </span><del>-With two related tables, we can now construct a join amongst them, like this: </del><ins>+With two related tables, we can now construct a join amongst them using the `join` method: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> r = users.join(email_addresses).select().execute() </del><ins>+ >>> r = users_table.join(email_addresses_table).select().execute() </ins><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><span class="lines">@@ -258,7 +261,10 @@ </span><span class="cx"> 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: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> print join(users, email_addresses, and_(users.c.user_id==email_addresses.c.user_id, users.c.password==email_addresses.c.email_address)) </del><ins>+ >>> print join(users_table, email_addresses_table, + ... and_(users_table.c.user_id==email_addresses_table.c.user_id, + ... users_table.c.password==email_addresses_table.c.email_address) + ... ) </ins><span class="cx"> users JOIN email_addresses ON users.user_id = email_addresses.user_id AND users.password = email_addresses.email_address </span><span class="cx"> </span><span class="cx"> Working with Object Mappers {@name=orm} </span><span class="lines">@@ -278,37 +284,25 @@ </span><span class="cx"> 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: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> usermapper = mapper(User, users) </del><ins>+ >>> usermapper = mapper(User, users_table) </ins><span class="cx"> >>> u1 = User() </span><span class="cx"> >>> print u1.user_name </span><span class="cx"> None </span><span class="cx"> >>> print u1.password </span><span class="cx"> None </span><span class="cx"> </span><del>-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. </del><ins>+The `mapper` function returns a new instance of `Mapper`. As it is the first Mapper we have created for the `User` class, it is known as the classes' *primary mapper*. We generally don't need to hold onto the `usermapper` instance variable; SA's ORM can automatically locate this Mapper when it deals with the class, or instances of that class. </ins><span class="cx"> </span><del>-### Querying Objects {@name=querying} - -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 Fred,password:None), (User Harry,password:None)] - </del><span class="cx"> ### Obtaining a Session {@name=session} </span><span class="cx"> </span><del>-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. </del><ins>+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 *attached* to a `Session` object, which represents a kind of "workspace" of objects that are loaded into memory. A particular object instance can only be attached to one `Session` at a time. </ins><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 load or save 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. So now, lets get a handle to that `Session` and deal with it directly. To locate the `Session` corresponding to the current thread, just use `current_session()`: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> session = get_session() </del><ins>+ >>> session = current_session() </ins><span class="cx"> >>> session # doctest:+ELLIPSIS </span><del>- <sqlalchemy.mapping.objectstore.Session object at 0x...> </del><ins>+ <sqlalchemy.orm.session.Session object at 0x...> </ins><span class="cx"> </span><span class="cx"> ### The Query Object {@name=query} </span><span class="cx"> </span><span class="lines">@@ -323,7 +317,7 @@ </span><span class="cx"> ['Harry'] </span><span class="cx"> [(User Harry,password:None)] </span><span class="cx"> </span><del>-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. </del><ins>+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 `current_session()` to locate the session bound to the current thread, if one is available. </ins><span class="cx"> </span><span class="cx"> 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: </span><span class="cx"> </span><span class="lines">@@ -340,7 +334,7 @@ </span><span class="cx"> >>> print query.count() </span><span class="cx"> 4 </span><span class="cx"> </span><del>-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. </del><ins>+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_table.c.user_name`, recalling that `User` is the Python class and `users` is our `Table` object. </ins><span class="cx"> </span><span class="cx"> ### Making Changes {@name=changes} </span><span class="cx"> </span><span class="lines">@@ -348,7 +342,7 @@ </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> ed = User() </span><del>- >>> ed.user_name = 'ed' </del><ins>+ >>> ed.user_name = 'Ed' </ins><span class="cx"> >>> ed.password = 'edspassword' </span><span class="cx"> >>> ed in session </span><span class="cx"> True </span><span class="lines">@@ -393,14 +387,110 @@ </span><span class="cx"> INSERT INTO users (user_name, password) VALUES (?, ?) </span><span class="cx"> [None, None] </span><span class="cx"> INSERT INTO users (user_name, password) VALUES (?, ?) </span><del>- ['ed', 'edspassword'] </del><ins>+ ['Ed', 'edspassword'] </ins><span class="cx"> DELETE FROM users WHERE users.user_id = ? </span><span class="cx"> [[3]] </span><span class="cx"> COMMIT </span><span class="cx"> </span><ins>+### Relationships + +When our User object contains relationships to other kinds of information, such as a list of email addresses, we can indicate this by using a function when creating the `Mapper` called `relation()`. While there is a lot you can do with relations, we'll cover a simple one here. First, recall that our `users` table has a foreign key relationship to another table called `email_addresses`. A single row in `email_addresses` has a column `user_id` that references a row in the `users` table; since many rows in the `email_addresses` table can reference a single row in `users`, this is called a *one to many* relationship. + +First, deal with the `email_addresses` table by itself. We will create a new class `Address` which represents a single row in the `email_addresses` table, and a corresponding `Mapper` which will associate the `Address` class with the `email_addresses` table: + + {python} + >>> class Address(object): + ... def __init__(self, email_address): + ... self.email_address = email_address + ... def __repr__(self): + ... return "(Address %s)" % (self.email_address) + >>> mapper(Address, email_addresses_table) # doctest: +ELLIPSIS + <sqlalchemy.orm.mapper.Mapper object at 0x...> </ins><span class="cx"> </span><ins>+Next, we associate the `User` and `Address` classes together by creating a relation using `relation()`, and then adding that relation to the `User` mapper, using the `add_property` function: + + {python} + >>> usermapper.add_property('addresses', relation(Address)) + +The `relation()` function takes either a class or a Mapper as its first argument, and has many options to further control its behavior. The 'User' mapper has now placed additional property on each `User` instance called `addresses`. SQLAlchemy will automatically determine that this relationship is a one-to-many relationship, and will subsequently create `addresses` as a list. When a new `User` is created, this list will begin as empty. + +Lets see what we get for the email addresses already in the database. Since we have made a change to the mapper's configuration, its best that we clear out our `Session`, which is currently holding onto every `User` object we have already loaded: + + {python} + >>> session.clear() + +We can then treat the `addresses` attribute on each `User` object like a regular list: + + {python} + >>> mary = query.get_by(user_name='Mary') # doctest: +NORMALIZE_WHITESPACE + 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 + LIMIT 1 OFFSET 0 + ['Mary'] + >>> print [a for a in mary.addresses] + SELECT email_addresses.user_id AS email_addresses_user_id, email_addresses.address_id AS email_addresses_address_id, email_addresses.email_address AS email_addresses_email_address + FROM email_addresses + WHERE email_addresses.user_id = ? ORDER BY email_addresses.oid + [1] + [(Address ma...@ma...)] + +Adding to the list is just as easy. New `Address` objects will be detected and saved when we `flush` the Session: + + {python} + >>> mary.addresses.append(Address('ma...@gm...')) + >>> session.flush() # doctest: +NORMALIZE_WHITESPACE + BEGIN + INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) + ['ma...@gm...', 1] + COMMIT + +Main documentation for using mappers: [datamapping](rel:datamapping) + </ins><span class="cx"> ### Transactions </span><span class="cx"> </span><ins>+You may have noticed from the example above that when we say `session.flush()`, SQLAlchemy indicates the names `BEGIN` and `COMMIT` to indicate a transaction with the database. The `flush()` method, since it may execute many statements in a row, will automatically use a transaction in order to execute these instructions. But what if we want to use `flush()` inside of a larger transaction? This is performed via the `SessionTransaction` object, which we can establish using `session.create_transaction()`. Below, we will perform a more complicated `SELECT` statement, make several changes to our collection of users and email addresess, and then create a new user with two email addresses, within the context of a transaction. We will perform a `flush()` in the middle of it to write the changes we have so far, and then allow the remaining changes to be written when we finally `commit()` the transaction. We enclose our operations within a `try/except` block to insu! re that resources are properly freed: + + {python} + >>> transaction = session.create_transaction() + >>> try: # doctest: +NORMALIZE_WHITESPACE + ... (ed, harry, mary) = session.query(User).select( + ... User.c.user_name.in_('Ed', 'Harry', 'Mary'), order_by=User.c.user_name + ... ) + ... del mary.addresses[1] + ... harry.addresses.append(Address('ha...@gm...')) + ... session.flush() + ... print "***flushed the session***" + ... fred = User() + ... fred.user_name = 'fred_again' + ... fred.addresses.append(Address('fr...@fr...')) + ... fred.addresses.append(Address('fre...@fr...')) + ... transaction.commit() + ... except: + ... transaction.rollback() + ... raise + BEGIN + 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 IN (?, ?, ?) ORDER BY users.user_name + ['Ed', 'Harry', 'Mary'] + SELECT email_addresses.user_id AS email_addresses_user_id, email_addresses.address_id AS email_addresses_address_id, email_addresses.email_address AS email_addresses_email_address + FROM email_addresses + WHERE email_addresses.user_id = ? ORDER BY email_addresses.oid + [4] + UPDATE email_addresses SET user_id=? WHERE email_addresses.address_id = ? + [None, 3] + INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) + ['ha...@gm...', 4] + ***flushed the session*** + INSERT INTO users (user_name, password) VALUES (?, ?) + ['fred_again', None] + INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) + ['fr...@fr...', 7] + INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?) + ['fre...@fr...', 7] + COMMIT + </ins><span class="cx"> Main documentation: [unitofwork](rel:unitofwork), [dbengine_transactions](rel:dbengine_transactions). </span><span class="cx"> </span><span class="cx"> Conclusion </span></span></pre> </div> </div> </body> </html> |