From: Mike Bayer Date: Mon, 29 May 2006 05:17:21 +0000 (+0000) Subject: working on [ticket:190], getting circular dependency sort to work with inheriting... X-Git-Tag: rel_0_2_2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0f4f7fd620d43dfbe263e8d1caf62ece2f625483;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git working on [ticket:190], getting circular dependency sort to work with inheriting mappers --- diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index b122b621d4..195aff2b08 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -378,7 +378,7 @@ class MapperStub(object): pass def delete_obj(self, *args, **kwargs): pass - def _primary_mapper(self): + def primary_mapper(self): return self def base_mapper(self): return self \ No newline at end of file diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 3907b0b12e..07e4f503d3 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -111,7 +111,7 @@ class Mapper(object): # tables - a collection of underlying Table objects pulled from mapped_table for table in (local_table, select_table): - if table is not None and isinstance(local_table, sql.SelectBaseMixin): + if table is not None and isinstance(table, sql.SelectBaseMixin): # some db's, noteably postgres, dont want to select from a select # without an alias. also if we make our own alias internally, then # the configured properties on the mapper are not matched against the alias @@ -174,6 +174,7 @@ class Mapper(object): else: self.select_table = self.mapped_table self.unjoined_table = self.local_table + # locate all tables contained within the "table" passed in, which # may be a join or other construct @@ -265,11 +266,6 @@ class Mapper(object): if isinstance(self.polymorphic_map[key], type): self.polymorphic_map[key] = class_mapper(self.polymorphic_map[key]) - l = [(key, prop) for key, prop in self.props.iteritems()] - for key, prop in l: - if getattr(prop, 'key', None) is None: - prop.init(key, self) - # select_table specified...set up a surrogate mapper that will be used for selects # select_table has to encompass all the columns of the mapped_table either directly # or through proxying relationships @@ -279,9 +275,15 @@ class Mapper(object): for key, prop in properties.iteritems(): if sql.is_column(prop): props[key] = self.select_table.corresponding_column(prop) - elif (isinstance(column, list) and sql.is_column(column[0])): + elif (isinstance(prop, list) and sql.is_column(prop[0])): props[key] = [self.select_table.corresponding_column(c) for c in prop] self.__surrogate_mapper = Mapper(self.class_, self.select_table, non_primary=True, properties=props, polymorphic_map=self.polymorphic_map, polymorphic_on=self.polymorphic_on) + + l = [(key, prop) for key, prop in self.props.iteritems()] + for key, prop in l: + if getattr(prop, 'key', None) is None: + prop.init(key, self) + def base_mapper(self): """returns the ultimate base mapper in an inheritance chain""" @@ -356,7 +358,7 @@ class Mapper(object): """returns True if this mapper is the primary mapper for its class key (class + entity_name)""" return mapper_registry.get(self.class_key, None) is self - def _primary_mapper(self): + def primary_mapper(self): """returns the primary mapper corresponding to this mapper's class key (class + entity_name)""" return mapper_registry[self.class_key] @@ -514,8 +516,6 @@ class Mapper(object): def _setattrbycolumn(self, obj, column, value): self.columntoproperty[column][0].setattr(obj, value) - def primary_mapper(self): - return mapper_registry[self.class_key] def save_obj(self, objects, uow, postupdate=False): """called by a UnitOfWork object to save objects, which involves either an INSERT or @@ -1103,6 +1103,8 @@ class TranslatingDict(dict): self.selectable = selectable def __translate_col(self, col): ourcol = self.selectable.corresponding_column(col, keys_ok=False, raiseerr=False) + #if col is not ourcol: + # print "TD TRANSLATING ", col, "TO", ourcol if ourcol is None: return col else: diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 59b85c0578..9f8883a310 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -174,7 +174,7 @@ class PropertyLoader(mapper.MapperProperty): def do_init_subclass(self, key, parent): """template method for subclasses of PropertyLoader""" pass - + def do_init(self, key, parent): import sqlalchemy.orm if isinstance(self.argument, type): @@ -274,18 +274,18 @@ class PropertyLoader(mapper.MapperProperty): return if binary.left.primary_key: if dependent[0] is binary.left.table: - raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (table '%s' appears on both sides of the relationship, or in an otherwise ambiguous manner). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), str(binary.left.table))) + raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (table '%s' appears on both sides of the relationship, or in an otherwise ambiguous manner). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), binary.left.table.name)) dependent[0] = binary.right.table self.foreignkey= binary.right elif binary.right.primary_key: if dependent[0] is binary.right.table: - raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (table '%s' appears on both sides of the relationship, or in an otherwise ambiguous manner). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), str(binary.right.table))) + raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (table '%s' appears on both sides of the relationship, or in an otherwise ambiguous manner). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), binary.right.table.name)) dependent[0] = binary.left.table self.foreignkey = binary.left visitor = BinaryVisitor(foo) self.primaryjoin.accept_visitor(visitor) if dependent[0] is None: - raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (no relationships joining tables '%s' and '%s' could be located). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), str(binary.left.table), str(binary.right.table))) + raise exceptions.ArgumentError("Could not determine the parent/child relationship for property '%s', based on join condition '%s' (no relationships joining tables '%s' and '%s' could be located). please specify the 'foreignkey' keyword parameter to the relation() function indicating a column on the remote side of the relationship" % (self.key, str(self.primaryjoin), str(binary.left.table), binary.right.table.name)) else: self.foreigntable = dependent[0] @@ -637,7 +637,7 @@ class BackRef(object): PropertyLoader's mapper.""" # try to set a LazyLoader on our mapper referencing the parent mapper mapper = prop.mapper.primary_mapper() - if not prop.mapper.props.has_key(self.key): + if not mapper.props.has_key(self.key): pj = self.kwargs.pop('primaryjoin', None) sj = self.kwargs.pop('secondaryjoin', None) # TODO: we are going to have the newly backref'd property create its diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index e6d5c424e1..1ebf9cd698 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -336,8 +336,8 @@ class UOWTransaction(object): """called by mapper.PropertyLoader to register the objects handled by one mapper being dependent on the objects handled by another.""" # correct for primary mapper (the mapper offcially associated with the class) - mapper = mapper._primary_mapper().base_mapper() - dependency = dependency._primary_mapper().base_mapper() + mapper = mapper.primary_mapper().base_mapper() + dependency = dependency.primary_mapper().base_mapper() self.dependencies[(mapper, dependency)] = True self._mark_modified() @@ -353,8 +353,8 @@ class UOWTransaction(object): #print "registerprocessor", str(mapper), repr(processor.key), str(mapperfrom) # correct for primary mapper (the mapper offcially associated with the class) - mapper = mapper._primary_mapper() - mapperfrom = mapperfrom._primary_mapper() + mapper = mapper.primary_mapper() + mapperfrom = mapperfrom.primary_mapper() task = self.get_task_by_mapper(mapper) targettask = self.get_task_by_mapper(mapperfrom) @@ -678,6 +678,12 @@ class UOWTask(object): self._execute_per_element_childtasks(trans, True) self._delete_objects(trans) + def polymorphic_tasks(self): + yield self + for task in self.inheriting_tasks: + for t in task.polymorphic_tasks(): + yield t + def contains_object(self, obj, polymorphic=False): if obj in self.objects: return True @@ -745,55 +751,59 @@ class UOWTask(object): return l def dependency_in_cycles(dep): - proctask = trans.get_task_by_mapper(dep.processor.mapper, True) + proctask = trans.get_task_by_mapper(dep.processor.mapper.primary_mapper(), True) return dep.targettask in cycles and (proctask is not None and proctask in cycles) # organize all original UOWDependencyProcessors by their target task deps_by_targettask = {} - for task in cycles: - for dep in task.dependencies: - if not dependency_in_cycles(dep): - extradeplist.append(dep) - l = deps_by_targettask.setdefault(dep.targettask, []) - l.append(dep) - - for task in cycles: - for taskelement in task.get_elements(polymorphic=True): - obj = taskelement.obj - object_to_original_task[obj] = task - #print "OBJ", repr(obj), "TASK", repr(task) - - for dep in deps_by_targettask.get(task, []): - # is this dependency involved in one of the cycles ? + for t in cycles: + for task in t.polymorphic_tasks(): + for dep in task.dependencies: if not dependency_in_cycles(dep): - continue - - (processor, targettask) = (dep.processor, dep.targettask) - isdelete = taskelement.isdelete + extradeplist.append(dep) + l = deps_by_targettask.setdefault(dep.targettask, []) + l.append(dep) + + for t in cycles: + for task in t.polymorphic_tasks(): + for taskelement in task.get_elements(polymorphic=False): + obj = taskelement.obj + object_to_original_task[obj] = task + #print "OBJ", repr(obj), "TASK", repr(task) + + for dep in deps_by_targettask.get(task, []): + # is this dependency involved in one of the cycles ? + if not dependency_in_cycles(dep): + continue + #print "DEP", dep.processor.key + (processor, targettask) = (dep.processor, dep.targettask) + isdelete = taskelement.isdelete - # list of dependent objects from this object - childlist = dep.get_object_dependencies(obj, trans, passive = True) + # list of dependent objects from this object + childlist = dep.get_object_dependencies(obj, trans, passive = True) - # the task corresponding to saving/deleting of those dependent objects - childtask = trans.get_task_by_mapper(processor.mapper) + # the task corresponding to saving/deleting of those dependent objects + childtask = trans.get_task_by_mapper(processor.mapper.primary_mapper()) - childlist = childlist.added_items() + childlist.unchanged_items() + childlist.deleted_items() + childlist = childlist.added_items() + childlist.unchanged_items() + childlist.deleted_items() - for o in childlist: - if o is None or not childtask.contains_object(o, polymorphic=True): - continue - 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(whosdep[0], dep).append(whosdep[0], isdelete=isdelete) + for o in childlist: + if o is None or not childtask.contains_object(o, polymorphic=True): + continue + #print "CHILD", o + 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(whosdep[0], dep).append(whosdep[0], isdelete=isdelete) + else: + get_dependency_task(whosdep[0], dep).append(whosdep[1], isdelete=isdelete) else: - get_dependency_task(whosdep[0], dep).append(whosdep[1], isdelete=isdelete) - else: - get_dependency_task(obj, dep).append(obj, isdelete=isdelete) - + get_dependency_task(obj, dep).append(obj, isdelete=isdelete) + + #print "TUPLES", tuples head = DependencySorter(tuples, allobjects).sort() if head is None: return None @@ -818,6 +828,7 @@ class UOWTask(object): can_add_to_parent = t.mapper is parenttask.mapper original_task = object_to_original_task[node.item] + #print "ORIG TASK", original_task if original_task.contains_object(node.item, polymorphic=False): if can_add_to_parent: parenttask.append(node.item, original_task.objects[node.item].listonly, isdelete=original_task.objects[node.item].isdelete, childtask=t)