From: Mike Bayer Date: Sat, 25 Mar 2006 17:23:41 +0000 (+0000) Subject: doc devel X-Git-Tag: rel_0_1_5~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=47841349d95660b4b21522ef3b97ad41f4a36d33;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git doc devel --- diff --git a/doc/build/content/datamapping.myt b/doc/build/content/datamapping.myt index d341d56d5c..ef8f6ed01c 100644 --- a/doc/build/content/datamapping.myt +++ b/doc/build/content/datamapping.myt @@ -6,9 +6,9 @@ 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.

-

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 Unit of Work). The __init__() method of the object is also decorated to communicate changes when new instances of the object are created.

+

When a Mapper is created to associate a Table object with a class, all of the columns defined in the Table object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes. These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "commits" the current transactional context (known as a Unit of Work). The __init__() method of the object is also decorated to communicate changes when new instances of the object are created.

-

The Mapper also provides the interface by which instances of the object are loaded from the database. The primary method for this is its select() method, which has similar arguments to a sqlalchemy.sql.Select object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects.

+

The Mapper also provides the interface by which instances of the object are loaded from the database. The primary method for this is its select() method, which has similar arguments to a sqlalchemy.sql.Select object. But this select method executes automatically and returns results, instead of awaiting an execute() call. Instead of returning a cursor-like object, it returns an array of objects.

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.

@@ -65,15 +65,26 @@ UPDATE users SET user_name=:user_name userlist = User.mapper.select_by(user_id=12) -

There is also a full-blown "monkeypatch" function that creates a primary mapper, attaches the above mapper class property, and also the methods get, get_by, select, select_by, selectone, selectfirst, commit and delete:

+

There is also a full-blown "monkeypatch" function that creates a primary mapper, attaches the above mapper class property, and also the methods get, get_by, select, select_by, selectone, selectfirst, commit and delete:

<&|formatting.myt:code&> + # "assign" a mapper to the User class/users table assign_mapper(User, users) + + # methods are attached to the class for selecting userlist = User.select_by(user_id=12) + + myuser = User.get(1) + + # mark an object as deleted for the next commit + myuser.delete() + + # commit the changes on a specific object + myotheruser.commit()

Other methods of associating mappers and finder methods with their corresponding classes, such as via common base classes or mixins, can be devised as well. SQLAlchemy does not aim to dictate application architecture and will always allow the broadest variety of architectural patterns, but may include more helper objects and suggested architectures in the future.

<&|doclib.myt:item, name="overriding", description="Overriding Properties"&> -

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:

+

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:

<&|formatting.myt:code&> class MyClass(object): def _set_email(self, email): @@ -125,6 +136,10 @@ UPDATE users SET user_name=:user_name # using a WHERE criterion to get a scalar u = mapper.selectfirst(users.c.user_name=='john') + + # 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') # using a full select object result = mapper.select(users.select(users.c.user_name=='john')) @@ -135,7 +150,7 @@ UPDATE users SET user_name=:user_name # or using a "text" object result = mapper.select(text("select * from users where user_name='fred'", engine=engine)) -

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 c to the class itself, which can be used just like the table metadata to access the columns of the table:

+

Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause. These columns are also accessible off of the mapped class directly. When a mapper is assigned to a class, it also attaches a special property accessor c to the class itself, which can be used just like the table metadata to access the columns of the table:

<&|formatting.myt:code&> User.mapper = mapper(User, users) @@ -143,9 +158,9 @@ UPDATE users SET user_name=:user_name <&|doclib.myt:item, name="saving", description="Saving Objects" &> -

When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called sqlalchemy.mapping.objectstore. The changes are then written to the database when an application calls objectstore.commit(). This pattern is known as a Unit of Work, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The commit() operation uses a transaction as well, and will also perform "concurrency checking" to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). Transactional resources are used effectively in all cases; the unit of work handles all the details.

- -

When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its __init__() method is also decorated to mark new objects as "new".

+

When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called sqlalchemy.mapping.objectstore. The changes are then written to the database when an application calls objectstore.commit(). This pattern is known as a Unit of Work, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The commit() operation uses a transaction as well, and will also perform "concurrency checking" to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). Transactional resources are used effectively in all cases; the unit of work handles all the details.

+

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.

+

When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its __init__() method is also decorated to mark new objects as "new".

<&|formatting.myt:code&> User.mapper = mapper(User, users) @@ -209,13 +224,14 @@ INSERT INTO users (user_name, password) VALUES (:user_name, :password)

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.

+ <&|doclib.myt:item, name="relations", description="Defining and Using Relationships" &> -

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 relation 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:

+

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 relation 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:

<&|formatting.myt:code&> from sqlalchemy import * - engine = create_engine('sqlite', {'filename':'mydb'}) + engine = create_engine('sqlite://filename=mydb') # define user table users = Table('users', engine, @@ -250,7 +266,7 @@ INSERT INTO users (user_name, password) VALUES (:user_name, :password) self.state = state self.zip = zip -

And then a Mapper that will define a relationship of the User and the Address classes to each other as well as their table metadata. We will add an additional mapper keyword argument properties which is a dictionary relating the name of an object property to a database relationship, in this case a relation object against a newly defined mapper for the Address class:

+

And then a Mapper that will define a relationship of the User and the Address classes to each other as well as their table metadata. We will add an additional mapper keyword argument properties which is a dictionary relating the name of an object property to a database relationship, in this case a relation object against a newly defined mapper for the Address class:

<&|formatting.myt:code&> User.mapper = mapper(User, users, properties = { 'addresses' : relation(mapper(Address, addresses)) @@ -277,7 +293,7 @@ INSERT INTO addresses (user_id, street, city, state, zip) VALUES (:user_id, :str

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.

-

Also notice that when we created a Mapper on the User class which defined an 'addresses' relation, the newly created User instance magically had an "addresses" attribute which behaved like a list. This list is in reality a property accessor function, which returns an instance of sqlalchemy.util.HistoryArraySet, which fulfills the full set of Python list accessors, but maintains a unique set of objects (based on their in-memory identity), and also tracks additions and deletions to the list:

+

Also notice that when we created a Mapper on the User class which defined an 'addresses' relation, the newly created User instance magically had an "addresses" attribute which behaved like a list. This list is in reality a property accessor function, which returns an instance of sqlalchemy.util.HistoryArraySet, which fulfills the full set of Python list accessors, but maintains a unique set of objects (based on their in-memory identity), and also tracks additions and deletions to the list:

<&|formatting.myt:code&> del u.addresses[1] u.addresses.append(Address('27 New Place', 'Houston', 'TX', '34839')) @@ -295,7 +311,7 @@ VALUES (:user_id, :street, :city, :state, :zip) <&|doclib.myt:item, name="private", description="Useful Feature: Private Relations" &> -

So our one address that was removed from the list, was updated to have a user_id of None, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the private=True parameter of relation: +

So our one address that was removed from the list, was updated to have a user_id of None, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the private=True parameter of relation: <&|formatting.myt:code&> User.mapper = mapper(User, users, properties = { @@ -315,10 +331,10 @@ DELETE FROM addresses WHERE addresses.address_id = :address_id -

In this case, with the private flag set, the element that was removed from the addresses list was also removed from the database. By specifying the private flag on a relation, it is indicated to the Mapper that these related objects exist only as children of the parent object, otherwise should be deleted.

+

In this case, with the private flag set, the element that was removed from the addresses list was also removed from the database. By specifying the private flag on a relation, it is indicated to the Mapper that these related objects exist only as children of the parent object, otherwise should be deleted.

<&|doclib.myt:item, name="backreferences", description="Useful Feature: Backreferences" &> -

By creating relations with the backref keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, even without any database queries being executed. Below, the User mapper is created with an "addresses" property, and the corresponding Address mapper receives a "backreference" to the User object via the property name "user": +

By creating relations with the backref keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, even without any database queries being executed. Below, the User mapper is created with an "addresses" property, and the corresponding Address mapper receives a "backreference" to the User object via the property name "user": <&|formatting.myt:code&> Address.mapper = mapper(Address, addresses) User.mapper = mapper(User, users, properties = { @@ -343,7 +359,7 @@ DELETE FROM addresses WHERE addresses.address_id = :address_id True -

The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property is placed on the child mapper. The default arguments to this property can be overridden using the backref() function: +

The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property is placed on the child mapper. The default arguments to this property can be overridden using the backref() function: <&|formatting.myt:code&> Address.mapper = mapper(Address, addresses) @@ -355,7 +371,7 @@ DELETE FROM addresses WHERE addresses.address_id = :address_id <&|doclib.myt:item, name="cascade", description="Creating Relationships Automatically with cascade_mappers" &> -

The mapper package has a helper function cascade_mappers() which can simplify the task of linking several mappers together. Given a list of classes and/or mappers, it identifies the foreign key relationships between the given mappers or corresponding class mappers, and creates relation() objects representing those relationships, including a backreference. Attempts to find +

The mapper package has a helper function cascade_mappers() which can simplify the task of linking several mappers together. Given a list of classes and/or mappers, it identifies the foreign key relationships between the given mappers or corresponding class mappers, and creates relation() objects representing those relationships, including a backreference. Attempts to find the "secondary" table in a many-to-many relationship as well. The names of the relations are a lowercase version of the related class. In the case of one-to-many or many-to-many, the name is "pluralized", which currently is based on the English language (i.e. an 's' or @@ -380,7 +396,7 @@ the name is "pluralized", which currently is based on the English language (i.e. <&|doclib.myt:item, name="lazyload", description="Selecting from Relationships: Lazy Load" &> -

We've seen how the relation specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a Lazy Loader when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.

+

We've seen how the relation specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a Lazy Loader when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.

<&|formatting.myt:code&> # define a mapper @@ -411,7 +427,7 @@ WHERE addresses.user_id = :users_user_id ORDER BY addresses.oid <&|doclib.myt:item, name="relselectby", description="Useful Feature: Creating Joins via select_by" &> -

In mappers that have relationships, the select_by method and its cousins include special functionality that can be used to create joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: +

In mappers that have relationships, the select_by method and its cousins include special functionality that can be used to create joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: <&|formatting.myt:code&> <&formatting.myt:poplink&>l = User.mapper.select_by(street='123 Green Street') <&|formatting.myt:codepopper, link="sql" &>SELECT users.user_id AS users_user_id, @@ -441,7 +457,7 @@ ORDER BY users.oid

This operation will clear out all currently mapped object instances, and subsequent select statements will load fresh copies from the databse.

-

To operate upon a single object, just use the remove function:

+

To operate upon a single object, just use the remove function:

<&|formatting.myt:code&> # (this function coming soon) objectstore.remove(myobject) @@ -497,7 +513,7 @@ ORDER BY users.oid, addresses.oid

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.

<&|doclib.myt:item, name="options", description="Switching Lazy/Eager, No Load" &> -

The options method of mapper provides an easy way to get alternate forms of a mapper from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions eagerload(), lazyload() and noload(): +

The options method of mapper provides an easy way to get alternate forms of a mapper from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions eagerload(), lazyload() and noload():

<&|formatting.myt:code&> # user mapper with lazy addresses @@ -527,7 +543,7 @@ ORDER BY users.oid, addresses.oid <&|doclib.myt:item, name="onetoone", description="One to One/Many to One" &> -

The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the relation function can usually figure out what you want:

+

The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the relation function can usually figure out what you want:

<&|formatting.myt:code&> # a table to store a user's preferences for a site @@ -604,7 +620,7 @@ VALUES (:address_id, :user_id, :email_address) <&|doclib.myt:item, name="manytomany", description="Many to Many" &> -

The relation function handles a basic many-to-many relationship when you specify the association table:

+

The relation function handles a basic many-to-many relationship when you specify the association table:

<&|formatting.myt:code&> articles = Table('articles', engine, Column('article_id', Integer, primary_key = True), diff --git a/doc/build/content/document_base.myt b/doc/build/content/document_base.myt index a1ccc6c610..86ee9a8992 100644 --- a/doc/build/content/document_base.myt +++ b/doc/build/content/document_base.myt @@ -24,7 +24,7 @@ onepage='documentation' index='index' title='SQLAlchemy Documentation' - version = '0.1.4' + version = '0.1.5' <%method title> diff --git a/doc/build/content/metadata.myt b/doc/build/content/metadata.myt index 5e9a431f84..16bb06676d 100644 --- a/doc/build/content/metadata.myt +++ b/doc/build/content/metadata.myt @@ -134,7 +134,7 @@ DROP TABLE employees <&|doclib.myt:item, name="defaults", description="Column Defaults and OnUpdates" &>

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.

<&|doclib.myt:item, name="oninsert", description="Pre-Executed Insert Defaults" &> -

A basic default is most easily specified by the "default" keyword argument to Column:

+

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:

<&|formatting.myt:code&> # a function to create primary key ids i = 0 @@ -169,7 +169,7 @@ DROP TABLE employees <&|doclib.myt:item, name="onupdate", description="Pre-Executed OnUpdate Defaults" &> -

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:

+

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:

<&|formatting.myt:code&> t = Table("mytable", db, Column('id', Integer, primary_key=True), @@ -178,7 +178,7 @@ DROP TABLE employees Column('last_updated', DateTime, onupdate=func.current_timestamp()), ) -

To use a ColumnDefault explicitly for an on-update, use the "for_update" keyword argument:

+

To use an explicit ColumnDefault object to specify an on-update, use the "for_update" keyword argument:

<&|formatting.myt:code&> Column('mycolumn', String(30), ColumnDefault(func.get_data(), for_update=True)) @@ -219,7 +219,7 @@ DROP TABLE employees primary_key = table.engine.last_inserted_ids() row = table.select(table.c.id == primary_key[0]) -

Tables that are reflected from the database which have default values set on them, will receive those defaults as PassiveDefaults.

+

When Tables are reflected from the database using autoload=True, any DEFAULT values set on the columns will be reflected in the Table object as PassiveDefault instances.

<&|doclib.myt:item, name="postgres", description="The Catch: Postgres Primary Key Defaults always Pre-Execute" &>

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 still pre-executed since SQLAlchemy would otherwise have no way of retrieving the row just inserted.

@@ -242,7 +242,7 @@ DROP TABLE employees <&|doclib.myt:item, name="indexes", description="Defining Indexes" &> -

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.

+

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.

<&|formatting.myt:code&> mytable = Table('mytable', engine, diff --git a/doc/build/content/unitofwork.myt b/doc/build/content/unitofwork.myt index 3833e9a228..86c6cdc54e 100644 --- a/doc/build/content/unitofwork.myt +++ b/doc/build/content/unitofwork.myt @@ -9,20 +9,136 @@

- <&|doclib.myt:item, name="getting", description="Accessing UnitOfWork Instances" &> -

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:

+ <&|doclib.myt:item, name="session", description="The Session Interface" &> +

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:

<&|formatting.myt:code&> # get the current thread's session - s = objectstore.get_session() + session = objectstore.get_session() -

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. +

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:

+ <&|formatting.myt:code&> + # this... + objectstore.get_session().commit() + + # is the same as this: + objectstore.commit() + + +

A description of the most important methods and concepts follows.

+ + <&|doclib.myt:item, name="identitymap", description="Identity Map" &> +

The first concept to understand about the Unit of Work is that it is keeping track of all mapped objects which have been loaded from the database, as well as all mapped objects which have been saved to the database in the current session. This means that everytime you issue a select call to a mapper which returns results, all of those objects are now installed within the current Session, mapped to their identity.

+ +

In particular, it is insuring that only one instance of a particular object, corresponding to a particular database identity, exists within the Session at one time. By "database identity" we mean a table or relational concept in the database combined with a particular primary key in that table. The session accomplishes this task using a dictionary known as an Identity Map. When select or get calls on mappers issue queries to the database, they will in nearly all cases go out to the database on each call to fetch results. However, when the mapper instantiates objects corresponding to the result set rows it receives, it will check the current identity map first before instantating a new object, and return the same instance already present in the identiy map if it already exists.

+ +

Example:

+ <&|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 + +

The Identity Map is an instance of weakref.WeakValueDictionary, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. The current SA attributes implementation places some circular refs upon objects, although this may change in the future. There are other ways to remove object instances from the current session, as well as to clear the current session entirely, which are described later in this section.

+

To view the Session's identity map, it is accessible via the identity_map accessor, and is an instance of weakref.WeakValueDictionary:

+ <&|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 ?" &> +

The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores records of all newly created objects, records of all objects whose attributes have been modified, records of all objects that have been marked as deleted, and records of all list-based attributes where additions or deletions have occurred. These lists are used when a commit() call is issued to save all changes. After the commit occurs, these lists are all cleared out.

+ +

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:

+ <&|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 + +

Heres an interactive example, assuming the User and Address mapper setup first outlined in <&formatting.myt:link, path="datamapping_relations"&>:

+ <&|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" &> +

This is the main gateway to what the Unit of Work does best, which is save everything ! +

+ + + <&|doclib.myt:item, name="delete", description="Delete" &> + + + <&|doclib.myt:item, name="clear", description="Clear" &>

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:

<&|formatting.myt:code&> @@ -33,6 +149,17 @@ objectstore.get_session().clear()

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.

+ + + <&|doclib.myt:item, name="refreshexpire", description="Refresh / Expire" &> + + + <&|doclib.myt:item, name="expunge", description="Expunge" &> + + + <&|doclib.myt:item, name="import", description="Import Instance" &> + + <&|doclib.myt:item, name="begincommit", description="Begin/Commit" &>

The current thread's UnitOfWork object keeps track of objects that are modified. It maintains the following lists:

@@ -170,7 +297,6 @@ <&|doclib.myt:item, name="advscope", description="Advanced UnitOfWork Management"&> <&|doclib.myt:item, name="object", description="Per-Object Sessions" &> -

status - 'using' function not yet released

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:

<&|formatting.myt:code&> # make a new Session with a global UnitOfWork diff --git a/doc/docs.css b/doc/docs.css index ccf5a488ab..a8d73d21e5 100644 --- a/doc/docs.css +++ b/doc/docs.css @@ -91,6 +91,11 @@ padding:5px; } +code { + font-family:courier, serif; + font-size:12px; +} + .codeline { font-family:courier, serif; font-size:12px;