[Sqlalchemy-commits] [1493] sqlalchemy/branches/schema/test: dependency processor refactored into fo
Brought to you by:
zzzeek
<!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>[1493] sqlalchemy/branches/schema/test: dependency processor refactored into four separate classes to deal with different relationship styles independently, avoid excessive if/else calls</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1493</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-23 18:57:29 -0500 (Tue, 23 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>dependency processor refactored into four separate classes to deal with different relationship styles independently, avoid excessive if/else calls simplifications to unitofwork, circular sorting pared down alot </pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormdependencypy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschematestcyclespy">sqlalchemy/branches/schema/test/cycles.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyormdependencypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py (1492 => 1493)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py 2006-05-23 15:40:29 UTC (rev 1492) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py 2006-05-23 23:57:29 UTC (rev 1493) </span><span class="lines">@@ -11,6 +11,17 @@ </span><span class="cx"> from sync import ONETOMANY,MANYTOONE,MANYTOMANY </span><span class="cx"> from sqlalchemy import sql </span><span class="cx"> </span><ins>+def create_dependency_processor(key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): + types = { + ONETOMANY : OneToManyDP, + MANYTOONE: ManyToOneDP, + MANYTOMANY : ManyToManyDP, + } + if association is not None: + return AssociationDP(key, syncrules, cascade, secondary, association, is_backref, post_update) + else: + return types[syncrules.direction](key, syncrules, cascade, secondary, association, is_backref, post_update) + </ins><span class="cx"> class DependencyProcessor(object): </span><span class="cx"> def __init__(self, key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): </span><span class="cx"> # TODO: update instance variable names to be more meaningful </span><span class="lines">@@ -25,22 +36,6 @@ </span><span class="cx"> self.post_update = post_update </span><span class="cx"> self.key = key </span><span class="cx"> </span><del>- class MapperStub(object): - """poses as a Mapper representing the association table in a many-to-many - join, when performing a flush(). - - The Task objects in the objectstore module treat it just like - any other Mapper, but in fact it only serves as a "dependency" placeholder - for the many-to-many update task.""" - def __init__(self, mapper): - self.mapper = mapper - def save_obj(self, *args, **kwargs): - pass - def delete_obj(self, *args, **kwargs): - pass - def _primary_mapper(self): - return self - </del><span class="cx"> def register_dependencies(self, uowcommit): </span><span class="cx"> """tells a UOWTransaction what mappers are dependent on which, with regards </span><span class="cx"> to the two or three mappers handled by this PropertyLoader. </span><span class="lines">@@ -49,66 +44,8 @@ </span><span class="cx"> will be executed after that mapper's objects have been saved or before </span><span class="cx"> they've been deleted. The process operation manages attributes and dependent </span><span class="cx"> operations upon the objects of one of the involved mappers.""" </span><del>- if self.association is not None: - # association object. our mapper should be dependent on both - # the parent mapper and the association object mapper. - # this is where we put the "stub" as a marker, so we get - # association/parent->stub->self, then we process the child - # elments after the 'stub' save, which is before our own - # mapper's save. - stub = DependencyProcessor.MapperStub(self.association) - uowcommit.register_dependency(self.parent, stub) - uowcommit.register_dependency(self.association, stub) - uowcommit.register_dependency(stub, self.mapper) - uowcommit.register_processor(stub, self, self.parent, False) - uowcommit.register_processor(stub, self, self.parent, True) </del><ins>+ raise NotImplementedError() </ins><span class="cx"> </span><del>- elif self.direction == MANYTOMANY: - # many-to-many. create a "Stub" mapper to represent the - # "middle table" in the relationship. This stub mapper doesnt save - # or delete any objects, but just marks a dependency on the two - # related mappers. its dependency processor then populates the - # association table. - - if self.is_backref: - # if we are the "backref" half of a two-way backref - # relationship, let the other mapper handle inserting the rows - return - stub = DependencyProcessor.MapperStub(self.mapper) - uowcommit.register_dependency(self.parent, stub) - uowcommit.register_dependency(self.mapper, stub) - uowcommit.register_processor(stub, self, self.parent, False) - uowcommit.register_processor(stub, self, self.parent, True) - elif self.direction == ONETOMANY: - if self.post_update: - stub = DependencyProcessor.MapperStub(self.mapper) - uowcommit.register_dependency(self.mapper, stub) - uowcommit.register_dependency(self.parent, stub) - uowcommit.register_processor(stub, self, self.parent, False) - uowcommit.register_processor(stub, self, self.parent, True) - else: - uowcommit.register_dependency(self.parent, self.mapper) - uowcommit.register_processor(self.parent, self, self.parent, False) - uowcommit.register_processor(self.parent, self, self.parent, True) - elif self.direction == MANYTOONE: - if self.post_update: - stub = DependencyProcessor.MapperStub(self.mapper) - uowcommit.register_dependency(self.mapper, stub) - uowcommit.register_dependency(self.parent, stub) - uowcommit.register_processor(stub, self, self.parent, False) - uowcommit.register_processor(stub, self, self.parent, True) - else: - uowcommit.register_dependency(self.mapper, self.parent) - uowcommit.register_processor(self.mapper, self, self.parent, False) - uowcommit.register_processor(self.mapper, self, self.parent, True) - else: - raise AssertionError(" no foreign key ?") - - def get_object_dependencies(self, obj, uowcommit, passive = True): - """returns the list of objects that are dependent on the given object, as according to the relationship - this dependency processor represents""" - return uowcommit.uow.attributes.get_history(obj, self.key, passive = passive) - </del><span class="cx"> def whose_dependent_on_who(self, obj1, obj2): </span><span class="cx"> """given an object pair assuming obj2 is a child of obj1, returns a tuple </span><span class="cx"> with the dependent object second, or None if they are equal. </span><span class="lines">@@ -125,64 +62,42 @@ </span><span class="cx"> """this method is called during a flush operation to synchronize data between a parent and child object. </span><span class="cx"> it is called within the context of the various mappers and sometimes individual objects sorted according to their </span><span class="cx"> insert/update/delete order (topological sort).""" </span><del>- #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) </del><ins>+ raise NotImplementedError() </ins><span class="cx"> </span><del>- def getlist(obj, passive=True): - return self.get_object_dependencies(obj, uowcommit, passive) </del><ins>+ def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + """used before the flushes' topological sort to traverse through related objects and insure every + instance which will require save/update/delete is properly added to the UOWTransaction.""" + raise NotImplementedError() </ins><span class="cx"> </span><del>- # plugin point </del><ins>+ def _synchronize(self, obj, child, associationrow, clearkeys): + """called during a flush to synchronize primary key identifier values between a parent/child object, as well as + to an associationrow in the case of many-to-many.""" + raise NotImplementedError() + + def get_object_dependencies(self, obj, uowcommit, passive = True): + """returns the list of objects that are dependent on the given object, as according to the relationship + this dependency processor represents""" + return uowcommit.uow.attributes.get_history(obj, self.key, passive = passive) </ins><span class="cx"> </span><del>- # TODO: process_dependencies has been refactored into two methods, process_dependencies and preprocess_dependencies. - # cleanup is still required to hone the method down to its minimal amount of code. - - if self.direction == MANYTOMANY: - connection = uowcommit.transaction.connection(self.mapper) - secondary_delete = [] - secondary_insert = [] - if delete: - for obj in deplist: - childlist = getlist(obj, False) - for child in childlist.deleted_items() + childlist.unchanged_items(): - associationrow = {} - self._synchronize(obj, child, associationrow, False) - secondary_delete.append(associationrow) - else: - for obj in deplist: - childlist = getlist(obj) - if childlist is None: continue - for child in childlist.added_items(): - associationrow = {} - self._synchronize(obj, child, associationrow, False) - secondary_insert.append(associationrow) - for child in childlist.deleted_items(): - associationrow = {} - self._synchronize(obj, child, associationrow, False) - secondary_delete.append(associationrow) - if len(secondary_delete): - # TODO: precompile the delete/insert queries and store them as instance variables - # on the PropertyLoader - statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c])) - connection.execute(statement, secondary_delete) - if len(secondary_insert): - statement = self.secondary.insert() - connection.execute(statement, secondary_insert) - elif self.direction == MANYTOONE and delete: - if self.cascade.delete_orphan: - pass - elif self.post_update: - # post_update means we have to update our row to not reference the child object - # before we can DELETE the row - for obj in deplist: - self._synchronize(obj, None, None, True) - uowcommit.register_object(obj, postupdate=True) - elif self.direction == ONETOMANY and delete: </del><ins>+ +class OneToManyDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + if self.post_update: + stub = MapperStub(self.mapper) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_processor(stub, self, self.parent) + else: + uowcommit.register_dependency(self.parent, self.mapper) + uowcommit.register_processor(self.parent, self, self.parent) + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if delete: </ins><span class="cx"> # head object is being deleted, and we manage its list of child objects </span><span class="cx"> # the child objects have to have their foreign key to the parent set to NULL </span><del>- if self.cascade.delete_orphan and not self.post_update: - pass - else: </del><ins>+ if not self.cascade.delete_orphan or self.post_update: </ins><span class="cx"> for obj in deplist: </span><del>- childlist = getlist(obj, False) </del><ins>+ childlist = self.get_object_dependencies(obj, uowcommit, passive=False) </ins><span class="cx"> for child in childlist.deleted_items(): </span><span class="cx"> if child is not None and childlist.hasparent(child) is False: </span><span class="cx"> self._synchronize(obj, child, None, True) </span><span class="lines">@@ -193,89 +108,31 @@ </span><span class="cx"> self._synchronize(obj, child, None, True) </span><span class="cx"> if self.post_update: </span><span class="cx"> uowcommit.register_object(child, postupdate=True) </span><del>- elif self.association is not None: - # manage association objects. - for obj in deplist: - childlist = getlist(obj, passive=True) - if childlist is None: continue - - #print "DIRECTION", self.direction - d = {} - for child in childlist: - self._synchronize(obj, child, None, False) - key = self.mapper.instance_key(child) - #print "SYNCHRONIZED", child, "INSTANCE KEY", key - d[key] = child - uowcommit.unregister_object(child) - - for child in childlist.added_items(): - uowcommit.register_object(child) - key = self.mapper.instance_key(child) - #print "ADDED, INSTANCE KEY", key - d[key] = child - - for child in childlist.unchanged_items(): - key = self.mapper.instance_key(child) - o = d[key] - o._instance_key= key - - for child in childlist.deleted_items(): - key = self.mapper.instance_key(child) - #print "DELETED, INSTANCE KEY", key - if d.has_key(key): - o = d[key] - o._instance_key = key - uowcommit.unregister_object(child) - else: - #print "DELETE ASSOC OBJ", repr(child) - uowcommit.register_object(child, isdelete=True) </del><span class="cx"> else: </span><span class="cx"> for obj in deplist: </span><del>- childlist = getlist(obj, passive=True) </del><ins>+ childlist = self.get_object_dependencies(obj, uowcommit, passive=True) </ins><span class="cx"> if childlist is not None: </span><span class="cx"> for child in childlist.added_items(): </span><span class="cx"> self._synchronize(obj, child, None, False) </span><del>- if self.direction == ONETOMANY and child is not None and self.post_update: </del><ins>+ if child is not None and self.post_update: </ins><span class="cx"> uowcommit.register_object(child, postupdate=True) </span><del>- if self.direction == MANYTOONE: - if self.post_update: - uowcommit.register_object(obj, postupdate=True) - else: - for child in childlist.deleted_items(): - if not self.cascade.delete_orphan: - self._synchronize(obj, child, None, True) </del><ins>+ for child in childlist.deleted_items(): + if not self.cascade.delete_orphan: + self._synchronize(obj, child, None, True) </ins><span class="cx"> </span><span class="cx"> def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): </span><del>- """used before the flushes' topological sort to traverse through related objects and insure every - instance which will require save/update/delete is properly added to the UOWTransaction.""" </del><span class="cx"> #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) </span><span class="cx"> </span><del>- # TODO: post_update instructions should be established in this step as well - # (and executed in the regular traversal) - if self.post_update: - return - - # TODO: this method is the product of splitting process_dependencies into two methods. - # cleanup is still required to hone the method down to its minimal amount of code. - - def getlist(obj, passive=True): - return self.get_object_dependencies(obj, uowcommit, passive) - - if self.direction == MANYTOMANY: - pass - elif self.direction == MANYTOONE and delete: - if self.cascade.delete_orphan: - for obj in deplist: - childlist = getlist(obj, False) - for child in childlist.deleted_items() + childlist.unchanged_items(): - if child is not None and childlist.hasparent(child) is False: - uowcommit.register_object(child, isdelete=True) - elif self.direction == ONETOMANY and delete: </del><ins>+ if delete: </ins><span class="cx"> # head object is being deleted, and we manage its list of child objects </span><span class="cx"> # the child objects have to have their foreign key to the parent set to NULL </span><del>- if self.cascade.delete_orphan and not self.post_update: </del><ins>+ if self.post_update: + # TODO: post_update instructions should be established in this step as well + # (and executed in the regular traversal) + pass + elif self.cascade.delete_orphan: </ins><span class="cx"> for obj in deplist: </span><del>- childlist = getlist(obj, False) </del><ins>+ childlist = self.get_object_dependencies(obj, uowcommit, passive=False) </ins><span class="cx"> for child in childlist.deleted_items(): </span><span class="cx"> if child is not None and childlist.hasparent(child) is False: </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><span class="lines">@@ -284,48 +141,213 @@ </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><span class="cx"> else: </span><span class="cx"> for obj in deplist: </span><del>- childlist = getlist(obj, False) </del><ins>+ childlist = self.get_object_dependencies(obj, uowcommit, passive=False) </ins><span class="cx"> for child in childlist.deleted_items(): </span><span class="cx"> if child is not None and childlist.hasparent(child) is False: </span><span class="cx"> uowcommit.register_object(child) </span><span class="cx"> for child in childlist.unchanged_items(): </span><span class="cx"> if child is not None: </span><span class="cx"> uowcommit.register_object(child) </span><del>- elif self.association is not None: - # TODO: clean up the association step in process_dependencies and move the - # appropriate sections of it to here - pass </del><span class="cx"> else: </span><span class="cx"> for obj in deplist: </span><del>- childlist = getlist(obj, passive=True) </del><ins>+ childlist = self.get_object_dependencies(obj, uowcommit, passive=True) </ins><span class="cx"> if childlist is not None: </span><span class="cx"> for child in childlist.added_items(): </span><del>- if self.direction == ONETOMANY and child is not None: </del><ins>+ if child is not None: </ins><span class="cx"> uowcommit.register_object(child) </span><del>- if self.direction == MANYTOONE: - uowcommit.register_object(obj) - else: - for child in childlist.deleted_items(): - if not self.cascade.delete_orphan: - uowcommit.register_object(child, isdelete=False) - elif childlist.hasparent(child) is False: </del><ins>+ for child in childlist.deleted_items(): + if not self.cascade.delete_orphan: + uowcommit.register_object(child, isdelete=False) + elif childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + + def _synchronize(self, obj, child, associationrow, clearkeys): + source = obj + dest = child + if dest is None: + return + self.syncrules.execute(source, dest, obj, child, clearkeys) + +class ManyToOneDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + if self.post_update: + stub = MapperStub(self.mapper) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_processor(stub, self, self.parent) + else: + uowcommit.register_dependency(self.mapper, self.parent) + uowcommit.register_processor(self.mapper, self, self.parent) + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if delete: + if self.post_update and not self.cascade.delete_orphan: + # post_update means we have to update our row to not reference the child object + # before we can DELETE the row + for obj in deplist: + self._synchronize(obj, None, None, True) + uowcommit.register_object(obj, postupdate=True) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is not None: + for child in childlist.added_items(): + self._synchronize(obj, child, None, False) + if self.post_update: + uowcommit.register_object(obj, postupdate=True) + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + # TODO: post_update instructions should be established in this step as well + # (and executed in the regular traversal) + if self.post_update: + return + if delete: + if self.cascade.delete_orphan: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items() + childlist.unchanged_items(): + if child is not None and childlist.hasparent(child) is False: </ins><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><ins>+ else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + uowcommit.register_object(obj) + + def _synchronize(self, obj, child, associationrow, clearkeys): + source = child + dest = obj + if dest is None: + return + self.syncrules.execute(source, dest, obj, child, clearkeys) </ins><span class="cx"> </span><del>- </del><ins>+class ManyToManyDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + # many-to-many. create a "Stub" mapper to represent the + # "middle table" in the relationship. This stub mapper doesnt save + # or delete any objects, but just marks a dependency on the two + # related mappers. its dependency processor then populates the + # association table. + + if self.is_backref: + # if we are the "backref" half of a two-way backref + # relationship, let the other mapper handle inserting the rows + return + stub = MapperStub(self.mapper) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_processor(stub, self, self.parent) + + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + connection = uowcommit.transaction.connection(self.mapper) + secondary_delete = [] + secondary_insert = [] + if delete: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items() + childlist.unchanged_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_delete.append(associationrow) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit) + if childlist is None: continue + for child in childlist.added_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_insert.append(associationrow) + for child in childlist.deleted_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_delete.append(associationrow) + if len(secondary_delete): + # TODO: precompile the delete/insert queries and store them as instance variables + # on the PropertyLoader + statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c])) + connection.execute(statement, secondary_delete) + if len(secondary_insert): + statement = self.secondary.insert() + connection.execute(statement, secondary_insert) + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + pass </ins><span class="cx"> def _synchronize(self, obj, child, associationrow, clearkeys): </span><del>- """called during a flush to synchronize primary key identifier values between a parent/child object, as well as - to an associationrow in the case of many-to-many.""" - if self.direction == ONETOMANY: - source = obj - dest = child - elif self.direction == MANYTOONE: - source = child - dest = obj - elif self.direction == MANYTOMANY: - dest = associationrow - source = None - </del><ins>+ dest = associationrow + source = None </ins><span class="cx"> if dest is None: </span><span class="cx"> return </span><ins>+ self.syncrules.execute(source, dest, obj, child, clearkeys) </ins><span class="cx"> </span><del>- self.syncrules.execute(source, dest, obj, child, clearkeys) </del><ins>+class AssociationDP(OneToManyDP): + def register_dependencies(self, uowcommit): + # association object. our mapper should be dependent on both + # the parent mapper and the association object mapper. + # this is where we put the "stub" as a marker, so we get + # association/parent->stub->self, then we process the child + # elments after the 'stub' save, which is before our own + # mapper's save. + stub = MapperStub(self.association) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_dependency(self.association, stub) + uowcommit.register_dependency(stub, self.mapper) + uowcommit.register_processor(stub, self, self.parent) + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + # manage association objects. + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is None: continue + + #print "DIRECTION", self.direction + d = {} + for child in childlist: + self._synchronize(obj, child, None, False) + key = self.mapper.instance_key(child) + #print "SYNCHRONIZED", child, "INSTANCE KEY", key + d[key] = child + uowcommit.unregister_object(child) + + for child in childlist.added_items(): + uowcommit.register_object(child) + key = self.mapper.instance_key(child) + #print "ADDED, INSTANCE KEY", key + d[key] = child + + for child in childlist.unchanged_items(): + key = self.mapper.instance_key(child) + o = d[key] + o._instance_key= key + + for child in childlist.deleted_items(): + key = self.mapper.instance_key(child) + #print "DELETED, INSTANCE KEY", key + if d.has_key(key): + o = d[key] + o._instance_key = key + uowcommit.unregister_object(child) + else: + #print "DELETE ASSOC OBJ", repr(child) + uowcommit.register_object(child, isdelete=True) + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + # TODO: clean up the association step in process_dependencies and move the + # appropriate sections of it to here + pass + + +class MapperStub(object): + """poses as a Mapper representing the association table in a many-to-many + join, when performing a flush(). + + The Task objects in the objectstore module treat it just like + any other Mapper, but in fact it only serves as a "dependency" placeholder + for the many-to-many update task.""" + def __init__(self, mapper): + self.mapper = mapper + def save_obj(self, *args, **kwargs): + pass + def delete_obj(self, *args, **kwargs): + pass + def _primary_mapper(self): + return self </ins></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1492 => 1493)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-23 15:40:29 UTC (rev 1492) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-23 23:57:29 UTC (rev 1493) </span><span class="lines">@@ -228,7 +228,7 @@ </span><span class="cx"> self.uselist = True </span><span class="cx"> </span><span class="cx"> self._compile_synchronizers() </span><del>- self._dependency_processor = dependency.DependencyProcessor(self.key, self.syncrules, self.cascade, secondary=self.secondary, association=self.association, is_backref=self.is_backref, post_update=self.post_update) </del><ins>+ self._dependency_processor = dependency.create_dependency_processor(self.key, self.syncrules, self.cascade, secondary=self.secondary, association=self.association, is_backref=self.is_backref, post_update=self.post_update) </ins><span class="cx"> </span><span class="cx"> # primary property handler, set up class attributes </span><span class="cx"> if self.is_primary(): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py (1492 => 1493)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-23 15:40:29 UTC (rev 1492) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-23 23:57:29 UTC (rev 1493) </span><span class="lines">@@ -269,7 +269,7 @@ </span><span class="cx"> self.dependencies = {} </span><span class="cx"> self.tasks = {} </span><span class="cx"> self.__modified = False </span><del>- self._is_executing = False </del><ins>+ self.__is_executing = False </ins><span class="cx"> </span><span class="cx"> def register_object(self, obj, isdelete = False, listonly = False, postupdate=False, **kwargs): </span><span class="cx"> """adds an object to this UOWTransaction to be updated in the database. </span><span class="lines">@@ -281,6 +281,7 @@ </span><span class="cx"> save/delete operation on the object itself, unless an additional save/delete </span><span class="cx"> registration is entered for the object.""" </span><span class="cx"> #print "REGISTER", repr(obj), repr(getattr(obj, '_instance_key', None)), str(isdelete), str(listonly) </span><ins>+ </ins><span class="cx"> # things can get really confusing if theres duplicate instances floating around, </span><span class="cx"> # so make sure everything is OK </span><span class="cx"> self.uow._validate_obj(obj) </span><span class="lines">@@ -288,12 +289,12 @@ </span><span class="cx"> mapper = object_mapper(obj) </span><span class="cx"> self.mappers.append(mapper) </span><span class="cx"> task = self.get_task_by_mapper(mapper) </span><del>- </del><ins>+ </ins><span class="cx"> if postupdate: </span><span class="cx"> mod = task.append_postupdate(obj) </span><del>- self.__modified = self.__modified or mod </del><ins>+ if mod: self._mark_modified() </ins><span class="cx"> return </span><del>- </del><ins>+ </ins><span class="cx"> # for a cyclical task, things need to be sorted out already, </span><span class="cx"> # so this object should have already been added to the appropriate sub-task </span><span class="cx"> # can put an assertion here to make sure.... </span><span class="lines">@@ -301,12 +302,18 @@ </span><span class="cx"> return </span><span class="cx"> </span><span class="cx"> mod = task.append(obj, listonly, isdelete=isdelete, **kwargs) </span><del>- self.__modified = self.__modified or mod </del><ins>+ if mod: self._mark_modified() </ins><span class="cx"> </span><span class="cx"> def unregister_object(self, obj): </span><span class="cx"> mapper = object_mapper(obj) </span><span class="cx"> task = self.get_task_by_mapper(mapper) </span><del>- task.delete(obj) </del><ins>+ if obj in task.objects: + task.delete(obj) + self._mark_modified() + + def _mark_modified(self): + #if self.__is_executing: + # raise "test assertion failed" </ins><span class="cx"> self.__modified = True </span><span class="cx"> </span><span class="cx"> def get_task_by_mapper(self, mapper): </span><span class="lines">@@ -325,9 +332,9 @@ </span><span class="cx"> one mapper being dependent on the objects handled by another.""" </span><span class="cx"> # correct for primary mapper (the mapper offcially associated with the class) </span><span class="cx"> self.dependencies[(mapper._primary_mapper(), dependency._primary_mapper())] = True </span><del>- self.__modified = True </del><ins>+ self._mark_modified() </ins><span class="cx"> </span><del>- def register_processor(self, mapper, processor, mapperfrom, isdeletefrom): </del><ins>+ def register_processor(self, mapper, processor, mapperfrom): </ins><span class="cx"> """called by mapper.PropertyLoader to register itself as a "processor", which </span><span class="cx"> will be associated with a particular UOWTask, and be given a list of "dependent" </span><span class="cx"> objects corresponding to another UOWTask to be processed, either after that secondary </span><span class="lines">@@ -335,23 +342,23 @@ </span><span class="cx"> # when the task from "mapper" executes, take the objects from the task corresponding </span><span class="cx"> # to "mapperfrom"'s list of save/delete objects, and send them to "processor" </span><span class="cx"> # for dependency processing </span><del>- #print "registerprocessor", str(mapper), repr(processor.key), str(mapperfrom), repr(isdeletefrom) </del><ins>+ #print "registerprocessor", str(mapper), repr(processor.key), str(mapperfrom) </ins><span class="cx"> </span><span class="cx"> # correct for primary mapper (the mapper offcially associated with the class) </span><span class="cx"> mapper = mapper._primary_mapper() </span><span class="cx"> mapperfrom = mapperfrom._primary_mapper() </span><span class="cx"> task = self.get_task_by_mapper(mapper) </span><span class="cx"> targettask = self.get_task_by_mapper(mapperfrom) </span><del>- up = UOWDependencyProcessor(processor, targettask, isdeletefrom) </del><ins>+ up = UOWDependencyProcessor(processor, targettask) </ins><span class="cx"> task.dependencies.append(up) </span><span class="cx"> up.preexecute(self) </span><del>- self.__modified = True </del><ins>+ self._mark_modified() </ins><span class="cx"> </span><span class="cx"> def execute(self, echo=False): </span><span class="cx"> for task in self.tasks.values(): </span><span class="cx"> task.mapper.register_dependencies(self) </span><span class="cx"> </span><del>- self._is_executing = True </del><ins>+ self.__is_executing = True </ins><span class="cx"> </span><span class="cx"> head = self._sort_dependencies() </span><span class="cx"> self.__modified = False </span><span class="lines">@@ -365,10 +372,7 @@ </span><span class="cx"> #if self.__modified and head is not None: </span><span class="cx"> # raise "Assertion failed ! new pre-execute dependency step should eliminate post-execute changes (except post_update stuff)." </span><span class="cx"> if LOG or echo: </span><del>- if self.__modified and head is not None: - print "\nAfter Execute:\n" + head.dump() - else: - print "\nExecute complete (no post-exec changes)\n" </del><ins>+ print "\nExecute complete\n" </ins><span class="cx"> </span><span class="cx"> def post_exec(self): </span><span class="cx"> """after an execute/flush is completed, all of the objects and lists that have </span><span class="lines">@@ -427,22 +431,19 @@ </span><span class="cx"> """in between the saving and deleting of objects, process "dependent" data, such as filling in </span><span class="cx"> a foreign key on a child item from a new primary key, or deleting association rows before a </span><span class="cx"> delete. This object acts as a proxy to a DependencyProcessor.""" </span><del>- def __init__(self, processor, targettask, isdeletefrom): </del><ins>+ def __init__(self, processor, targettask): </ins><span class="cx"> self.processor = processor </span><span class="cx"> self.targettask = targettask </span><del>- self.isdeletefrom = isdeletefrom </del><span class="cx"> </span><span class="cx"> def preexecute(self, trans): </span><del>- if not self.isdeletefrom: - self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements() if elem.obj is not None], trans, delete=self.isdeletefrom) - else: - self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements() if elem.obj is not None], trans, delete=self.isdeletefrom) </del><ins>+ self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements if elem.obj is not None], trans, delete=False) + self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements if elem.obj is not None], trans, delete=True) </ins><span class="cx"> </span><del>- def execute(self, trans): - if not self.isdeletefrom: - self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements() if elem.obj is not None], trans, delete=self.isdeletefrom) </del><ins>+ def execute(self, trans, delete): + if not delete: + self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements if elem.obj is not None], trans, delete=False) </ins><span class="cx"> else: </span><del>- self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements() if elem.obj is not None], trans, delete=self.isdeletefrom) </del><ins>+ self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements if elem.obj is not None], trans, delete=True) </ins><span class="cx"> </span><span class="cx"> def get_object_dependencies(self, obj, trans, passive): </span><span class="cx"> return self.processor.get_object_dependencies(obj, trans, passive=passive) </span><span class="lines">@@ -451,7 +452,7 @@ </span><span class="cx"> return self.processor.whose_dependent_on_who(obj, o) </span><span class="cx"> </span><span class="cx"> def branch(self, task): </span><del>- return UOWDependencyProcessor(self.processor, task, self.isdeletefrom) </del><ins>+ return UOWDependencyProcessor(self.processor, task) </ins><span class="cx"> </span><span class="cx"> class UOWTask(object): </span><span class="cx"> def __init__(self, uowtransaction, mapper): </span><span class="lines">@@ -508,41 +509,29 @@ </span><span class="cx"> self.circular.execute(trans) </span><span class="cx"> return </span><span class="cx"> </span><del>- self.mapper.save_obj(self.tosave_objects(), trans) - for dep in self.cyclical_save_dependencies(): - dep.execute(trans) - for element in self.tosave_elements(): </del><ins>+ self.mapper.save_obj(self.tosave_objects, trans) + for dep in self.cyclical_dependencies: + dep.execute(trans, False) + for element in self.tosave_elements: </ins><span class="cx"> for task in element.childtasks: </span><span class="cx"> task.execute(trans) </span><del>- for dep in self.save_dependencies(): - dep.execute(trans) - for dep in self.delete_dependencies(): - dep.execute(trans) - for dep in self.cyclical_delete_dependencies(): - dep.execute(trans) </del><ins>+ for dep in self.dependencies: + dep.execute(trans, False) + for dep in self.dependencies: + dep.execute(trans, True) + for dep in self.cyclical_dependencies: + dep.execute(trans, True) </ins><span class="cx"> for child in self.childtasks: </span><span class="cx"> child.execute(trans) </span><del>- for element in self.todelete_elements(): </del><ins>+ for element in self.todelete_elements: </ins><span class="cx"> for task in element.childtasks: </span><span class="cx"> task.execute(trans) </span><del>- self.mapper.delete_obj(self.todelete_objects(), trans) </del><ins>+ self.mapper.delete_obj(self.todelete_objects, trans) </ins><span class="cx"> </span><del>- def tosave_elements(self): - return [rec for rec in self.objects.values() if not rec.isdelete] - def todelete_elements(self): - return [rec for rec in self.objects.values() if rec.isdelete] - def tosave_objects(self): - return [rec.obj for rec in self.objects.values() if rec.obj is not None and not rec.listonly and rec.isdelete is False] - def todelete_objects(self): - return [rec.obj for rec in self.objects.values() if rec.obj is not None and not rec.listonly and rec.isdelete is True] - def save_dependencies(self): - return [dep for dep in self.dependencies if not dep.isdeletefrom] - def cyclical_save_dependencies(self): - return [dep for dep in self.cyclical_dependencies if not dep.isdeletefrom] - def delete_dependencies(self): - return [dep for dep in self.dependencies if dep.isdeletefrom] - def cyclical_delete_dependencies(self): - return [dep for dep in self.cyclical_dependencies if dep.isdeletefrom] </del><ins>+ tosave_elements = property(lambda self: [rec for rec in self.objects.values() if not rec.isdelete]) + todelete_elements = property(lambda self:[rec for rec in self.objects.values() if rec.isdelete]) + tosave_objects = property(lambda self:[rec.obj for rec in self.objects.values() if rec.obj is not None and not rec.listonly and rec.isdelete is False]) + todelete_objects = property(lambda self:[rec.obj for rec in self.objects.values() if rec.obj is not None and not rec.listonly and rec.isdelete is True]) </ins><span class="cx"> </span><span class="cx"> def _sort_circular_dependencies(self, trans, cycles): </span><span class="cx"> """for a single task, creates a hierarchical tree of "subtasks" which associate </span><span class="lines">@@ -605,21 +594,18 @@ </span><span class="cx"> #print "OBJ", repr(obj), "TASK", repr(task) </span><span class="cx"> </span><span class="cx"> for dep in deps_by_targettask.get(task, []): </span><del>- (processor, targettask, isdelete) = (dep.processor, dep.targettask, dep.isdeletefrom) - if taskelement.isdelete is not dep.isdeletefrom: </del><ins>+ # is this dependency involved in one of the cycles ? + cyclicaldep = dep.targettask in cycles and trans.get_task_by_mapper(dep.processor.mapper) in cycles + if not cyclicaldep: </ins><span class="cx"> continue </span><del>- #print "GETING LIST OFF PROC", processor.key, "OBJ", repr(obj) - - # traverse through the modified child items of each object. normally this - # is done via PropertyLoader in properties.py, but we need all the info - # up front here to do the object-level topological sort. </del><ins>+ + (processor, targettask) = (dep.processor, dep.targettask) + isdelete = taskelement.isdelete </ins><span class="cx"> </span><span class="cx"> # list of dependent objects from this object </span><span class="cx"> childlist = dep.get_object_dependencies(obj, trans, passive = True) </span><span class="cx"> # the task corresponding to the processor's objects </span><span class="cx"> childtask = trans.get_task_by_mapper(processor.mapper) </span><del>- # is this dependency involved in one of the cycles ? - cyclicaldep = dep.targettask in cycles and trans.get_task_by_mapper(dep.processor.mapper) in cycles </del><span class="cx"> </span><span class="cx"> if isdelete: </span><span class="cx"> childlist = childlist.unchanged_items() + childlist.deleted_items() </span><span class="lines">@@ -627,23 +613,19 @@ </span><span class="cx"> childlist = childlist.added_items() </span><span class="cx"> </span><span class="cx"> for o in childlist: </span><del>- if o is None: </del><ins>+ if o is None or o not in childtask.objects: </ins><span class="cx"> continue </span><del>- if not o in childtask.objects: - object_to_original_task[o] = childtask - if cyclicaldep: - # cyclical, so create an ordered pair for the dependency sort - whosdep = dep.whose_dependent_on_who(obj, o) - if whosdep is not None: - tuples.append(whosdep) - # create a UOWDependencyProcessor representing this pair of objects. - # append it to a UOWTask - if whosdep[0] is obj: - get_dependency_task(obj, dep).append(whosdep[0], isdelete=isdelete) - else: - get_dependency_task(obj, dep).append(whosdep[1], isdelete=isdelete) </del><ins>+ whosdep = dep.whose_dependent_on_who(obj, o) + if whosdep is not None: + tuples.append(whosdep) + # create a UOWDependencyProcessor representing this pair of objects. + # append it to a UOWTask + if whosdep[0] is obj: + get_dependency_task(obj, dep).append(whosdep[0], isdelete=isdelete) </ins><span class="cx"> else: </span><del>- get_dependency_task(obj, dep).append(obj, isdelete=isdelete) </del><ins>+ get_dependency_task(obj, dep).append(whosdep[1], isdelete=isdelete) + else: + get_dependency_task(obj, dep).append(obj, isdelete=isdelete) </ins><span class="cx"> </span><span class="cx"> head = DependencySorter(tuples, allobjects).sort() </span><span class="cx"> if head is None: </span><span class="lines">@@ -675,8 +657,8 @@ </span><span class="cx"> else: </span><span class="cx"> t.append(node.item, original_task.objects[node.item].listonly, isdelete=original_task.objects[node.item].isdelete) </span><span class="cx"> parenttask.append(None, listonly=False, isdelete=original_task.objects[node.item].isdelete, childtask=t) </span><del>- else: - parenttask.append(None, listonly=False, isdelete=original_task.objects[node.item].isdelete, childtask=t) </del><ins>+ #else: + # parenttask.append(None, listonly=False, isdelete=original_task.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 class="lines">@@ -716,17 +698,15 @@ </span><span class="cx"> buf.write(text) </span><span class="cx"> headers[text] = True </span><span class="cx"> </span><del>- def _dump_processor(proc): - if proc.isdeletefrom: </del><ins>+ def _dump_processor(proc, deletes): + if deletes: </ins><span class="cx"> val = [t for t in proc.targettask.objects.values() if t.isdelete] </span><span class="cx"> else: </span><span class="cx"> val = [t for t in proc.targettask.objects.values() if not t.isdelete] </span><span class="cx"> </span><span class="cx"> buf.write(_indent() + " |- %s attribute on %s (UOWDependencyProcessor(%d) processing %s)\n" % ( </span><span class="cx"> repr(proc.processor.key), </span><del>- (proc.isdeletefrom and - "%s's to be deleted" % _repr_task_class(proc.targettask) - or "saved %s's" % _repr_task_class(proc.targettask)), </del><ins>+ ("%s's to be %s" % (_repr_task_class(proc.targettask), deletes and "deleted" or "saved")), </ins><span class="cx"> id(proc), </span><span class="cx"> _repr_task(proc.targettask)) </span><span class="cx"> ) </span><span class="lines">@@ -778,39 +758,39 @@ </span><span class="cx"> buf.write(i + " " + _repr_task(self)) </span><span class="cx"> </span><span class="cx"> buf.write("\n") </span><del>- for rec in self.tosave_elements(): </del><ins>+ for rec in self.tosave_elements: </ins><span class="cx"> if rec.listonly: </span><span class="cx"> continue </span><span class="cx"> header(buf, _indent() + " |- Save elements\n") </span><span class="cx"> buf.write(_indent() + " |- " + _repr_task_element(rec) + "\n") </span><del>- for dep in self.cyclical_save_dependencies(): </del><ins>+ for dep in self.cyclical_dependencies: </ins><span class="cx"> header(buf, _indent() + " |- Cyclical Save dependencies\n") </span><del>- _dump_processor(dep) - for element in self.tosave_elements(): </del><ins>+ _dump_processor(dep, False) + for element in self.tosave_elements: </ins><span class="cx"> for task in element.childtasks: </span><span class="cx"> header(buf, _indent() + " |- Save subelements of UOWTaskElement(%s)\n" % id(element)) </span><span class="cx"> task._dump(buf, indent + 1) </span><del>- for dep in self.save_dependencies(): </del><ins>+ for dep in self.dependencies: </ins><span class="cx"> header(buf, _indent() + " |- Save dependencies\n") </span><del>- _dump_processor(dep) - for dep in self.delete_dependencies(): </del><ins>+ _dump_processor(dep, False) + for dep in self.dependencies: </ins><span class="cx"> header(buf, _indent() + " |- Delete dependencies\n") </span><del>- _dump_processor(dep) - for dep in self.cyclical_delete_dependencies(): </del><ins>+ _dump_processor(dep, True) + for dep in self.cyclical_dependencies: </ins><span class="cx"> header(buf, _indent() + " |- Cyclical Delete dependencies\n") </span><del>- _dump_processor(dep) </del><ins>+ _dump_processor(dep, True) </ins><span class="cx"> for child in self.childtasks: </span><span class="cx"> header(buf, _indent() + " |- Child tasks\n") </span><span class="cx"> child._dump(buf, indent + 1) </span><span class="cx"> # for obj in self.postupdate: </span><span class="cx"> # header(buf, _indent() + " |- Post Update objects\n") </span><span class="cx"> # buf.write(_repr(obj) + "\n") </span><del>- ... [truncated message content] |