summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/mapping/sync.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/mapping/sync.py')
-rw-r--r--lib/sqlalchemy/mapping/sync.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/lib/sqlalchemy/mapping/sync.py b/lib/sqlalchemy/mapping/sync.py
new file mode 100644
index 000000000..d5c1f87bf
--- /dev/null
+++ b/lib/sqlalchemy/mapping/sync.py
@@ -0,0 +1,121 @@
+import sqlalchemy.sql as sql
+import sqlalchemy.schema as schema
+from sqlalchemy.exceptions import *
+import properties
+
+"""contains the ClauseSynchronizer class which is used to map attributes between two objects
+in a manner corresponding to a SQL clause that compares column values."""
+
+ONETOMANY = 0
+MANYTOONE = 1
+MANYTOMANY = 2
+
+class ClauseSynchronizer(object):
+ """Given a SQL clause, usually a series of one or more binary
+ expressions between columns, and a set of 'source' and 'destination' mappers, compiles a set of SyncRules
+ corresponding to that information. The ClauseSynchronizer can then be executed given a set of parent/child
+ objects or destination dictionary, which will iterate through each of its SyncRules and execute them.
+ Each SyncRule will copy the value of a single attribute from the parent
+ to the child, corresponding to the pair of columns in a particular binary expression, using the source and
+ destination mappers to map those two columns to object attributes within parent and child."""
+ def __init__(self, parent_mapper, child_mapper, direction):
+ self.parent_mapper = parent_mapper
+ self.child_mapper = child_mapper
+ self.direction = direction
+ self.syncrules = []
+
+ def compile(self, sqlclause, source_tables, target_tables, issecondary=None):
+ def check_for_table(binary, l):
+ for col in [binary.left, binary.right]:
+ if col.table in l:
+ return col
+ else:
+ return None
+
+ def compile_binary(binary):
+ """assembles a SyncRule given a single binary condition"""
+ if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column):
+ return
+
+ if binary.left.table == binary.right.table:
+ # self-cyclical relation
+ if binary.left.primary_key:
+ source = binary.left
+ dest = binary.right
+ elif binary.right.primary_key:
+ source = binary.right
+ dest = binary.left
+ else:
+ raise ArgumentError("Cant determine direction for relationship %s = %s" % (binary.left.fullname, binary.right.fullname))
+ if self.direction == ONETOMANY:
+ self.syncrules.append(SyncRule(self.parent_mapper, source, dest, dest_mapper=self.child_mapper))
+ elif self.direction == MANYTOONE:
+ self.syncrules.append(SyncRule(self.child_mapper, source, dest, dest_mapper=self.parent_mapper))
+ else:
+ raise AssertionError("assert failed")
+ else:
+ pt = check_for_table(binary, source_tables)
+ tt = check_for_table(binary, target_tables)
+ #print "OK", binary, [t.name for t in source_tables], [t.name for t in target_tables]
+ if pt and tt:
+ if self.direction == ONETOMANY:
+ self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper))
+ elif self.direction == MANYTOONE:
+ self.syncrules.append(SyncRule(self.child_mapper, tt, pt, dest_mapper=self.parent_mapper))
+ else:
+ if not issecondary:
+ self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper, issecondary=issecondary))
+ else:
+ self.syncrules.append(SyncRule(self.child_mapper, pt, tt, dest_mapper=self.parent_mapper, issecondary=issecondary))
+
+ rules_added = len(self.syncrules)
+ processor = BinaryVisitor(compile_binary)
+ sqlclause.accept_visitor(processor)
+ if len(self.syncrules) == rules_added:
+ raise ArgumentError("No syncrules generated for join criterion " + str(sqlclause))
+
+ def execute(self, source, dest, obj, child, clearkeys):
+ for rule in self.syncrules:
+ rule.execute(source, dest, obj, child, clearkeys)
+
+class SyncRule(object):
+ """An instruction indicating how to populate the objects on each side of a relationship.
+ i.e. if table1 column A is joined against
+ table2 column B, and we are a one-to-many from table1 to table2, a syncrule would say
+ 'take the A attribute from object1 and assign it to the B attribute on object2'.
+
+ A rule contains the source mapper, the source column, destination column,
+ destination mapper in the case of a one/many relationship, and
+ the integer direction of this mapper relative to the association in the case
+ of a many to many relationship.
+ """
+ def __init__(self, source_mapper, source_column, dest_column, dest_mapper=None, issecondary=None):
+ self.source_mapper = source_mapper
+ self.source_column = source_column
+ self.issecondary = issecondary
+ self.dest_mapper = dest_mapper
+ self.dest_column = dest_column
+ #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper, direction
+
+ def execute(self, source, dest, obj, child, clearkeys):
+ if source is None:
+ if self.issecondary is False:
+ source = obj
+ elif self.issecondary is True:
+ source = child
+ if clearkeys or source is None:
+ value = None
+ else:
+ value = self.source_mapper._getattrbycolumn(source, self.source_column)
+ if isinstance(dest, dict):
+ dest[self.dest_column.key] = value
+ else:
+ #print "SYNC VALUE", value, "TO", dest
+ self.dest_mapper._setattrbycolumn(dest, self.dest_column, value)
+
+class BinaryVisitor(sql.ClauseVisitor):
+ def __init__(self, func):
+ self.func = func
+ def visit_binary(self, binary):
+ self.func(binary)
+