[Sqlalchemy-commits] [1371] sqlalchemy/branches/schema/doc/build/content: doc
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-01 23:56:49
|
<!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>[1371] sqlalchemy/branches/schema/doc/build/content: doc</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1371</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-01 18:56:37 -0500 (Mon, 01 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormutilpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.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 (1370 => 1371)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 23:07:48 UTC (rev 1370) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 23:56:37 UTC (rev 1371) </span><span class="lines">@@ -138,12 +138,30 @@ </span><span class="cx"> </span><span class="cx"> #### query() {@name=query} </span><span class="cx"> </span><ins>+The `query()` function takes a class or `Mapper` as an argument, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. If a Mapper is passed, then the Query uses that mapper. Otherwise, if a class is sent, it will locate the primary mapper for that class which is used to construct the Query. + +"entity_name" is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the "entity_name" argument, so that a given class can be properly matched to the desired primary mapper. + +All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating Session. + </ins><span class="cx"> #### get() {@name=get} </span><span class="cx"> </span><ins>+Given a class or mapper, a scalar or tuple-based identity, and an optional "entity_name" keyword argument, creates a `Query` corresponding to the given mapper or class/entity_name combination, and calls the `get()` method with the given identity value. If the object already exists within this Session, it is simply returned, else it is queried from the database. If the instance is not found, the method returns `None`. + </ins><span class="cx"> #### load() {@name=load} </span><span class="cx"> </span><ins>+load() is similar to get() except it will raise an exception if the instance does not exist in the database. It will also load the object's data from the database in all cases, and **overwrite** all changes on the object if it already exists in the session with the latest data from the database. + </ins><span class="cx"> #### save() {@name=save} </span><span class="cx"> </span><ins>+save() is called with a single transient (unsaved, unattached) instance as an argument, which is then added to the Session and becomes pending. When the session is next `flush()`ed, the instance will be saved to the database uponwhich it becomes persistent (saved, attached). If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised. + +save() is called automatically for new instances by the classes' associated mapper, if a default Session context is in effect (such as a thread-local session), which means that newly created instances automatically become pending. If there is no default session available, then the instance remains transient (unattached) until it is explicitly added to a Session via the save() method. + +A transient instance also can be automatically `save()`ed if it is associated with a parent object which specifies `save-update` within its *cascade* rules, and that parent is already attached or becomes attached to a Session. For more information on cascade, see the next section. + +The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). + </ins><span class="cx"> #### flush() {@name=flush} </span><span class="cx"> </span><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="lines">@@ -170,6 +188,8 @@ </span><span class="cx"> </span><span class="cx"> #### close() {@name=close} </span><span class="cx"> </span><ins>+This method first calls `clear()`, removing all objects from this Session, and then insures that any transactional resources are closed. + </ins><span class="cx"> #### delete() {@name=delete} </span><span class="cx"> </span><span class="cx"> The delete call places an instance into the Unit of Work's list of objects to be marked as deleted: </span><span class="lines">@@ -182,7 +202,7 @@ </span><span class="cx"> # flush </span><span class="cx"> session.flush() </span><span class="cx"> </span><del>-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. </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; cascade rules are described further in the following section. By default, associated instances may need to be updated in the database 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><span class="cx"> 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. </span><span class="cx"> </span><span class="lines">@@ -220,26 +240,85 @@ </span><span class="cx"> </span><span class="cx"> #### bind_mapper()/bind_table() {@name=bind} </span><span class="cx"> </span><ins>+Both of these methods receive two arguments; in the case of `bind_mapper()`, it is a `Mapper` and an `Engine` or `Connection` instance; in the case of `bind_table()`, it is a `Table` instance or other `Selectable` (such as an `Alias`, `Select`, etc.), and an `Engine` or `Connection` instance. + +Normally, when a Session is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retreieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raiased. A second form of `create_session()` takes the argument `bind_to=engine_or_connection`, where all operations performed by this Session are done via the single Engine or Connection passed to the constructor. + +The point of these methods is to bind individual mapper and/or table operations to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the constructor. Configurations which interact with multiple explicit database connections at one time must use either or both of these methods in order to associate Session operations with the appropriate connection resource. + +Binding a `Mapper` to a resource takes precedence over a `Table` bind, meaning if mapper A is associated with table B, and the Session binds mapper A to connection X and table B to connection Y, an opertaion with mapper A will use connection X, not connection Y. + </ins><span class="cx"> #### update() {@name=update} </span><span class="cx"> </span><ins>+The update() method is used *only* with detached instances. A detached instance only exists if its `Session` was cleared or closed, or the instance was `expunge()`d from its session. `update()` will re-attach the detached instance with this Session, bringing it back to the persistent state. If the instance is already attached to an existing Session, an exception is raised. + +The `save_or_update()` method is a convenience method which will call the `save()` or `update()` methods appropriately dependening on whether or not the instance has a database identity (but the instance still must be unattached). + </ins><span class="cx"> #### save_or_update() {@name=save_or_update} </span><span class="cx"> </span><ins>+This method is a combination of the `save()` and `update()` methods, which will examine the given instance for a database identity (i.e. if it is transient or detached), and will call the implementation of `save()` or `update()` as appropriate. Use `save_or_update()` to add unattached instances to a session when you're not sure if they were newly created or not. + </ins><span class="cx"> #### merge() {@name=merge} </span><span class="cx"> </span><del>-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: </del><ins>+`merge()` is used to return the persistent version of an instance that is not attached to this Session. When passed an instance, if an instance with its database identity already exists within this Session, it is returned. If the instance does not exist in this Session, it is loaded from the database and then returned. </ins><span class="cx"> </span><ins>+A future version of `merge()` will also update the Session's instance with the state of the given instance (hence the name "merge"). + +This method is useful for bringing in objects which may have been restored from a serialization, such as those stored in an HTTP session: + </ins><span class="cx"> {python} </span><span class="cx"> # deserialize an object </span><span class="cx"> myobj = pickle.loads(mystring) </span><span class="cx"> </span><del>- # "import" it. if the objectstore already had this object in the </del><ins>+ # "merge" it. if the session already had this object in the </ins><span class="cx"> # identity map, then you get back the one from the current session. </span><del>- myobj = session.import_instance(myobj) </del><ins>+ myobj = session.merge(myobj) + +Note that `merge()` *does not* associate the given instance with the Session; it remains detached (or attached to whatever Session it was already attached to). </ins><span class="cx"> </span><del>-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. </del><ins>+### Cascade rules {@name=cascade} </ins><span class="cx"> </span><ins>+Mappers support the concept of configurable *cascade* behavior on `relation()`s. This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session. Cascade is indicated as a comma-separated list of string keywords, with the possible values `all`, `delete`, `save-update`, `refresh-expire`, `merge`, `expunge`, and `delete-orphan`. </ins><span class="cx"> </span><ins>+Cascading is configured by setting the `cascade` keyword argument on a `relation()`: </ins><span class="cx"> </span><ins>+ {python} + mapper(Order, order_table, properties={ + 'items' : relation(Item, items_table, cascade="all, delete-orphan"), + 'customer' : relation(User, users_table, user_orders_table, cascade="save-update"), + }) + +The above mapper specifies two relations, `items` and `customer`. The `items` relationship specifies "all, delete-orphan" as its `cascade` value, indicating that all `save`, `update`, `merge`, `expunge`, `refresh` `delete` and `expire` operations performed on a parent `Order` instance should also be performed on the child `Item` instances attached to it (`save` and `update` are cascaded using the `save_or_update()` method, so that the database identity of the instance doesn't matter). The `delete-orphan` cascade value additionally indicates that if an `Item` instance is no longer associated with an `Order`, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an `Order` and an `Item` object. + +The `customer` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent `Order` instance to a child `User` instance, except for if the `Order` is attached with a particular session, either via the `save()`, `update()`, or `save-update()` method. + +Additionally, when a child item is attached to a parent item that specifies the "save-update" cascade value on the relationship, the child is automatically passed to `save_or_update()` (and the operation is further cascaded to the child item). + +Note that cascading doesn't do anything that isn't possible by manually calling Session methods on individual instances within a hierarchy, it merely automates common operations on a group of associated instances. + +### SessionTransaction {@name=transaction} + +SessionTransaction is a multi-engine transaction manager, which aggregates one or more Engine/Connection pairs and keeps track of a Transaction object for each one. As the Session receives requests to execute SQL statements, it uses the Connection that is referenced by the SessionTransaction. At commit time, the underyling Session is flushed, and each Transaction is the committed. + +Example usage is as follows: + + {python} + sess = create_session() + trans = sess.create_transaction() + try: + item1 = sess.query(Item).get(1) + item2 = sess.query(Item).get(2) + item1.foo = 'bar' + item2.bar = 'foo' + trans.commit() + except: + trans.rollback() + raise + +The `create_transaction()` method creates a new SessionTransaction object but does not declare any connection/transaction resources. At the point of the first `get()` call, a connection resource is opened off the engine that corresponds to the Item classes' mapper and is stored within the SessionTransaction with an open Transaction. When `trans.commit()` is called, the `flush()` method is called on the `Session` and the corresponding update statements are issued to the database within the scope of the transaction already opened; afterwards, the underying Transaction is committed, and connection resources are freed. + +`SessionTransaction`, like the `Transaction` off of `Connection` also supports "nested" behavior, and is safe to pass to other functions which then issue their own `begin()`/`commit()` pair; only the outermost `begin()`/`commit()` pair actually affects the transaction, and any call to `rollback()` within a particular call stack will issue a rollback. + </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="sqlalchemybranchesschemalibsqlalchemyormutilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py (1370 => 1371)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py 2006-05-01 23:07:48 UTC (rev 1370) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/util.py 2006-05-01 23:56:37 UTC (rev 1371) </span><span class="lines">@@ -15,6 +15,7 @@ </span><span class="cx"> self.save_update = "save-update" in values or "all" in values </span><span class="cx"> self.merge = "merge" in values or "all" in values </span><span class="cx"> self.expunge = "expunge" in values or "all" in values </span><ins>+ self.refresh_expire = "refresh-expire" in values or "all" in values </ins><span class="cx"> def __contains__(self, item): </span><span class="cx"> return getattr(self, item.replace("-", "_"), False) </span><span class="cx"> </span><span class="cx">\ No newline at end of file </span></span></pre> </div> </div> </body> </html> |