[Sqlalchemy-commits] [1198] sqlalchemy/trunk/doc: doc devel
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-03-25 17:23:52
|
<!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>[1198] sqlalchemy/trunk/doc: doc devel</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1198</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-03-25 11:23:41 -0600 (Sat, 25 Mar 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc devel</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunkdocbuildcontentdatamappingmyt">sqlalchemy/trunk/doc/build/content/datamapping.myt</a></li> <li><a href="#sqlalchemytrunkdocbuildcontentdocument_basemyt">sqlalchemy/trunk/doc/build/content/document_base.myt</a></li> <li><a href="#sqlalchemytrunkdocbuildcontentmetadatamyt">sqlalchemy/trunk/doc/build/content/metadata.myt</a></li> <li><a href="#sqlalchemytrunkdocbuildcontentunitofworkmyt">sqlalchemy/trunk/doc/build/content/unitofwork.myt</a></li> <li><a href="#sqlalchemytrunkdocdocscss">sqlalchemy/trunk/doc/docs.css</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkdocbuildcontentdatamappingmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/build/content/datamapping.myt (1197 => 1198)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/build/content/datamapping.myt 2006-03-25 15:25:29 UTC (rev 1197) +++ sqlalchemy/trunk/doc/build/content/datamapping.myt 2006-03-25 17:23:41 UTC (rev 1198) </span><span class="lines">@@ -6,9 +6,9 @@ </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. </p> </span><span class="cx"> </span><del>-<p>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 also 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 <b>Unit of Work</b>). The <span class="codeline">__init__()</span> method of the object is also decorated to communicate changes when new instances of the object are created.</p> </del><ins>+<p>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 <b>Unit of Work</b>). The <code>__init__()</code> method of the object is also decorated to communicate changes when new instances of the object are created.</p> </ins><span class="cx"> </span><del>-<p>The Mapper also provides the interface by which instances of the object are loaded from the database. The primary method for this is its <span class="codeline">select()</span> method, which has similar arguments to a <span class="codeline">sqlalchemy.sql.Select</span> 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.</p> </del><ins>+<p>The Mapper also provides the interface by which instances of the object are loaded from the database. The primary method for this is its <code>select()</code> method, which has similar arguments to a <code>sqlalchemy.sql.Select</code> 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.</p> </ins><span class="cx"> </span><span class="cx"> <p>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. </span><span class="cx"> </p> </span><span class="lines">@@ -65,15 +65,26 @@ </span><span class="cx"> </span><span class="cx"> userlist = User.mapper.select_by(user_id=12) </span><span class="cx"> </&> </span><del>- <p>There is also a full-blown "monkeypatch" function that creates a primary mapper, attaches the above mapper class property, and also the methods <span class="codeline">get, get_by, select, select_by, selectone, selectfirst, commit</span> and <span class="codeline">delete</span>:</p> </del><ins>+ <p>There is also a full-blown "monkeypatch" function that creates a primary mapper, attaches the above mapper class property, and also the methods <code>get, get_by, select, select_by, selectone, selectfirst, commit</code> and <code>delete</code>:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><ins>+ # "assign" a mapper to the User class/users table </ins><span class="cx"> assign_mapper(User, users) </span><ins>+ + # methods are attached to the class for selecting </ins><span class="cx"> userlist = User.select_by(user_id=12) </span><ins>+ + myuser = User.get(1) + + # mark an object as deleted for the next commit + myuser.delete() + + # commit the changes on a specific object + myotheruser.commit() </ins><span class="cx"> </&> </span><span class="cx"> <p>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.</p> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="overriding", description="Overriding Properties"&> </span><del>- <p>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 just how its done normally; 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:</p> </del><ins>+ <p>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:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> class MyClass(object): </span><span class="cx"> def _set_email(self, email): </span><span class="lines">@@ -125,6 +136,10 @@ </span><span class="cx"> </span><span class="cx"> # using a WHERE criterion to get a scalar </span><span class="cx"> u = mapper.selectfirst(users.c.user_name=='john') </span><ins>+ + # selectone() is a stricter version of selectfirst() which + # will raise an exception if there is not exactly one row + u = mapper.selectone(users.c.user_name=='john') </ins><span class="cx"> </span><span class="cx"> # using a full select object </span><span class="cx"> result = mapper.select(users.select(users.c.user_name=='john')) </span><span class="lines">@@ -135,7 +150,7 @@ </span><span class="cx"> # or using a "text" object </span><span class="cx"> result = mapper.select(text("select * from users where user_name='fred'", engine=engine)) </span><span class="cx"> </&> </span><del>- <p>The last few examples above show 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 <span class="codeline">c</span> to the class itself, which can be used just like the table metadata to access the columns of the table:</p> </del><ins>+ <p>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 <code>c</code> to the class itself, which can be used just like the table metadata to access the columns of the table:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> User.mapper = mapper(User, users) </span><span class="cx"> </span><span class="lines">@@ -143,9 +158,9 @@ </span><span class="cx"> </&> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="saving", description="Saving Objects" &> </span><del>- <p>When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called <span class="codeline">sqlalchemy.mapping.objectstore</span>. The changes are then written to the database when an application calls <span class="codeline">objectstore.commit()</span>. This pattern is known as a <b>Unit of Work</b>, 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 ef! fectively in all cases; the unit of work handles all the details.</p> - - <p>When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its <span class="codeline">__init__()</span> method is also decorated to mark new objects as "new".</p> </del><ins>+ <p>When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called <code>sqlalchemy.mapping.objectstore</code>. The changes are then written to the database when an application calls <code>objectstore.commit()</code>. This pattern is known as a <b>Unit of Work</b>, 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 th! e details.</p> + <p>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 <&formatting.myt:link, path="unitofwork"&> section for a full description on all its operations, including session control, deletion, and developmental guidelines.</p> + <p>When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its <code>__init__()</code> method is also decorated to mark new objects as "new".</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> User.mapper = mapper(User, users) </span><span class="cx"> </span><span class="lines">@@ -209,13 +224,14 @@ </span><span class="cx"> </&> </span><span class="cx"> </span><span class="cx"> <p>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.</p> </span><ins>+ </ins><span class="cx"> </&> </span><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="relations", description="Defining and Using Relationships" &> </span><del>-<p>So that covers how to map the columns in a table to an object, how to load objects, create new ones, and save changes. The next step is how to define an object's relationships to other database-persisted objects. This is done via the <span class="codeline">relation</span> function provided by the mapper module. So with our User class, lets also define the User has having one or more mailing addresses. First, the table metadata:</p> </del><ins>+<p>So that covers how to map the columns in a table to an object, how to load objects, create new ones, and save changes. The next step is how to define an object's relationships to other database-persisted objects. This is done via the <code>relation</code> function provided by the mapper module. So with our User class, lets also define the User has having one or more mailing addresses. First, the table metadata:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> from sqlalchemy import * </span><del>- engine = create_engine('sqlite', {'filename':'mydb'}) </del><ins>+ engine = create_engine('sqlite://filename=mydb') </ins><span class="cx"> </span><span class="cx"> # define user table </span><span class="cx"> users = Table('users', engine, </span><span class="lines">@@ -250,7 +266,7 @@ </span><span class="cx"> self.state = state </span><span class="cx"> self.zip = zip </span><span class="cx"> </&> </span><del>-<p>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 <span class="codeline">properties</span> which is a dictionary relating the name of an object property to a database relationship, in this case a <span class="codeline">relation</span> object against a newly defined mapper for the Address class:</p> </del><ins>+<p>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 <code>properties</code> which is a dictionary relating the name of an object property to a database relationship, in this case a <code>relation</code> object against a newly defined mapper for the Address class:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> User.mapper = mapper(User, users, properties = { </span><span class="cx"> 'addresses' : relation(mapper(Address, addresses)) </span><span class="lines">@@ -277,7 +293,7 @@ </span><span class="cx"> </&> </span><span class="cx"> <p>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.</p> </span><span class="cx"> </span><del>-<p>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 <span class="codeline">sqlalchemy.util.HistoryArraySet</span>, which fulfills the full set of Python list accessors, but maintains a <b>unique</b> set of objects (based on their in-memory identity), and also tracks additions and deletions to the list:</p> </del><ins>+<p>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 <code>sqlalchemy.util.HistoryArraySet</code>, which fulfills the full set of Python list accessors, but maintains a <b>unique</b> set of objects (based on their in-memory identity), and also tracks additions and deletions to the list:</p> </ins><span class="cx"> <&|formatting.myt:code&> </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="lines">@@ -295,7 +311,7 @@ </span><span class="cx"> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="private", description="Useful Feature: Private Relations" &> </span><del>-<p>So our one address that was removed from the list, was updated to have a user_id of <span class="codeline">None</span>, 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 <span class="codeline">private=True</span> parameter of <span class="codeline">relation</span>: </del><ins>+<p>So our one address that was removed from the list, was updated to have a user_id of <code>None</code>, 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 <code>private=True</code> parameter of <code>relation</code>: </ins><span class="cx"> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> User.mapper = mapper(User, users, properties = { </span><span class="lines">@@ -315,10 +331,10 @@ </span><span class="cx"> </&> </span><span class="cx"> </span><span class="cx"> </&> </span><del>-<p>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 <span class="codeline">private</span> 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.</p> </del><ins>+<p>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 <code>private</code> 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.</p> </ins><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="backreferences", description="Useful Feature: Backreferences" &> </span><del>-<p>By creating relations with the <span class="codeline">backref</span> 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": </del><ins>+<p>By creating relations with the <code>backref</code> 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": </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> Address.mapper = mapper(Address, addresses) </span><span class="cx"> User.mapper = mapper(User, users, properties = { </span><span class="lines">@@ -343,7 +359,7 @@ </span><span class="cx"> True </span><span class="cx"> </&> </span><span class="cx"> </span><del>-<p>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 <span class="codeline">backref()</span> function: </del><ins>+<p>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 <code>backref()</code> function: </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> Address.mapper = mapper(Address, addresses) </span><span class="cx"> </span><span class="lines">@@ -355,7 +371,7 @@ </span><span class="cx"> </&> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="cascade", description="Creating Relationships Automatically with cascade_mappers" &> </span><del>-<p>The mapper package has a helper function <span class="codeline">cascade_mappers()</span> 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 </del><ins>+<p>The mapper package has a helper function <code>cascade_mappers()</code> 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 </ins><span class="cx"> the "secondary" table in a many-to-many relationship as well. The names of the relations </span><span class="cx"> are a lowercase version of the related class. In the case of one-to-many or many-to-many, </span><span class="cx"> the name is "pluralized", which currently is based on the English language (i.e. an 's' or </span><span class="lines">@@ -380,7 +396,7 @@ </span><span class="cx"> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="lazyload", description="Selecting from Relationships: Lazy Load" &> </span><del>- <P>We've seen how the <span class="codeline">relation</span> 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 <b>Lazy Loader</b> 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.</p> </del><ins>+ <P>We've seen how the <code>relation</code> 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 <b>Lazy Loader</b> 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.</p> </ins><span class="cx"> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # define a mapper </span><span class="lines">@@ -411,7 +427,7 @@ </span><span class="cx"> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="relselectby", description="Useful Feature: Creating Joins via select_by" &> </span><del>- <p>In mappers that have relationships, the <span class="codeline">select_by</span> 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>+ <p>In mappers that have relationships, the <code>select_by</code> 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: </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> <&formatting.myt:poplink&>l = User.mapper.select_by(street='123 Green Street') </span><span class="cx"> <&|formatting.myt:codepopper, link="sql" &>SELECT users.user_id AS users_user_id, </span><span class="lines">@@ -441,7 +457,7 @@ </span><span class="cx"> </&> </span><span class="cx"> <p>This operation will clear out all currently mapped object instances, and subsequent select statements will load fresh copies from the databse.</p> </span><span class="cx"> </span><del>- <p>To operate upon a single object, just use the <span class="codeline">remove</span> function:</p> </del><ins>+ <p>To operate upon a single object, just use the <code>remove</code> function:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # (this function coming soon) </span><span class="cx"> objectstore.remove(myobject) </span><span class="lines">@@ -497,7 +513,7 @@ </span><span class="cx"> <p>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.</p> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="options", description="Switching Lazy/Eager, No Load" &> </span><del>- <p>The <span class="codeline">options</span> 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 <span class="codeline">eagerload()</span>, <span class="codeline">lazyload()</span> and <span class="codeline">noload()</span>: </del><ins>+ <p>The <code>options</code> 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 <code>eagerload()</code>, <code>lazyload()</code> and <code>noload()</code>: </ins><span class="cx"> </p> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # user mapper with lazy addresses </span><span class="lines">@@ -527,7 +543,7 @@ </span><span class="cx"> </span><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="onetoone", description="One to One/Many to One" &> </span><del>-<p>The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the <span class="codeline">relation</span> function can usually figure out what you want:</p> </del><ins>+<p>The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the <code>relation</code> function can usually figure out what you want:</p> </ins><span class="cx"> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # a table to store a user's preferences for a site </span><span class="lines">@@ -604,7 +620,7 @@ </span><span class="cx"> </&> </span><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="manytomany", description="Many to Many" &> </span><del>-<p>The <span class="codeline">relation</span> function handles a basic many-to-many relationship when you specify the association table:</p> </del><ins>+<p>The <code>relation</code> function handles a basic many-to-many relationship when you specify the association table:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> articles = Table('articles', engine, </span><span class="cx"> Column('article_id', Integer, primary_key = True), </span></span></pre></div> <a id="sqlalchemytrunkdocbuildcontentdocument_basemyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/build/content/document_base.myt (1197 => 1198)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/build/content/document_base.myt 2006-03-25 15:25:29 UTC (rev 1197) +++ sqlalchemy/trunk/doc/build/content/document_base.myt 2006-03-25 17:23:41 UTC (rev 1198) </span><span class="lines">@@ -24,7 +24,7 @@ </span><span class="cx"> onepage='documentation' </span><span class="cx"> index='index' </span><span class="cx"> title='SQLAlchemy Documentation' </span><del>- version = '0.1.4' </del><ins>+ version = '0.1.5' </ins><span class="cx"> </%attr> </span><span class="cx"> </span><span class="cx"> <%method title> </span></span></pre></div> <a id="sqlalchemytrunkdocbuildcontentmetadatamyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/build/content/metadata.myt (1197 => 1198)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/build/content/metadata.myt 2006-03-25 15:25:29 UTC (rev 1197) +++ sqlalchemy/trunk/doc/build/content/metadata.myt 2006-03-25 17:23:41 UTC (rev 1198) </span><span class="lines">@@ -134,7 +134,7 @@ </span><span class="cx"> <&|doclib.myt:item, name="defaults", description="Column Defaults and OnUpdates" &> </span><span class="cx"> <p>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.</p> </span><span class="cx"> <&|doclib.myt:item, name="oninsert", description="Pre-Executed Insert Defaults" &> </span><del>- <p>A basic default is most easily specified by the "default" keyword argument to Column:</p> </del><ins>+ <p>A basic default is most easily specified by the "default" keyword argument to Column. This defines a value, function, or SQL expression that will be pre-executed to produce the new value, before the row is inserted:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # a function to create primary key ids </span><span class="cx"> i = 0 </span><span class="lines">@@ -169,7 +169,7 @@ </span><span class="cx"> </&> </span><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="onupdate", description="Pre-Executed OnUpdate Defaults" &> </span><del>- <p>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 constanct, plain Python function or SQL expression:</p> </del><ins>+ <p>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:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> t = Table("mytable", db, </span><span class="cx"> Column('id', Integer, primary_key=True), </span><span class="lines">@@ -178,7 +178,7 @@ </span><span class="cx"> Column('last_updated', DateTime, onupdate=func.current_timestamp()), </span><span class="cx"> ) </span><span class="cx"> </&> </span><del>- <p>To use a ColumnDefault explicitly for an on-update, use the "for_update" keyword argument:</p> </del><ins>+ <p>To use an explicit ColumnDefault object to specify an on-update, use the "for_update" keyword argument:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> Column('mycolumn', String(30), ColumnDefault(func.get_data(), for_update=True)) </span><span class="cx"> </&> </span><span class="lines">@@ -219,7 +219,7 @@ </span><span class="cx"> primary_key = table.engine.last_inserted_ids() </span><span class="cx"> row = table.select(table.c.id == primary_key[0]) </span><span class="cx"> </&> </span><del>- <p>Tables that are reflected from the database which have default values set on them, will receive those defaults as PassiveDefaults.</p> </del><ins>+ <p>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.</p> </ins><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="postgres", description="The Catch: Postgres Primary Key Defaults always Pre-Execute" &> </span><span class="cx"> <p>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.</p> </span><span class="lines">@@ -242,7 +242,7 @@ </span><span class="cx"> </&> </span><span class="cx"> </&> </span><span class="cx"> <&|doclib.myt:item, name="indexes", description="Defining Indexes" &> </span><del>- <p>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.</p> </del><ins>+ <p>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.</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> mytable = Table('mytable', engine, </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunkdocbuildcontentunitofworkmyt"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/build/content/unitofwork.myt (1197 => 1198)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/build/content/unitofwork.myt 2006-03-25 15:25:29 UTC (rev 1197) +++ sqlalchemy/trunk/doc/build/content/unitofwork.myt 2006-03-25 17:23:41 UTC (rev 1198) </span><span class="lines">@@ -9,20 +9,136 @@ </span><span class="cx"> <ul> </span><span class="cx"> <li>The ability to monitor scalar and list attributes on object instances, as well as object creates. This is handled via the attributes package.</li> </span><span class="cx"> <li>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 <b>topological sort</b>.</li> </span><del>- <li>The ability to "roll back" the attributes that have changed on an object instance since the last commit() operation. this is also handled by the attributes package.</li> </del><span class="cx"> <li>The ability to define custom functionality that occurs within the unit-of-work commit phase, such as "before insert", "after insert", etc. This is accomplished via MapperExtension.</li> </span><span class="cx"> <li>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.</li> </span><del>- <li>Thread-local operation. the Identity map as well as the Unit of work itself are normally instantiated and accessed in a manner that is local to the current thread. Another concurrently executing thread will therefore have its own Identity Map/Unit of Work, so unless an application explicitly shares objects between threads, the operation of the object relational mapping is automatically threadsafe. Unit of Work objects can also be constructed manually to allow any user-defined scoping.</li> </del><ins>+ <li>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.</li> </ins><span class="cx"> </ul></p> </span><span class="cx"> </&> </span><del>- <&|doclib.myt:item, name="getting", description="Accessing UnitOfWork Instances" &> - <p>The current unit of work is accessed via the Session interface. The Session is available in a thread-local context from the objectstore module as follows:</p> </del><ins>+ <&|doclib.myt:item, name="session", description="The Session Interface" &> + <p>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:</p> </ins><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # get the current thread's session </span><del>- s = objectstore.get_session() </del><ins>+ session = objectstore.get_session() </ins><span class="cx"> </&> </span><del>- <p>The Session object acts as a proxy to an underlying UnitOfWork object. Common methods include commit(), begin(), clear(), and delete(). Most of these methods are available at the module level in the objectstore module, which operate upon the Session returned by the get_session() function. </del><ins>+ <p>The Session object acts as a proxy to an underlying UnitOfWork object. Common methods include commit(), begin(), clear(), and delete(). Most of these methods are available at the module level in the objectstore module, which operate upon the Session returned by the get_session() function: </ins><span class="cx"> </p> </span><ins>+ <&|formatting.myt:code&> + # this... + objectstore.get_session().commit() + + # is the same as this: + objectstore.commit() + </&> + + <p>A description of the most important methods and concepts follows.</p> + + <&|doclib.myt:item, name="identitymap", description="Identity Map" &> + <p>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 <code>select</code> call to a mapper which returns results, all of those objects are now installed within the current Session, mapped to their identity.</p> + + <p>In particular, it is insuring that only <b>one</b> 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 <b>Identity Map</b>. When <code>select</code> or <code>get</code> 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 <b>instantiates</b> objects corresponding to the result set rows it receives, it will <b>check the current identity map first</b> before instantating a new object, and return <b>the same instance</b> already present in the identiy map if it already exists.</p> + + <p>Example:</p> + <&|formatting.myt:code&> + mymapper = mapper(MyClass, mytable) + + obj1 = mymapper.selectfirst(mytable.c.id==15) + obj2 = mymapper.selectfirst(mytable.c.id==15) + + >>> obj1 is obj2 + True + </&> + <p>The Identity Map is an instance of <code>weakref.WeakValueDictionary</code>, 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.</p> + <p>To view the Session's identity map, it is accessible via the <code>identity_map</code> accessor, and is an instance of <code>weakref.WeakValueDictionary</code>:</p> + <&|formatting.myt:code&><% """ + >>> objectstore.get_session().identity_map.values() + [<__main__.User object at 0x712630>, <__main__.Address object at 0x712a70>] + """ %> + </&> + + </&> + + <&|doclib.myt:item, name="changed", description="Whats Changed ?" &> + <p>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 <b>newly created</b> objects, records of all objects whose attributes have been modified, records of all objects that have been marked as deleted, and records of all list-based attributes where additions or deletions have occurred. These lists are used when a <code>commit()</code> call is issued to save all changes. After the commit occurs, these lists are all cleared out.</p> + + <p>These records are all tracked by a collection of <code>Set</code> objects (which are a SQLAlchemy-specific instance called a <code>HashSet</code>) that are also viewable off the Session:</p> + <&|formatting.myt:code&> + # new objects that were just constructed + session.new + + # objects that exist in the database, that were modified + session.dirty + + # objects that have been marked as deleted via session.delete(obj) + session.deleted + + # list-based attributes thave been appended + session.modified_lists + </&> + <p>Heres an interactive example, assuming the <code>User</code> and <code>Address</code> mapper setup first outlined in <&formatting.myt:link, path="datamapping_relations"&>:</p> + <&|formatting.myt:code&> + >>> session = objectstore.get_session() + + >>> u = User(user_name='Fred') + >>> u.addresses.append(Address(city='New York')) + >>> u.addresses.append(Address(city='Boston')) + + >>> session.new + [<__main__.User object at 0x713630>, <__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>] + + >>> # view the "modified lists" member, reveals our two Address objects as well + >>> session.modified_lists + [[<__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>]] + + >>> # lets view what the class/ID is for the list objects + >>> ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] + ['sqlalchemy.mapping.unitofwork.UOWListElement 7391872'] + + >>> # now commit + >>> session.commit() + + >>> # new list is blank + >>> session.new + [] + >>> # modified lists is blank + >>> session.modified_lists + [] + + >>> # now lets modify an object + >>> u.user_name='Ed' + + >>> # it gets placed in "dirty" + >>> 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 + >>> del u.addresses[0] + >>> session.deleted + [<__main__.Address object at 0x713a70>] + + >>> # commit + >>> session.commit() + + >>> # 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>] + </&> + </&> + + <&|doclib.myt:item, name="commit", description="Commit" &> + <p>This is the main gateway to what the Unit of Work does best, which is save everything ! + </p> + </&> + + <&|doclib.myt:item, name="delete", description="Delete" &> + </&> + + <&|doclib.myt:item, name="clear", description="Clear" &> </ins><span class="cx"> <p>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: </span><span class="cx"> </p> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="lines">@@ -34,6 +150,17 @@ </span><span class="cx"> </&> </span><span class="cx"> <p>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 before the clear operation should be discarded.</p> </span><span class="cx"> </&> </span><ins>+ + <&|doclib.myt:item, name="refreshexpire", description="Refresh / Expire" &> + </&> + + <&|doclib.myt:item, name="expunge", description="Expunge" &> + </&> + + <&|doclib.myt:item, name="import", description="Import Instance" &> + </&> + + </&> </ins><span class="cx"> <&|doclib.myt:item, name="begincommit", description="Begin/Commit" &> </span><span class="cx"> <p>The current thread's UnitOfWork object keeps track of objects that are modified. It maintains the following lists:</p> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="lines">@@ -170,7 +297,6 @@ </span><span class="cx"> <&|doclib.myt:item, name="advscope", description="Advanced UnitOfWork Management"&> </span><span class="cx"> </span><span class="cx"> <&|doclib.myt:item, name="object", description="Per-Object Sessions" &> </span><del>- <p><b>status</b> - 'using' function not yet released</p> </del><span class="cx"> <p>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 entire "global"/"threadlocal" UnitOfWork system and explicitly using a particular Session:</p> </span><span class="cx"> <&|formatting.myt:code&> </span><span class="cx"> # make a new Session with a global UnitOfWork </span></span></pre></div> <a id="sqlalchemytrunkdocdocscss"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/doc/docs.css (1197 => 1198)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/doc/docs.css 2006-03-25 15:25:29 UTC (rev 1197) +++ sqlalchemy/trunk/doc/docs.css 2006-03-25 17:23:41 UTC (rev 1198) </span><span class="lines">@@ -91,6 +91,11 @@ </span><span class="cx"> padding:5px; </span><span class="cx"> } </span><span class="cx"> </span><ins>+code { + font-family:courier, serif; + font-size:12px; +} + </ins><span class="cx"> .codeline { </span><span class="cx"> font-family:courier, serif; </span><span class="cx"> font-size:12px; </span></span></pre> </div> </div> </body> </html> |