From: Mike Bayer Date: Sat, 25 Mar 2006 18:13:01 +0000 (+0000) Subject: doc dev X-Git-Tag: rel_0_1_5~22 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ea288d73ed82c82ebb11d5c989d84458c12ed49e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git doc dev --- diff --git a/doc/build/content/unitofwork.myt b/doc/build/content/unitofwork.myt index 86c6cdc54e..daeb8ec9dc 100644 --- a/doc/build/content/unitofwork.myt +++ b/doc/build/content/unitofwork.myt @@ -58,7 +58,7 @@ <&|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.

+

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

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&> @@ -76,63 +76,95 @@

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() + ">>>" # get the current thread's session + ">>>" session = objectstore.get_session() - >>> u = User(user_name='Fred') - >>> u.addresses.append(Address(city='New York')) - >>> u.addresses.append(Address(city='Boston')) + ">>>" # create a new object, with a list-based attribute + ">>>" # containing two more new objects + ">>>" u = User(user_name='Fred') + ">>>" u.addresses.append(Address(city='New York')) + ">>>" u.addresses.append(Address(city='Boston')) - >>> session.new - [<__main__.User object at 0x713630>, <__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>] + ">>>" # objects are in the "new" list + ">>>" 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 + ">>>" # view the "modified lists" member, + ">>>" # reveals our two Address objects as well, inside of a list + ">>>" session.modified_lists [[<__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>]] - >>> # lets view what the class/ID is for the list objects - >>> ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] + ">>>" # lets view what the class/ID is for the list object + ">>>" ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] ['sqlalchemy.mapping.unitofwork.UOWListElement 7391872'] - >>> # now commit - >>> session.commit() + ">>>" # now commit + ">>>" session.commit() - >>> # new list is blank - >>> session.new + ">>>" # the "new" list is now empty + ">>>" session.new [] - >>> # modified lists is blank - >>> session.modified_lists + + ">>>" # the "modified lists" list is now empty + ">>>" session.modified_lists [] - >>> # now lets modify an object - >>> u.user_name='Ed' + ">>>" # now lets modify an object + ">>>" u.user_name='Ed' - >>> # it gets placed in "dirty" - >>> session.dirty + ">>>" # it gets placed in the "dirty" list + ">>>" session.dirty [<__main__.User object at 0x713630>] - >>> # delete one of the addresses - >>> session.delete(u.addresses[0]) - >>> # and also delete it off the User object, note that this is not automatic - >>> del u.addresses[0] - >>> session.deleted + ">>>" # delete one of the addresses + ">>>" session.delete(u.addresses[0]) + + ">>>" # and also delete it off the User object, note that + ">>>" # this is *not automatic* when using session.delete() + ">>>" del u.addresses[0] + ">>>" session.deleted [<__main__.Address object at 0x713a70>] - >>> # commit - >>> session.commit() + ">>>" # commit + ">>>" session.commit() - >>> # all lists are cleared out - >>> session.new, session.dirty, session.modified_lists, session.deleted + ">>>" # 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() + ">>>" # identity map has the User and the one remaining Address + ">>>" session.identity_map.values() [<__main__.Address object at 0x713b30>, <__main__.User object at 0x713630>] +

Unlike the identity map, the new, dirty, modified_lists, and deleted lists are not weak referencing. This means if you abandon all references to new or modified objects within a session, they are still present and will be saved on the next commit operation, unless they are removed from the Session explicitly (more on that later). The new list may change in a future release to be weak-referencing, however for the deleted list, one can see that its quite natural for a an object marked as deleted to have no references in the application, yet a DELETE operation is still required.

<&|doclib.myt:item, name="commit", description="Commit" &> -

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

This is the main gateway to what the Unit of Work does best, which is save everything ! It should be clear by now that a commit looks like:

+ <&|formatting.myt:code&> + objectstore.get_session().commit() + +

It also can be called with a list of objects; in this form, the commit operation will be limited only to the objects specified in the list, as well as any child objects within private relationships for a delete operation:

+ <&|formatting.myt:code&> + # saves only user1 and address2. all other modified + # objects remain present in the session. + objectstore.get_session().commit(user1, address2) + +

This second form of commit should be used more carefully as it will not necessarily locate other dependent objects within the session, whose database representation may have foreign constraint relationships with the objects being operated upon.

+ + <&|doclib.myt:item, name="whatis", description="What Commit is, and Isn't" &> +

The purpose of the Commit operation is to instruct the Unit of Work to analyze its lists of modified objects, assemble them into a dependency graph, and fire off the appopriate INSERT, UPDATE, and DELETE statements via the mappers related to those objects. And thats it. This means, it is not going to change anything about your objects as they exist in memory, with the exception of populating scalar object attributes with newly generated default column values which normally only involves primary and foreign key identifiers. A brief list of what will not happen includes:

+ +

So the primary guideline for dealing with commit() is, the developer is responsible for maintaining the objects in memory, the unit of work is responsible for maintaining the database representation.

+ +

A terrific feature of SQLAlchemy which is also a supreme source of confusion is the backreference feature, described in <&formatting.myt:link, path="datamapping_relations_backreferences"&>. This feature allows two types of objects to maintain attributes that reference each other, typically one object maintaining a list of elements of the other side. When you append an element to the list, the element gets a "backreference" back to the object which has the list. When you attach the list-holding element to the child element, the child element gets attached to the list. This feature has nothing to do whatsoever with the Unit of Work. It is strictly a small convenience feature added to support an extremely common pattern. Besides this one little feature, the developer must maintain in-memory object relationships manually. Note that we are talking about the manipulation of objects, not the initial loading of them which is handled by the mapper.

+ <&|doclib.myt:item, name="delete", description="Delete" &> diff --git a/doc/build/lib/highlight.py b/doc/build/lib/highlight.py index df711965ca..1a838408b7 100644 --- a/doc/build/lib/highlight.py +++ b/doc/build/lib/highlight.py @@ -176,7 +176,13 @@ class PythonHighlighter(Highlighter): curstyle = self.get_style(t[0], t[1]) (start, end) = self._line_grid(line, t[2], t[3]) - tokens.append(line[start[1]:end[1]]) + text = line[start[1]:end[1]] + + # special hardcoded rule to allow "interactive" demos without + # >>> getting sucked in as >> , > operators + if text == '">>>"': + text = '>>>' + tokens.append(text) curc = t[3][1] curl = t[3][0] diff --git a/doc/docs.css b/doc/docs.css index a8d73d21e5..3db51b893b 100644 --- a/doc/docs.css +++ b/doc/docs.css @@ -96,6 +96,9 @@ code { font-size:12px; } +pre { + overflow:auto; +} .codeline { font-family:courier, serif; font-size:12px;