[Sqlalchemy-commits] [1311] sqlalchemy/branches/schema/lib/sqlalchemy: doing some cascade stuff
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-21 21:22:31
|
<!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>[1311] sqlalchemy/branches/schema/lib/sqlalchemy: doing some cascade stuff</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1311</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-21 16:22:18 -0500 (Fri, 21 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>doing some cascade stuff</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemaexamplesadjacencytreebyroot_treepy">sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemy__init__py">sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyattributespy">sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginedefaultpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingmapperpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemaexamplesadjacencytreebyroot_treepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -5,16 +5,15 @@ </span><span class="cx"> """a more advanced example of basic_tree.py. illustrates MapperExtension objects which </span><span class="cx"> add application-specific functionality to a Mapper object.""" </span><span class="cx"> </span><del>-engine = create_engine('sqlite://', echo = True) -#engine = sqlalchemy.engine.create_engine('mysql', {'db':'test', 'host':'127.0.0.1', 'user':'scott'}, echo=True) -#engine = sqlalchemy.engine.create_engine('postgres', {'database':'test', 'host':'127.0.0.1', 'user':'scott', 'password':'tiger'}, echo=True) -#engine = sqlalchemy.engine.create_engine('oracle', {'dsn':os.environ['DSN'], 'user':os.environ['USER'], 'password':os.environ['PASSWORD']}, echo=True) </del><ins>+engine = create_engine('sqlite:///:memory:', echo = True) </ins><span class="cx"> </span><ins>+metadata = BoundMetaData(engine) + </ins><span class="cx"> """create the treenodes table. This is ia basic adjacency list model table. </span><span class="cx"> One additional column, "root_node_id", references a "root node" row and is used </span><span class="cx"> in the 'byroot_tree' example.""" </span><span class="cx"> </span><del>-trees = Table('treenodes', engine, </del><ins>+trees = Table('treenodes', metadata, </ins><span class="cx"> Column('node_id', Integer, Sequence('treenode_id_seq',optional=False), primary_key=True), </span><span class="cx"> Column('parent_node_id', Integer, ForeignKey('treenodes.node_id'), nullable=True), </span><span class="cx"> Column('root_node_id', Integer, ForeignKey('treenodes.node_id'), nullable=True), </span><span class="lines">@@ -23,7 +22,7 @@ </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> treedata = Table( </span><del>- "treedata", engine, </del><ins>+ "treedata", metadata, </ins><span class="cx"> Column('data_id', Integer, primary_key=True), </span><span class="cx"> Column('value', String(100), nullable=False) </span><span class="cx"> ) </span><span class="lines">@@ -94,13 +93,13 @@ </span><span class="cx"> """creates an instance of a TreeNode. since the TreeNode constructor requires </span><span class="cx"> the 'name' argument, this method pulls the data from the database row directly.""" </span><span class="cx"> return TreeNode(row[mapper.c.name], _mapper_nohistory=True) </span><del>- def after_insert(self, mapper, instance): </del><ins>+ def after_insert(self, mapper, connection, instance): </ins><span class="cx"> """runs after the insert of a new TreeNode row. The primary key of the row is not determined </span><span class="cx"> until the insert is complete, since most DB's use autoincrementing columns. If this node is </span><span class="cx"> the root node, we will take the new primary key and update it as the value of the node's </span><span class="cx"> "root ID" as well, since its root node is itself.""" </span><span class="cx"> if instance.root is instance: </span><del>- mapper.primarytable.update(TreeNode.c.id==instance.id, values=dict(root_node_id=instance.id)).execute() </del><ins>+ connection.execute(mapper.primarytable.update(TreeNode.c.id==instance.id, values=dict(root_node_id=instance.id))) </ins><span class="cx"> instance.root_id = instance.id </span><span class="cx"> def append_result(self, mapper, row, imap, result, instance, isnew, populate_existing=False): </span><span class="cx"> """runs as results from a SELECT statement are processed, and newly created or already-existing </span><span class="lines">@@ -132,18 +131,18 @@ </span><span class="cx"> trees.create() </span><span class="cx"> </span><span class="cx"> </span><del>-assign_mapper(TreeNode, trees, properties=dict( </del><ins>+mapper(TreeNode, trees, properties=dict( </ins><span class="cx"> id=trees.c.node_id, </span><span class="cx"> name=trees.c.node_name, </span><span class="cx"> parent_id=trees.c.parent_node_id, </span><span class="cx"> root_id=trees.c.root_node_id, </span><span class="cx"> root=relation(TreeNode, primaryjoin=trees.c.root_node_id==trees.c.node_id, foreignkey=trees.c.node_id, lazy=None, uselist=False), </span><del>- children=relation(TreeNode, primaryjoin=trees.c.parent_node_id==trees.c.node_id, lazy=None, uselist=True, private=True), - data=relation(mapper(TreeData, treedata, properties=dict(id=treedata.c.data_id)), private=True, lazy=False) </del><ins>+ children=relation(TreeNode, primaryjoin=trees.c.parent_node_id==trees.c.node_id, lazy=None, uselist=True, cascade="delete,save-update"), + data=relation(mapper(TreeData, treedata, properties=dict(id=treedata.c.data_id)), cascade="delete,save-update", lazy=False) </ins><span class="cx"> </span><span class="cx"> ), extension = TreeLoader()) </span><del>-TreeNode.mapper </del><span class="cx"> </span><ins>+session = create_session() </ins><span class="cx"> </span><span class="cx"> node2 = TreeNode('node2') </span><span class="cx"> node2.append('subnode1') </span><span class="lines">@@ -162,10 +161,11 @@ </span><span class="cx"> print node.print_nodes() </span><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span><del>-print "Committing:" </del><ins>+print "flushing:" </ins><span class="cx"> print "----------------------------" </span><span class="cx"> </span><del>-objectstore.commit() </del><ins>+session.save(node) +session.flush() </ins><span class="cx"> #sys.exit() </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="cx"> print "Tree After Save:" </span><span class="lines">@@ -190,7 +190,7 @@ </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="cx"> print "Committing:" </span><span class="cx"> print "----------------------------" </span><del>-objectstore.commit() </del><ins>+session.flush() </ins><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="cx"> print "Tree After Save:" </span><span class="lines">@@ -205,8 +205,8 @@ </span><span class="cx"> print "tree new where root_id=%d:" % nodeid </span><span class="cx"> print "----------------------------" </span><span class="cx"> </span><del>-objectstore.clear() -t = TreeNode.mapper.select(TreeNode.c.root_id==nodeid, order_by=[TreeNode.c.id])[0] </del><ins>+session.clear() +t = session.query(TreeNode).select(TreeNode.c.root_id==nodeid, order_by=[TreeNode.c.id])[0] </ins><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="cx"> print "Full Tree:" </span><span class="lines">@@ -217,8 +217,8 @@ </span><span class="cx"> print "Marking root node as deleted" </span><span class="cx"> print "and committing:" </span><span class="cx"> print "----------------------------" </span><del>-objectstore.delete(t) -objectstore.commit() </del><ins>+session.delete(t) +session.flush() </ins><span class="cx"> </span><span class="cx"> </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemy__init__py"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/__init__.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -17,8 +17,8 @@ </span><span class="cx"> from sqlalchemy.mapping.objectstore import Session, get_session </span><span class="cx"> </span><span class="cx"> create_engine = sqlalchemy.engine.create_engine </span><ins>+create_session = objectstore.Session </ins><span class="cx"> </span><del>- </del><span class="cx"> def global_connect(*args, **kwargs): </span><span class="cx"> sqlalchemy.schema.default_metadata.connect(*args, **kwargs) </span><span class="cx"> </span><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -119,6 +119,7 @@ </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.set(self.obj, value, orig) </span><ins>+ self.value_changed(orig, value) </ins><span class="cx"> def delattr(self, **kwargs): </span><span class="cx"> orig = self.obj.__dict__.get(self.key, None) </span><span class="cx"> if self.orig is ScalarAttribute.NONE: </span><span class="lines">@@ -126,6 +127,7 @@ </span><span class="cx"> self.obj.__dict__[self.key] = None </span><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.set(self.obj, None, orig) </span><ins>+ self.value_changed(orig, None) </ins><span class="cx"> def append(self, obj): </span><span class="cx"> self.setattr(obj) </span><span class="cx"> def remove(self, obj): </span><span class="lines">@@ -136,9 +138,11 @@ </span><span class="cx"> self.orig = ScalarAttribute.NONE </span><span class="cx"> def commit(self): </span><span class="cx"> self.orig = ScalarAttribute.NONE </span><ins>+ def value_changed(self, oldvalue, newvalue): + pass </ins><span class="cx"> def added_items(self): </span><span class="cx"> if self.orig is not ScalarAttribute.NONE: </span><del>- return [self.obj.__dict__[self.key]] </del><ins>+ return [self.obj.__dict__.get(self.key)] </ins><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> def deleted_items(self): </span><span class="lines">@@ -148,7 +152,7 @@ </span><span class="cx"> return [] </span><span class="cx"> def unchanged_items(self): </span><span class="cx"> if self.orig is ScalarAttribute.NONE: </span><del>- return [self.obj.__dict__[self.key]] </del><ins>+ return [self.obj.__dict__.get(self.key)] </ins><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> </span><span class="lines">@@ -177,7 +181,7 @@ </span><span class="cx"> obj.__dict__[key] = [] </span><span class="cx"> </span><span class="cx"> util.HistoryArraySet.__init__(self, list_, readonly=kwargs.get('readonly', False)) </span><del>- def list_value_changed(self, obj, key, item, listval, isdelete): </del><ins>+ def value_changed(self, obj, key, item, listval, isdelete): </ins><span class="cx"> pass </span><span class="cx"> def setattr(self, value, **kwargs): </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="lines">@@ -187,21 +191,18 @@ </span><span class="cx"> def _setrecord(self, item): </span><span class="cx"> res = util.HistoryArraySet._setrecord(self, item) </span><span class="cx"> if res: </span><del>- self.list_value_changed(self.obj, self.key, item, self, False) </del><ins>+ self.value_changed(self.obj, self.key, item, self, False) </ins><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.append(self.obj, item) </span><span class="cx"> return res </span><span class="cx"> def _delrecord(self, item): </span><span class="cx"> res = util.HistoryArraySet._delrecord(self, item) </span><span class="cx"> if res: </span><del>- self.list_value_changed(self.obj, self.key, item, self, True) </del><ins>+ self.value_changed(self.obj, self.key, item, self, True) </ins><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.delete(self.obj, item) </span><span class="cx"> return res </span><span class="cx"> </span><del>-# deprecated -class ListElement(ListAttribute):pass - </del><span class="cx"> class TriggeredAttribute(ManagedAttribute): </span><span class="cx"> """Used by AttributeManager to allow the attaching of a callable item, representing the future value </span><span class="cx"> of a particular attribute on a particular object instance, as the current attribute on an object. </span><span class="lines">@@ -221,7 +222,7 @@ </span><span class="cx"> </span><span class="cx"> def plain_init(self, attrhist): </span><span class="cx"> if not self.uselist: </span><del>- p = ScalarAttribute(self.obj, self.key, **self.kwargs) </del><ins>+ p = self.manager.create_scalar(self.obj, self.key, **self.kwargs) </ins><span class="cx"> self.obj.__dict__[self.key] = None </span><span class="cx"> else: </span><span class="cx"> p = self.manager.create_list(self.obj, self.key, None, **self.kwargs) </span><span class="lines">@@ -247,7 +248,7 @@ </span><span class="cx"> raise AssertionError("AttributeError caught in callable prop:" + str(e.args)) </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> </span><del>- p = ScalarAttribute(self.obj, self.key, **self.kwargs) </del><ins>+ p = self.manager.create_scalar(self.obj, self.key, **self.kwargs) </ins><span class="cx"> else: </span><span class="cx"> if not self.obj.__dict__.has_key(self.key) or len(self.obj.__dict__[self.key]) == 0: </span><span class="cx"> if passive: </span><span class="lines">@@ -320,7 +321,8 @@ </span><span class="cx"> """creates a scalar property object, defaulting to SmartProperty, which </span><span class="cx"> will communicate change events back to this AttributeManager.""" </span><span class="cx"> return SmartProperty(self, key, uselist, callable_, **kwargs) </span><del>- </del><ins>+ def create_scalar(self, obj, key, **kwargs): + return ScalarAttribute(obj, key, **kwargs) </ins><span class="cx"> def create_list(self, obj, key, list_, **kwargs): </span><span class="cx"> """creates a history-aware list property, defaulting to a ListAttribute which </span><span class="cx"> is a subclass of HistoryArrayList.""" </span><span class="lines">@@ -473,7 +475,7 @@ </span><span class="cx"> if callable_ is not None: </span><span class="cx"> prop = self.create_callable(obj, key, callable_, uselist=uselist, **kwargs) </span><span class="cx"> elif not uselist: </span><del>- prop = ScalarAttribute(obj, key, **kwargs) </del><ins>+ prop = self.create_scalar(obj, key, **kwargs) </ins><span class="cx"> else: </span><span class="cx"> prop = self.create_list(obj, key, None, **kwargs) </span><span class="cx"> if attrdict is None: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginedefaultpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/default.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -24,7 +24,6 @@ </span><span class="cx"> dbapi = dialect.dbapi() </span><span class="cx"> if dbapi is None: </span><span class="cx"> raise exceptions.InvalidRequestException("Cant get DBAPI module for dialect '%s'" % dialect) </span><del>- </del><span class="cx"> self._pool = poolclass(lambda: dbapi.connect(*cargs, **cparams), **kwargs) </span><span class="cx"> else: </span><span class="cx"> if isinstance(pool, sqlalchemy.pool.DBProxy): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -689,10 +689,16 @@ </span><span class="cx"> prop.register_dependencies(uowcommit, *args, **kwargs) </span><span class="cx"> if self.inherits is not None: </span><span class="cx"> uowcommit.register_dependency(self.inherits, self) </span><del>- - def register_deleted(self, obj, uow): </del><ins>+ + def cascade_iterator(self, type, object): + yield object </ins><span class="cx"> for prop in self.props.values(): </span><del>- prop.register_deleted(obj, uow) </del><ins>+ for c in prop.cascade_iterator(type, object): + yield c + +# def register_deleted(self, obj, uow): +# for prop in self.props.values(): +# prop.register_deleted(obj, uow) </ins><span class="cx"> </span><span class="cx"> </span><span class="cx"> def _identity_key(self, row): </span><span class="lines">@@ -781,6 +787,8 @@ </span><span class="cx"> """called when the mapper receives a row. instance is the parent instance </span><span class="cx"> corresponding to the row. """ </span><span class="cx"> raise NotImplementedError() </span><ins>+ def cascade_iterator(self, type, object): + return [] </ins><span class="cx"> def copy(self): </span><span class="cx"> raise NotImplementedError() </span><span class="cx"> def get_criterion(self, key, value): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -222,23 +222,71 @@ </span><span class="cx"> for o in obj: </span><span class="cx"> self.uow.expunge(o) </span><span class="cx"> </span><del>- def add(self, *obj, **kwargs): </del><ins>+ def save(self, *obj, **kwargs): </ins><span class="cx"> """adds unsaved objects to this Session. </span><span class="cx"> </span><span class="cx"> The 'entity_name' keyword argument can also be given which will be assigned </span><span class="cx"> to the instances if given. </span><span class="cx"> """ </span><span class="cx"> for o in obj: </span><del>- if hasattr(o, '_instance_key'): - if not self.uow.has_key(o._instance_key): - raise InvalidRequestError("Instance '%s' is not bound to this Session; use session.import(instance)" % repr(o)) - else: - entity_name = kwargs.get('entity_name', None) - if entity_name is not None: - m = class_mapper(o.__class__, entity_name=entity_name) - m._assign_entity_name(o) - self._register_new(o) - </del><ins>+ for c in object_mapper(o, **kwargs).cascade_iterator('save-update', o): + if c is o: + self._save_impl(c, **kwargs) + else: + self.save_or_update(c, **kwargs) + + def update(self, *obj, **kwargs): + for o in obj: + for c in object_mapper(o, **kwargs).cascade_iterator('save-update', o): + if c is o: + self._update_impl(c, **kwargs) + else: + self.save_or_update(c, **kwargs) + + def save_or_update(self, *obj, **kwargs): + for o in obj: + for c in object_mapper(o, *kwargs).cascade_iterator('save-update', o): + key = self._instance_key(c, raiseerror=False, **kwargs) + if key is None: + self._save_impl(c, **kwargs) + else: + self._update_impl(c, **kwargs) + + def _save_impl(self, object, **kwargs): + if hasattr(object, '_instance_key'): + if not self.uow.has_key(object._instance_key): + raise InvalidRequestError("Instance '%s' attached to a different Session" % repr(object)) + else: + entity_name = kwargs.get('entity_name', None) + if entity_name is not None: + m = class_mapper(object.__class__, entity_name=entity_name) + m._assign_entity_name(object) + self._register_new(object) + + def _update_impl(self, object, **kwargs): + if self._is_bound(object): + return + if not hasattr(object, '_instance_key'): + raise InvalidRequestError("Instance '%s' is not persisted" % repr(object)) + self._register_dirty(object) + + def _instance_key(self, object, entity_name=None, raiseerror=True): + key = getattr(object, '_instance_key', None) + if key is None: + mapper = object_mapper(object, raiseerror=False) + if mapper is None: + mapper = class_mapper(object, entity_name=entity_name) + ident = mapper.identity(object) + for k in ident: + if k is None: + if raiseerror: + raise InvalidRequestError("Instance '%s' does not have a full set of identity values, and does not represent a saved entity in the database. Use the add() method to add unsaved instances to this Session." % str(object)) + else: + return None + key = mapper.identity_key(*ident) + return key + + </ins><span class="cx"> def _register_new(self, obj): </span><span class="cx"> self._bind_to(obj) </span><span class="cx"> self.uow.register_new(obj) </span><span class="lines">@@ -285,11 +333,13 @@ </span><span class="cx"> </span><span class="cx"> the keyword argument 'entity_name' can also be provided which will be used by the import.""" </span><span class="cx"> for o in obj: </span><del>- if not self._is_bound(o): - o = self.import_(o, **kwargs) - self.uow.register_deleted(o) - - def import_(self, instance, entity_name=None): </del><ins>+ for c in object_mapper(o, **kwargs).cascade_iterator('delete', o): + if not self._is_bound(c): + c = self.import_(c, **kwargs) + self.uow.register_deleted(c) + + + def merge(self, instance, entity_name=None): </ins><span class="cx"> """given an instance that represents a saved item, adds it to this session. </span><span class="cx"> the return value is either the given instance, or if an instance corresponding to the </span><span class="cx"> identity of the given instance already exists within this session, then that instance is returned; </span><span class="lines">@@ -307,16 +357,7 @@ </span><span class="cx"> """ </span><span class="cx"> if instance is None: </span><span class="cx"> return None </span><del>- key = getattr(instance, '_instance_key', None) - mapper = object_mapper(instance, raiseerror=False) - if mapper is None: - mapper = class_mapper(instance, entity_name=entity_name) - if key is None: - ident = mapper.identity(instance) - for k in ident: - if k is None: - raise InvalidRequestError("Instance '%s' does not have a full set of identity values, and does not represent a saved entity in the database. Use the add() method to add unsaved instances to this Session." % str(instance)) - key = mapper.identity_key(*ident) </del><ins>+ key = self._instance_key(instance, entity_name=entity_name, raiseerror=True) </ins><span class="cx"> u = self.uow </span><span class="cx"> if u.identity_map.has_key(key): </span><span class="cx"> return u.identity_map[key] </span><span class="lines">@@ -328,7 +369,7 @@ </span><span class="cx"> </span><span class="cx"> def import_instance(self, *args, **kwargs): </span><span class="cx"> """deprecated; a synynom for import()""" </span><del>- return self.import_(*args, **kwargs) </del><ins>+ return self.merge(*args, **kwargs) </ins><span class="cx"> </span><span class="cx"> def get_id_key(ident, class_, entity_name=None): </span><span class="cx"> return Session.get_id_key(ident, class_, entity_name) </span><span class="lines">@@ -339,8 +380,8 @@ </span><span class="cx"> def mapper(*args, **params): </span><span class="cx"> return sqlalchemy.mapping.mapper(*args, **params) </span><span class="cx"> </span><del>-def object_mapper(obj): - return sqlalchemy.mapping.object_mapper(obj) </del><ins>+def object_mapper(obj, **kwargs): + return sqlalchemy.mapping.object_mapper(obj, **kwargs) </ins><span class="cx"> </span><span class="cx"> def class_mapper(class_, **kwargs): </span><span class="cx"> return sqlalchemy.mapping.class_mapper(class_, **kwargs) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -18,6 +18,7 @@ </span><span class="cx"> import mapper </span><span class="cx"> import objectstore </span><span class="cx"> from sqlalchemy.exceptions import * </span><ins>+import sets </ins><span class="cx"> </span><span class="cx"> class ColumnProperty(MapperProperty): </span><span class="cx"> """describes an object attribute that corresponds to a table column.""" </span><span class="lines">@@ -93,7 +94,7 @@ </span><span class="cx"> if prop is self: </span><span class="cx"> continue </span><span class="cx"> instance.__dict__[prop.key] = row[prop.columns[0]] </span><del>- objectstore.global_attributes.create_history(instance, prop.key, uselist=False) </del><ins>+ objectstore.global_attributes.create_history(instance, prop.key, uselist=False, cascade=self.cascade) </ins><span class="cx"> return row[self.columns[0]] </span><span class="cx"> else: </span><span class="cx"> return connection.scalar(sql.select([self.columns[0]], clause, use_labels=True),None) </span><span class="lines">@@ -105,7 +106,7 @@ </span><span class="cx"> def execute(self, session, instance, row, identitykey, imap, isnew): </span><span class="cx"> if isnew: </span><span class="cx"> if not self.is_primary(): </span><del>- objectstore.global_attributes.create_history(instance, self.key, False, callable_=self.setup_loader(instance)) </del><ins>+ objectstore.global_attributes.create_history(instance, self.key, False, callable_=self.setup_loader(instance), cascade=self.cascade) </ins><span class="cx"> else: </span><span class="cx"> objectstore.global_attributes.reset_history(instance, self.key) </span><span class="cx"> </span><span class="lines">@@ -118,7 +119,7 @@ </span><span class="cx"> </span><span class="cx"> """describes an object property that holds a single item or list of items that correspond </span><span class="cx"> to a related database table.""" </span><del>- def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, use_alias=None, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False): </del><ins>+ def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, use_alias=None, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None): </ins><span class="cx"> self.uselist = uselist </span><span class="cx"> self.argument = argument </span><span class="cx"> self.secondary = secondary </span><span class="lines">@@ -136,8 +137,17 @@ </span><span class="cx"> self.foreigntable = foreignkey.table </span><span class="cx"> else: </span><span class="cx"> self.foreigntable = None </span><del>- - self.private = private </del><ins>+ + # hibernate cascades: + # create, merge, save-update, delete, lock, refresh, evict, replicate. + if cascade is not None: + self.cascade = sets.Set([c.strip() for c in cascade.split(',')]) + else: + if private: + self.cascade = sets.Set(["delete-orphan", "delete"]) + else: + self.cascade = sets.Set() + </ins><span class="cx"> self.association = association </span><span class="cx"> if selectalias is not None: </span><span class="cx"> print "'selectalias' argument to relation() is deprecated. eager loads automatically alias-ize tables now." </span><span class="lines">@@ -151,6 +161,23 @@ </span><span class="cx"> self.backref = backref </span><span class="cx"> self.is_backref = is_backref </span><span class="cx"> </span><ins>+ private = property(lambda s:"delete-orphan" in s.cascade) + + def cascade_iterator(self, type, object): + + if not type in self.cascade: + return + + if self.uselist: + childlist = objectstore.global_attributes.get_history(object, self.key, passive = False) + else: + childlist = objectstore.global_attributes.get_history(object, self.key) + for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items(): + if c is not None: + yield c + for c2 in self.mapper.cascade_iterator(type, c): + yield c2 + </ins><span class="cx"> def copy(self): </span><span class="cx"> x = self.__class__.__new__(self.__class__) </span><span class="cx"> x.__dict__.update(self.__dict__) </span><span class="lines">@@ -226,7 +253,7 @@ </span><span class="cx"> </span><span class="cx"> def _set_class_attribute(self, class_, key): </span><span class="cx"> """sets attribute behavior on our target class.""" </span><del>- objectstore.global_attributes.register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, extension=self.attributeext) </del><ins>+ objectstore.global_attributes.register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, extension=self.attributeext, cascade=self.cascade) </ins><span class="cx"> </span><span class="cx"> def _get_direction(self): </span><span class="cx"> """determines our 'direction', i.e. do we represent one to many, many to many, etc.""" </span><span class="lines">@@ -302,18 +329,7 @@ </span><span class="cx"> return c.copy_container() </span><span class="cx"> return None </span><span class="cx"> </span><del>- def register_deleted(self, obj, uow): - if not self.private: - return </del><span class="cx"> </span><del>- if self.uselist: - childlist = uow.attributes.get_history(obj, self.key, passive = False) - else: - childlist = uow.attributes.get_history(obj, self.key) - for child in childlist.deleted_items() + childlist.unchanged_items(): - if child is not None: - uow.register_deleted(child) - </del><span class="cx"> class MapperStub(object): </span><span class="cx"> """poses as a Mapper representing the association table in a many-to-many </span><span class="cx"> join, when performing a commit(). </span><span class="lines">@@ -546,7 +562,7 @@ </span><span class="cx"> if self.is_primary(): </span><span class="cx"> return </span><span class="cx"> #print "PLAIN PROPLOADER EXEC NON-PRIAMRY", repr(id(self)), repr(self.mapper.class_), self.key </span><del>- objectstore.global_attributes.create_history(instance, self.key, self.uselist) </del><ins>+ objectstore.global_attributes.create_history(instance, self.key, self.uselist, cascade=self.cascade) </ins><span class="cx"> </span><span class="cx"> def _compile_synchronizers(self): </span><span class="cx"> """assembles a list of 'synchronization rules', which are instructions on how to populate </span><span class="lines">@@ -596,7 +612,7 @@ </span><span class="cx"> def _set_class_attribute(self, class_, key): </span><span class="cx"> # establish a class-level lazy loader on our class </span><span class="cx"> #print "SETCLASSATTR LAZY", repr(class_), key </span><del>- objectstore.global_attributes.register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, callable_=lambda i: self.setup_loader(i), extension=self.attributeext) </del><ins>+ objectstore.global_attributes.register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, callable_=lambda i: self.setup_loader(i), extension=self.attributeext, cascade=self.cascade) </ins><span class="cx"> </span><span class="cx"> def setup_loader(self, instance): </span><span class="cx"> if not self.parent.is_assigned(instance): </span><span class="lines">@@ -644,7 +660,7 @@ </span><span class="cx"> #print "EXEC NON-PRIAMRY", repr(self.mapper.class_), self.key </span><span class="cx"> # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader, </span><span class="cx"> # which will override the class-level behavior </span><del>- objectstore.global_attributes.create_history(instance, self.key, self.uselist, callable_=self.setup_loader(instance)) </del><ins>+ objectstore.global_attributes.create_history(instance, self.key, self.uselist, callable_=self.setup_loader(instance), cascade=self.cascade) </ins><span class="cx"> else: </span><span class="cx"> #print "EXEC PRIMARY", repr(self.mapper.class_), self.key </span><span class="cx"> # we are the primary manager for this attribute on this class - reset its per-instance attribute state, </span><span class="lines">@@ -795,7 +811,7 @@ </span><span class="cx"> if isnew: </span><span class="cx"> # new row loaded from the database. initialize a blank container on the instance. </span><span class="cx"> # this will override any per-class lazyloading type of stuff. </span><del>- h = objectstore.global_attributes.create_history(instance, self.key, self.uselist) </del><ins>+ h = objectstore.global_attributes.create_history(instance, self.key, self.uselist, cascade=self.cascade) </ins><span class="cx"> </span><span class="cx"> if not self.uselist: </span><span class="cx"> if isnew: </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py (1310 => 1311)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-21 16:38:29 UTC (rev 1310) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-21 21:22:18 UTC (rev 1311) </span><span class="lines">@@ -34,48 +34,62 @@ </span><span class="cx"> super(UOWProperty, self).__init__(*args, **kwargs) </span><span class="cx"> self.class_ = class_ </span><span class="cx"> property = property(lambda s:class_mapper(s.class_).props[s.key], doc="returns the MapperProperty object associated with this property") </span><del>- -class UOWListElement(attributes.ListElement): </del><ins>+ + +class UOWListElement(attributes.ListAttribute): </ins><span class="cx"> """overrides ListElement to provide unit-of-work "dirty" hooks when list attributes are modified, </span><span class="cx"> plus specialzed append() method.""" </span><del>- def __init__(self, obj, key, data=None, deleteremoved=False, **kwargs): - attributes.ListElement.__init__(self, obj, key, data=data, **kwargs) </del><ins>+ def __init__(self, obj, key, data=None, deleteremoved=False, cascade=None, **kwargs): + attributes.ListAttribute.__init__(self, obj, key, data=data, **kwargs) </ins><span class="cx"> self.deleteremoved = deleteremoved </span><del>- def list_value_changed(self, obj, key, item, listval, isdelete): - sess = get_session(obj, raiseerror=True) </del><ins>+ self.cascade = cascade + def value_changed(self, obj, key, item, listval, isdelete): + sess = get_session(obj, raiseerror=False) </ins><span class="cx"> if sess is None: </span><span class="cx"> return </span><span class="cx"> # add the child item to this session. if its already got an identity then its </span><span class="cx"> # expected to be there already. </span><del>- sess.add(item) - if not isdelete and sess.deleted.contains(item): </del><ins>+ #sess.add(item) + #if not isdelete and sess.deleted.contains(item): </ins><span class="cx"> #raise InvalidRequestError("re-inserting a deleted value into a list") </span><del>- del sess.deleted[item] </del><ins>+ # del sess.deleted[item] </ins><span class="cx"> sess.modified_lists.append(self) </span><del>- if self.deleteremoved and isdelete: - sess._register_deleted(item) </del><ins>+ if self.cascade is not None: + if isdelete: + if "delete-orphan" in self.cascade: + sess.delete(item) + else: + if "save-update" in self.cascade: + sess.save_or_update(item) + #if self.deleteremoved and isdelete: + # sess._register_deleted(item) </ins><span class="cx"> def append(self, item, _mapper_nohistory = False): </span><span class="cx"> if _mapper_nohistory: </span><span class="cx"> self.append_nohistory(item) </span><span class="cx"> else: </span><del>- attributes.ListElement.append(self, item) - -class UOWAttributeManager(attributes.AttributeManager): - """overrides AttributeManager to provide unit-of-work "dirty" hooks when scalar attribues are modified, plus factory methods for UOWProperrty/UOWListElement.""" - def __init__(self): - attributes.AttributeManager.__init__(self) - - def value_changed(self, obj, key, value): </del><ins>+ attributes.ListAttribute.append(self, item) + +class UOWScalarElement(attributes.ScalarAttribute): + def value_changed(self, oldvalue, newvalue): + obj = self.obj </ins><span class="cx"> sess = get_session(obj, raiseerror=False) </span><span class="cx"> if sess is not None: </span><span class="cx"> if hasattr(obj, '_instance_key'): </span><span class="cx"> sess._register_dirty(obj) </span><span class="cx"> else: </span><span class="cx"> sess._register_new(obj) </span><del>- </del><ins>+ +class UOWAttributeManager(attributes.AttributeManager): + """overrides AttributeManager to provide unit-of-work "dirty" hooks when scalar attribues are modified, plus factory methods for UOWProperrty/UOWListElement.""" + def __init__(self): + attributes.AttributeManager.__init__(self) + </ins><span class="cx"> def create_prop(self, class_, key, uselist, callable_, **kwargs): </span><span class="cx"> return UOWProperty(class_, self, key, uselist, callable_, **kwargs) </span><span class="cx"> </span><ins>+ def create_scalar(self, obj, key, **kwargs): + return UOWScalarElement(obj, key, **kwargs) + </ins><span class="cx"> def create_list(self, obj, key, list_, **kwargs): </span><span class="cx"> return UOWListElement(obj, key, list_, **kwargs) </span><span class="cx"> </span><span class="lines">@@ -201,10 +215,6 @@ </span><span class="cx"> if not self.deleted.contains(obj): </span><span class="cx"> self._validate_obj(obj) </span><span class="cx"> self.deleted.append(obj) </span><del>- mapper = object_mapper(obj) - # TODO: should the cascading delete dependency thing - # happen wtihin PropertyLoader.process_dependencies ? - mapper.register_deleted(obj, self) </del><span class="cx"> </span><span class="cx"> def unregister_deleted(self, obj): </span><span class="cx"> try: </span><span class="lines">@@ -226,6 +236,7 @@ </span><span class="cx"> if self.deleted.contains(obj): </span><span class="cx"> continue </span><span class="cx"> flush_context.register_object(obj) </span><ins>+ </ins><span class="cx"> for item in self.modified_lists: </span><span class="cx"> obj = item.obj </span><span class="cx"> if objset is not None and not objset.contains(obj): </span><span class="lines">@@ -662,7 +673,13 @@ </span><span class="cx"> continue </span><span class="cx"> if not o in childtask.objects: </span><span class="cx"> # item needs to be saved since its added, or attached to a deleted object </span><del>- childtask.append(o, isdelete=isdelete and dep.processor.private) </del><ins>+ if isdelete: + childtask.append(o, "delete" in processor.cascade) + #if isdelete and "delete" in processor.cascade: + # childstask.append(o, True) + #elif not isdelete and "save-update" in processor.cascade: + # childtask.append(o, False) + #childtask.append(o, isdelete=isdelete and dep.processor.private) </ins><span class="cx"> if cyclicaldep: </span><span class="cx"> # cyclical, so create a placeholder UOWTask that may be built into the </span><span class="cx"> # final task tree </span><span class="lines">@@ -693,11 +710,12 @@ </span><span class="cx"> """takes a dependency-sorted tree of objects and creates a tree of UOWTasks""" </span><span class="cx"> t = objecttotask[node.item] </span><span class="cx"> can_add_to_parent = t.mapper is parenttask.mapper </span><del>- if can_add_to_parent: - parenttask.append(node.item, t.parent.objects[node.item].listonly, isdelete=t.parent.objects[node.item].isdelete, childtask=t) - else: - t.append(node.item, t.parent.objects[node.item].listonly, isdelete=t.parent.objects[node.item].isdelete) - parenttask.append(None, listonly=False, isdelete=t.parent.objects[node.item].isdelete, childtask=t) </del><ins>+ if t.parent.objects.has_key(node.item): + if can_add_to_parent: + parenttask.append(node.item, t.parent.objects[node.item].listonly, isdelete=t.parent.objects[node.item].isdelete, childtask=t) + else: + t.append(node.item, t.parent.objects[node.item].listonly, isdelete=t.parent.objects[node.item].isdelete) + parenttask.append(None, listonly=False, isdelete=t.parent.objects[node.item].isdelete, childtask=t) </ins><span class="cx"> if dependencies.has_key(node.item): </span><span class="cx"> for depprocessor, deptask in dependencies[node.item].iteritems(): </span><span class="cx"> if can_add_to_parent: </span></span></pre> </div> </div> </body> </html> |