[Sqlalchemy-commits] [1377] sqlalchemy/branches/schema/test: docs etc, reversed order of table sorts
Brought to you by:
zzzeek
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> |