[Sqlalchemy-commits] [1053] sqlalchemy/trunk/test: factored out "syncrule" logic to a separate packa
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-02-26 21:45:26
|
<!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>[1053] sqlalchemy/trunk/test: factored out "syncrule" logic to a separate package, so mapper will be able to make use of it as well as properties.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1053</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-02-26 15:45:10 -0600 (Sun, 26 Feb 2006)</dd> </dl> <h3>Log Message</h3> <pre>factored out "syncrule" logic to a separate package, so mapper will be able to make use of it as well as properties. also clarifies the "synchronization" idea</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemymappingobjectstorepy">sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingpropertiespy">sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemytrunktestinheritancepy">sqlalchemy/trunk/test/inheritance.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemymappingsyncpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py (1052 => 1053)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-02-26 21:39:14 UTC (rev 1052) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-02-26 21:45:10 UTC (rev 1053) </span><span class="lines">@@ -352,7 +352,7 @@ </span><span class="cx"> </span><span class="cx"> def register_callable(self, obj, key, func, uselist, **kwargs): </span><span class="cx"> self.attributes.set_callable(obj, key, func, uselist, **kwargs) </span><del>- </del><ins>+ </ins><span class="cx"> def register_clean(self, obj): </span><span class="cx"> try: </span><span class="cx"> del self.dirty[obj] </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py (1052 => 1053)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-02-26 21:39:14 UTC (rev 1052) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-02-26 21:45:10 UTC (rev 1053) </span><span class="lines">@@ -14,6 +14,7 @@ </span><span class="cx"> import sqlalchemy.engine as engine </span><span class="cx"> import sqlalchemy.util as util </span><span class="cx"> import sqlalchemy.attributes as attributes </span><ins>+import sync </ins><span class="cx"> import mapper </span><span class="cx"> import objectstore </span><span class="cx"> from sqlalchemy.exceptions import * </span><span class="lines">@@ -202,7 +203,15 @@ </span><span class="cx"> if self.backref is not None: </span><span class="cx"> # try to set a LazyLoader on our mapper referencing the parent mapper </span><span class="cx"> if not self.mapper.props.has_key(self.backref): </span><del>- self.mapper.add_property(self.backref, LazyLoader(self.parent, self.secondary, self.primaryjoin, self.secondaryjoin, backref=self.key, is_backref=True)); </del><ins>+ if self.secondaryjoin is not None: + # if setting up a backref to a many-to-many, reverse the order + # of the "primary" and "secondary" joins + pj = self.secondaryjoin + sj = self.primaryjoin + else: + pj = self.primaryjoin + sj = None + self.mapper.add_property(self.backref, LazyLoader(self.parent, self.secondary, pj, sj, backref=self.key, is_backref=True)); </ins><span class="cx"> else: </span><span class="cx"> # else set one of us as the "backreference" </span><span class="cx"> if not self.mapper.props[self.backref].is_backref: </span><span class="lines">@@ -525,71 +534,17 @@ </span><span class="cx"> The list of rules is used within commits by the _synchronize() method when dependent </span><span class="cx"> objects are processed.""" </span><span class="cx"> </span><del>- SyncRule = PropertyLoader.SyncRule - </del><span class="cx"> parent_tables = util.HashSet(self.parent.tables + [self.parent.primarytable]) </span><span class="cx"> target_tables = util.HashSet(self.mapper.tables + [self.mapper.primarytable]) </span><span class="cx"> </span><del>- 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): - """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 </del><ins>+ self.syncrules = sync.ClauseSynchronizer(self.parent, self.mapper, self.direction) + if self.direction == PropertyLoader.MANYTOMANY: + #print "COMPILING p/c", self.parent, self.mapper + self.syncrules.compile(self.primaryjoin, parent_tables, [self.secondary], False) + self.syncrules.compile(self.secondaryjoin, target_tables, [self.secondary], True) + else: + self.syncrules.compile(self.primaryjoin, parent_tables, target_tables) </ins><span class="cx"> </span><del>- 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 == PropertyLoader.ONETOMANY: - self.syncrules.append(SyncRule(self.parent, source, dest, dest_mapper=self.mapper)) - elif self.direction == PropertyLoader.MANYTOONE: - self.syncrules.append(SyncRule(self.mapper, source, dest, dest_mapper=self.parent)) - else: - raise AssertionError("assert failed") - else: - pt = check_for_table(binary, parent_tables) - tt = check_for_table(binary, target_tables) - st = check_for_table(binary, [self.secondary]) - #print "parenttable", [t.name for t in parent_tables] - #print "ttable", [t.name for t in target_tables] - #print "OK", str(binary), pt, tt, st - if pt and tt: - if self.direction == PropertyLoader.ONETOMANY: - self.syncrules.append(SyncRule(self.parent, pt, tt, dest_mapper=self.mapper)) - elif self.direction == PropertyLoader.MANYTOONE: - self.syncrules.append(SyncRule(self.mapper, tt, pt, dest_mapper=self.parent)) - else: - if visiting is self.primaryjoin: - self.syncrules.append(SyncRule(self.parent, pt, st, direction=PropertyLoader.ONETOMANY)) - else: - self.syncrules.append(SyncRule(self.mapper, tt, st, direction=PropertyLoader.MANYTOONE)) - elif pt and st: - self.syncrules.append(SyncRule(self.parent, pt, st, direction=PropertyLoader.ONETOMANY)) - elif tt and st: - self.syncrules.append(SyncRule(self.mapper, tt, st, direction=PropertyLoader.MANYTOONE)) - - self.syncrules = [] - processor = BinaryVisitor(compile) - visiting = self.primaryjoin - self.primaryjoin.accept_visitor(processor) - if self.secondaryjoin is not None: - visiting = self.secondaryjoin - self.secondaryjoin.accept_visitor(processor) - if len(self.syncrules) == 0: - raise ArgumentError("No syncrules generated for join criterion " + str(self.primaryjoin)) - </del><span class="cx"> def _synchronize(self, obj, child, associationrow, clearkeys): </span><span class="cx"> """called during a commit to execute the full list of syncrules on the </span><span class="cx"> given object/child/optional association row""" </span><span class="lines">@@ -606,54 +561,8 @@ </span><span class="cx"> if dest is None: </span><span class="cx"> return </span><span class="cx"> </span><del>- for rule in self.syncrules: - rule.execute(source, dest, obj, child, clearkeys) </del><ins>+ self.syncrules.execute(source, dest, obj, child, clearkeys) </ins><span class="cx"> </span><del>- 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, direction=None): - self.source_mapper = source_mapper - self.source_column = source_column - self.direction = direction - 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 self.direction is not None: - self.exec_many2many(dest, obj, child, clearkeys) - else: - self.exec_one2many(source, dest, clearkeys) - - def exec_many2many(self, destination, obj, child, clearkeys): - if self.direction == PropertyLoader.ONETOMANY: - source = obj - elif self.direction == PropertyLoader.MANYTOONE: - source = child - if clearkeys: - value = None - else: - value = self.source_mapper._getattrbycolumn(source, self.source_column) - destination[self.dest_column.key] = value - - def exec_one2many(self, source, destination, clearkeys): - if clearkeys or source is None: - value = None - else: - value = self.source_mapper._getattrbycolumn(source, self.source_column) - #print "SYNC VALUE", value, "TO", destination - self.dest_mapper._setattrbycolumn(destination, self.dest_column, value) - - </del><span class="cx"> class LazyLoader(PropertyLoader): </span><span class="cx"> def do_init_subclass(self, key, parent): </span><span class="cx"> (self.lazywhere, self.lazybinds) = create_lazy_clause(self.parent.table, self.primaryjoin, self.secondaryjoin, self.foreignkey) </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingsyncpy"></a> <div class="addfile"><h4>Added: sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py (1052 => 1053)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py 2006-02-26 21:39:14 UTC (rev 1052) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py 2006-02-26 21:45:10 UTC (rev 1053) </span><span class="lines">@@ -0,0 +1,121 @@ </span><ins>+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) + </ins></span></pre></div> <a id="sqlalchemytrunktestinheritancepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/inheritance.py (1052 => 1053)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/inheritance.py 2006-02-26 21:39:14 UTC (rev 1052) +++ sqlalchemy/trunk/test/inheritance.py 2006-02-26 21:45:10 UTC (rev 1053) </span><span class="lines">@@ -90,65 +90,67 @@ </span><span class="cx"> objectstore.commit() </span><span class="cx"> </span><span class="cx"> class InheritTest2(testbase.AssertMixin): </span><del>- def setUpAll(self): - engine = testbase.db - global foo, bar, foo_bar - foo = Table('foo', engine, - Column('id', Integer, primary_key=True), - Column('data', String(20)), - ).create() </del><ins>+ def setUpAll(self): + engine = testbase.db + global foo, bar, foo_bar + foo = Table('foo', engine, + Column('id', Integer, primary_key=True), + Column('data', String(20)), + ).create() </ins><span class="cx"> </span><del>- bar = Table('bar', engine, - Column('bid', Integer, ForeignKey('foo.id'), primary_key=True), - #Column('fid', Integer, ForeignKey('foo.id'), ) - ).create() </del><ins>+ bar = Table('bar', engine, + Column('bid', Integer, ForeignKey('foo.id'), primary_key=True), + #Column('fid', Integer, ForeignKey('foo.id'), ) + ).create() </ins><span class="cx"> </span><del>- foo_bar = Table('foo_bar', engine, - Column('foo_id', Integer, ForeignKey('foo.id')), - Column('bar_id', Integer, ForeignKey('bar.bid'))).create() </del><ins>+ foo_bar = Table('foo_bar', engine, + Column('foo_id', Integer, ForeignKey('foo.id')), + Column('bar_id', Integer, ForeignKey('bar.bid'))).create() </ins><span class="cx"> </span><del>- def tearDownAll(self): - foo_bar.drop() - bar.drop() - foo.drop() </del><ins>+ def tearDownAll(self): + foo_bar.drop() + bar.drop() + foo.drop() </ins><span class="cx"> </span><del>- def testbasic(self): - class Foo(object): - def __init__(self, data=None): - self.data = data - def __str__(self): - return "Foo(%s)" % self.data - def __repr__(self): - return str(self) </del><ins>+ def testbasic(self): + class Foo(object): + def __init__(self, data=None): + self.data = data + def __str__(self): + return "Foo(%s)" % self.data + def __repr__(self): + return str(self) </ins><span class="cx"> </span><del>- Foo.mapper = mapper(Foo, foo) - class Bar(Foo): - def __str__(self): - return "Bar(%s)" % self.data </del><ins>+ Foo.mapper = mapper(Foo, foo) + class Bar(Foo): + def __str__(self): + return "Bar(%s)" % self.data </ins><span class="cx"> </span><del>- Bar.mapper = mapper(Bar, bar, inherits=Foo.mapper, properties = { - # TODO: use syncrules for this - 'id':[bar.c.bid, foo.c.id] - }) </del><ins>+ Bar.mapper = mapper(Bar, bar, inherits=Foo.mapper, properties = { + # TODO: use syncrules for this + 'id':[bar.c.bid, foo.c.id] + }) </ins><span class="cx"> </span><del>- Bar.mapper.add_property('foos', relation(Foo.mapper, foo_bar, primaryjoin=bar.c.bid==foo_bar.c.bar_id, secondaryjoin=foo_bar.c.foo_id==foo.c.id, lazy=False)) - #Bar.mapper.add_property('foos', relation(Foo.mapper, foo_bar, lazy=False)) </del><ins>+ Bar.mapper.add_property('foos', relation(Foo.mapper, foo_bar, primaryjoin=bar.c.bid==foo_bar.c.bar_id, secondaryjoin=foo_bar.c.foo_id==foo.c.id, lazy=False)) + #Bar.mapper.add_property('foos', relation(Foo.mapper, foo_bar, lazy=False)) </ins><span class="cx"> </span><ins>+ b = Bar('barfoo') + objectstore.commit() </ins><span class="cx"> </span><del>- b = Bar('barfoo') - objectstore.commit() </del><ins>+ b.foos.append(Foo('subfoo1')) + b.foos.append(Foo('subfoo2')) </ins><span class="cx"> </span><ins>+ objectstore.commit() + objectstore.clear() </ins><span class="cx"> </span><del>- b.foos.append(Foo('subfoo1')) - b.foos.append(Foo('subfoo2')) </del><ins>+ l =b.mapper.select() + print l[0] + print l[0].foos + self.assert_result(l, Bar, +# {'id':1, 'data':'barfoo', 'bid':1, 'foos':(Foo, [{'id':2,'data':'subfoo1'}, {'id':3,'data':'subfoo2'}])}, + {'id':1, 'data':'barfoo', 'foos':(Foo, [{'id':2,'data':'subfoo1'}, {'id':3,'data':'subfoo2'}])}, + ) </ins><span class="cx"> </span><del>- objectstore.commit() - objectstore.clear() </del><span class="cx"> </span><del>- l =b.mapper.select() - print l[0] - print l[0].foos - - </del><span class="cx"> if __name__ == "__main__": </span><span class="cx"> testbase.main() </span></span></pre> </div> </div> </body> </html> |