[Sqlalchemy-commits] [1368] sqlalchemy/branches/schema/test: ok save-update is default cascade....
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-01 01:49:25
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1368] sqlalchemy/branches/schema/test: ok save-update is default cascade....</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1368</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 20:49:10 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>ok save-update is default cascade....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormsessionpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -34,6 +34,23 @@ </span><span class="cx"> {python} </span><span class="cx"> session = create_session() </span><span class="cx"> </span><ins>+A common option used with `create_session()` is to specify a specific `Engine` or `Connection` to be used for all operations performed by this Session: + + {python} + # create an engine + e = create_engine('postgres://some/url') + + # create a Session that will use this engine for all operations. + # it will open and close Connections as needed. + session = create_session(bind_to=e) + + # open a Connection + conn = e.connect() + + # create a Session that will use this specific Connection for all operations + session = create_session(bind_to=conn) + + </ins><span class="cx"> The session to which an object is attached can be acquired via the `object_session()` method, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -119,8 +136,16 @@ </span><span class="cx"> </span><span class="cx"> ### The Session API {@name=api} </span><span class="cx"> </span><del>-#### Flush {@name=flush} </del><ins>+#### query() {@name=query} </ins><span class="cx"> </span><ins>+#### get() {@name=get} + +#### load() {@name=load} + +#### save() {@name=save} + +#### flush() {@name=flush} + </ins><span class="cx"> This is the main gateway to what the Unit of Work does best, which is save everything ! It should be clear by now that a flush looks like: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -137,73 +162,70 @@ </span><span class="cx"> </span><span class="cx"> ##### Notes on Flush {@name=whatis} </span><span class="cx"> </span><del>-A common misconception about the `flush()` operation is that once performed, the newly persisted instances will automatically have related objects attached to them, based on the values of primary key identities that have been assigned to the instances before they were persisted. -The purpose of the flush operation is to instruct the Unit of Work to analyze its lists of modified objects, assemble them into a dependency graph, fire off the appopriate INSERT, UPDATE, and DELETE statements via the mappers related to those objects, and to synchronize column-based object attributes that correspond directly to updated/inserted database columns. </del><ins>+A common misconception about the `flush()` operation is that once performed, the newly persisted instances will automatically have related objects attached to them, based on the values of primary key identities that have been assigned to the instances before they were persisted. An example would be, you create a new `Address` object, set `address.user_id` to 5, and then `flush()` the session. The erroneous assumption would be that there is now a `User` object of identity "5" attached to the `Address` object, but in fact this is not the case. If you were to `refresh()` the `Address`, invalidating its current state and re-loading, *then* it would have the appropriate `User` object present. </ins><span class="cx"> </span><del>-The `session.flush()` operation also does not affect any `relation`-based object attributes, that is attributes that reference other objects or lists of other objects, in any way. A brief list of what will *not* happen includes: </del><ins>+This misunderstanding is related to the observed behavior of backreferences ([datamapping_relations_backreferences](rel:datamapping_relations_backreferences)), which automatically associates an instance "A" with another instance "B", in response to the manual association of instance "B" to instance "A" by the user. The backreference operation occurs completely externally to the `flush()` operation, and is pretty much the only example of a SQLAlchemy feature that manipulates the relationships of persistent objects. </ins><span class="cx"> </span><del>-* It will not append or delete any object instances to/from any list-based object attributes. Any objects that have been created or marked as deleted will be updated as such in the database, but if a newly deleted object instance is still attached to a parent object's list, the object itself will remain in that list. -* It will not set or remove any scalar references to other objects, even if the corresponding database identifier columns have been flushed. - -This means, if you set `address.user_id` to 5, that integer attribute will be saved, but it will not place an `Address` object in the `addresses` attribute of the corresponding `User` object. In some cases there may be a lazy-loader still attached to an object attribute which when first accesed performs a fresh load from the database and creates the appearance of this behavior, but this behavior should not be relied upon as it is specific to lazy loading and also may disappear in a future release. Similarly, if the `Address` object is marked as deleted and a flush is issued, the correct DELETE statements will be issued, but if the object instance itself is still attached to the `User`, it will remain. - -So the primary guideline for dealing with flush() is, *the developer is responsible for maintaining in-memory objects and their relationships to each other, the unit of work is responsible for maintaining the database representation of the in-memory objects.* The typical pattern is that the manipulation of objects *is* the way that changes get communicated to the unit of work, so that when the flush occurs, the objects are already in their correct in-memory representation and problems dont arise. The manipulation of identifier attributes like integer key values as well as deletes in particular are a frequent source of confusion. </del><ins>+The primary guideline for dealing with flush() is, the developer is responsible for maintaining in-memory objects and their relationships to each other, the unit of work is responsible for maintaining the database representation of the in-memory objects. The typical pattern is that the manipulation of objects *is* the way that changes get communicated to the unit of work, so that when the flush occurs, the objects are already in their correct in-memory representation and problems dont arise. The manipulation of identifier attributes like integer key values as well as deletes in particular are a frequent source of confusion. </ins><span class="cx"> </span><del>-A terrific feature of SQLAlchemy which is also a supreme source of confusion is the backreference feature, described in [datamapping_relations_backreferences](rel:datamapping_relations_backreferences). This feature allows two types of objects to maintain attributes that reference each other, typically one object maintaining a list of elements of the other side, which contains a scalar reference to the list-holding object. When you append an element to the list, the element gets a "backreference" back to the object which has the list. When you attach the list-holding element to the child element, the child element gets attached to the list. *This feature has nothing to do whatsoever with the Unit of Work.*`*` It is strictly a small convenience feature intended to support the developer's manual manipulation of in-memory objects, and the backreference operation happens at the moment objects are attached or removed to/from each other, independent of a! ny kind of database operation. It does not change the golden rule, that the developer is reponsible for maintaining in-memory object relationships. </del><ins>+#### close() {@name=close} </ins><span class="cx"> </span><del>-`*` there is an internal relationship between two `relations` that have a backreference, which state that a change operation is only logged once to the unit of work instead of two separate changes since the two changes are "equivalent", so a backreference does affect the information that is sent to the Unit of Work. But the Unit of Work itself has no knowledge of this arrangement and has no ability to affect it. </del><ins>+#### delete() {@name=delete} </ins><span class="cx"> </span><del>-#### Delete {@name=delete} </del><ins>+The delete call places an instance into the Unit of Work's list of objects to be marked as deleted: </ins><span class="cx"> </span><del>-The delete call places an object or objects into the Unit of Work's list of objects to be marked as deleted: - </del><span class="cx"> {python} </span><del>- # mark three objects to be deleted - objectstore.get_session().delete(obj1, obj2, obj3) </del><ins>+ # mark two objects to be deleted + session.delete(obj1) + session.delete(obj2) </ins><span class="cx"> </span><span class="cx"> # flush </span><del>- objectstore.get_session().flush() - -When objects which contain references to other objects are deleted, the mappers for those related objects will issue UPDATE statements for those objects that should no longer contain references to the deleted object, setting foreign key identifiers to NULL. Similarly, when a mapper contains relations with the `private=True` option, DELETE statements will be issued for objects within that relationship in addition to that of the primary deleted object; this is called a *cascading delete*. </del><ins>+ session.flush() </ins><span class="cx"> </span><del>-As stated before, the purpose of delete is strictly to issue DELETE statements to the database. It does not affect the in-memory structure of objects, other than changing the identifying attributes on objects, such as setting foreign key identifiers on updated rows to None. It has no effect on the status of references between object instances, nor any effect on the Python garbage-collection status of objects. </del><ins>+The delete operation will have an effect on instances that are attached to the deleted instance according to the `cascade` style of the relationship. By default, associated instances may need to be updated upon flush in order to reflect that they no longer are associated with the parent object, before the parent is deleted. If the relationship specifies `cascade="delete"`, then the associated instance will also be deleted upon flush, assuming it is still attached to the parent. If the relationship additionally includes the `delete-orphan` cascade style, the associated instance will be deleted if it is still attached to the parent, or is unattached to any other parent. </ins><span class="cx"> </span><del>-#### Clear {@name=clear} </del><ins>+The `delete()` operation has no relationship to the in-memory status of the instance, including usage of the `del` Python statement. An instance marked as deleted and flushed will still exist within memory until references to it are freed; similarly, removing an instance from memory via the `del` statement will have no effect, since the persistent instance will still be referenced by its Session. Obviously, if the instance is removed from the Session and then totally dereferenced, it will no longer exist in memory, but also won't exist in any Session and is therefore not deleted from the database. </ins><span class="cx"> </span><del>-To clear out the current thread's UnitOfWork, which has the effect of discarding the Identity Map and the lists of all objects that have been modified, just issue a clear: </del><ins>+#### clear() {@name=clear} + +This method detaches all instances from the Session, sending them to the detached or transient state as applicable, and replaces the underlying UnitOfWork with a new one. </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- # via module - objectstore.clear() - - # or via Session - objectstore.get_session().clear() </del><ins>+ session.clear() </ins><span class="cx"> </span><del>-This is the easiest way to "start fresh", as in a web application that wants to have a newly loaded graph of objects on each request. Any object instances created before the clear operation should either be discarded or at least not used with any Mapper or Unit Of Work operations (with the exception of `import_instance()`), as they no longer have any relationship to the current Unit of Work, and their behavior with regards to the current session is undefined. </del><ins>+The `clear()` method is particularly useful with a "default context" session such as a thread-local session, which can stay attached to the current thread to handle a new field of objects without having to re-attach a new Session. </ins><span class="cx"> </span><del>-#### Refresh / Expire {@name=refreshexpire} </del><ins>+#### refresh() / expire() {@name=refreshexpire} </ins><span class="cx"> </span><span class="cx"> To assist with the Unit of Work's "sticky" behavior, individual objects can have all of their attributes immediately re-loaded from the database, or marked as "expired" which will cause a re-load to occur upon the next access of any of the object's mapped attributes. This includes all relationships, so lazy-loaders will be re-initialized, eager relationships will be repopulated. Any changes marked on the object are discarded: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # immediately re-load attributes on obj1, obj2 </span><del>- session.refresh(obj1, obj2) </del><ins>+ session.refresh(obj1) + session.refresh(obj2) </ins><span class="cx"> </span><span class="cx"> # expire objects obj1, obj2, attributes will be reloaded </span><span class="cx"> # on the next access: </span><del>- session.expire(obj1, obj2, obj3) </del><ins>+ session.expire(obj1) + session.expire(obj2) </ins><span class="cx"> </span><del>-#### Expunge {@name=expunge} </del><ins>+#### expunge() {@name=expunge} </ins><span class="cx"> </span><del>-Expunge simply removes all record of an object from the current Session. This includes the identity map, and all history-tracking lists: </del><ins>+Expunge removes an object from the Session, sending persistent instances to the detached state, and pending instances to the transient state: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> session.expunge(obj1) </span><span class="cx"> </span><span class="cx"> Use `expunge` when youd like to remove an object altogether from memory, such as before calling `del` on it, which will prevent any "ghost" operations occuring when the session is flushed. </span><span class="cx"> </span><del>-#### Import Instance {@name=import} </del><ins>+#### bind_mapper()/bind_table() {@name=bind} </ins><span class="cx"> </span><ins>+#### update() {@name=update} + +#### save_or_update() {@name=save_or_update} + +#### merge() {@name=merge} + </ins><span class="cx"> The _instance_key attribute placed on object instances is designed to work with objects that are serialized into strings and brought back again. As it contains no references to internal structures or database connections, applications that use caches or session storage which require serialization (i.e. pickling) can store SQLAlchemy-loaded objects. However, as mentioned earlier, an object with a particular database identity is only allowed to exist uniquely within the current unit-of-work scope. So, upon deserializing such an object, it has to "check in" with the current Session. This is achieved via the `import_instance()` method: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -217,6 +239,7 @@ </span><span class="cx"> Note that the import_instance() function will either mark the deserialized object as the official copy in the current identity map, which includes updating its _instance_key with the current application's class instance, or it will discard it and return the corresponding object that was already present. Thats why its important to receive the return results from the method and use the result as the official object instance. </span><span class="cx"> </span><span class="cx"> </span><ins>+ </ins><span class="cx"> #### Analyzing Object Commits {@name=logging} </span><span class="cx"> </span><span class="cx"> The objectstore module can log an extensive display of its "commit plans", which is a graph of its internal representation of objects before they are committed to the database. To turn this logging on: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -146,7 +146,7 @@ </span><span class="cx"> if private: </span><span class="cx"> self.cascade = mapperutil.CascadeOptions("all, delete-orphan") </span><span class="cx"> else: </span><del>- self.cascade = mapperutil.CascadeOptions("all") </del><ins>+ self.cascade = mapperutil.CascadeOptions("save-update") </ins><span class="cx"> </span><span class="cx"> self.association = association </span><span class="cx"> self.order_by = order_by </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormsessionpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/session.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -276,7 +276,7 @@ </span><span class="cx"> This operation cascades the "save_or_update" method to associated instances if the relation is mapped </span><span class="cx"> with cascade="save-update".""" </span><span class="cx"> for c in object_mapper(object, entity_name=entity_name).cascade_iterator('save-update', object): </span><del>- if c is o: </del><ins>+ if c is object: </ins><span class="cx"> self._update_impl(c, entity_name=entity_name) </span><span class="cx"> else: </span><span class="cx"> self.save_or_update(c, entity_name=entity_name) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -65,7 +65,7 @@ </span><span class="cx"> sess = object_session(obj) </span><span class="cx"> if sess is not None: </span><span class="cx"> sess._register_dirty(obj) </span><del>- if self.cascade is not None: </del><ins>+ if newvalue is not None and self.cascade is not None: </ins><span class="cx"> if self.cascade.save_update: </span><span class="cx"> sess.save_or_update(newvalue) </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1367 => 1368)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 00:54:05 UTC (rev 1367) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-05-01 01:49:10 UTC (rev 1368) </span><span class="lines">@@ -443,6 +443,7 @@ </span><span class="cx"> self.assert_(u is not nu and u.user_id == nu.user_id and nu.user_name == 'savetester') </span><span class="cx"> </span><span class="cx"> # change first users name and save </span><ins>+ objectstore.get_session().update(u) </ins><span class="cx"> u.user_name = 'modifiedname' </span><span class="cx"> assert u in objectstore.get_session().dirty </span><span class="cx"> objectstore.get_session().flush() </span></span></pre> </div> </div> </body> </html> |