[Sqlalchemy-commits] [1523] sqlalchemy/trunk/test: latest overhaul to association objects, plus an a
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-27 19:44:23
|
<!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>[1523] sqlalchemy/trunk/test: latest overhaul to association objects, plus an actual unit test</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1523</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-27 14:44:05 -0500 (Sat, 27 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>latest overhaul to association objects, plus an actual unit test this change probably fixes [ticket:134]</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyormdependencypy">sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormmapperpy">sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormunitofworkpy">sqlalchemy/trunk/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormuowdumperpy">sqlalchemy/trunk/lib/sqlalchemy/orm/uowdumper.py</a></li> <li><a href="#sqlalchemytrunktestalltestspy">sqlalchemy/trunk/test/alltests.py</a></li> <li><a href="#sqlalchemytrunktestrelationshipspy">sqlalchemy/trunk/test/relationships.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemytrunktestassociationpy">sqlalchemy/trunk/test/association.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyormdependencypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -9,7 +9,7 @@ </span><span class="cx"> together to allow processing of scalar- and list-based dependencies at flush time.""" </span><span class="cx"> </span><span class="cx"> from sync import ONETOMANY,MANYTOONE,MANYTOMANY </span><del>-from sqlalchemy import sql </del><ins>+from sqlalchemy import sql, util </ins><span class="cx"> </span><span class="cx"> def create_dependency_processor(key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): </span><span class="cx"> types = { </span><span class="lines">@@ -309,41 +309,53 @@ </span><span class="cx"> uowcommit.register_processor(stub, self, self.parent) </span><span class="cx"> def process_dependencies(self, task, deplist, uowcommit, delete = False): </span><span class="cx"> #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) </span><del>- # manage association objects. </del><span class="cx"> for obj in deplist: </span><span class="cx"> childlist = self.get_object_dependencies(obj, uowcommit, passive=True) </span><span class="cx"> if childlist is None: continue </span><span class="cx"> </span><del>- #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) </del><ins>+ # for the association mapper, the list of association objects is organized into a unique list based on the + # "primary key". newly added association items which correspond to existing association items are "merged" + # into the existing one by moving the "_instance_key" over to the added item, so instead of insert/delete you + # just get an update operation. + if not delete: + tosave = util.OrderedDict() + for child in childlist: + self._synchronize(obj, child, None, False) + key = self.mapper.instance_key(child) + tosave[key] = child + uowcommit.unregister_object(child) </ins><span class="cx"> </span><del>- 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 </del><ins>+ todelete = {} + for child in childlist.deleted_items(): + self._synchronize(obj, child, None, False) + key = self.mapper.instance_key(child) + if not tosave.has_key(key): + todelete[key] = child + else: + tosave[key]._instance_key = key </ins><span class="cx"> uowcommit.unregister_object(child) </span><del>- else: - #print "DELETE ASSOC OBJ", repr(child) - uowcommit.register_object(child, isdelete=True) </del><ins>+ + for child in childlist.unchanged_items(): + key = self.mapper.instance_key(child) + tosave[key]._instance_key = key + + #print "OK for the save", [(o, getattr(o, '_instance_key', None)) for o in tosave.values()] + #print "OK for the delete", [(o, getattr(o, '_instance_key', None)) for o in todelete.values()] + + for obj in tosave.values(): + uowcommit.register_object(obj) + for obj in todelete.values(): + uowcommit.register_object(obj, isdelete=True) + else: + todelete = {} + for child in childlist.unchanged_items() + childlist.deleted_items(): + self._synchronize(obj, child, None, False) + key = self.mapper.instance_key(child) + todelete[key] = child + for obj in todelete.values(): + uowcommit.register_object(obj, isdelete=True) + + </ins><span class="cx"> def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): </span><span class="cx"> # TODO: clean up the association step in process_dependencies and move the </span><span class="cx"> # appropriate sections of it to here </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -681,6 +681,7 @@ </span><span class="cx"> """called by a UnitOfWork object to delete objects, which involves a </span><span class="cx"> DELETE statement for each table used by this mapper, for each object in the list.""" </span><span class="cx"> connection = uow.transaction.connection(self) </span><ins>+ #print "DELETE_OBJ MAPPER", self.class_.__name__, objects </ins><span class="cx"> </span><span class="cx"> for table in util.reversed(self.tables): </span><span class="cx"> if not self._has_pks(table): </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/unitofwork.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/unitofwork.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/unitofwork.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -305,6 +305,7 @@ </span><span class="cx"> if mod: self._mark_modified() </span><span class="cx"> </span><span class="cx"> def unregister_object(self, obj): </span><ins>+ #print "UNREGISTER", obj </ins><span class="cx"> mapper = object_mapper(obj) </span><span class="cx"> task = self.get_task_by_mapper(mapper) </span><span class="cx"> if obj in task.objects: </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormuowdumperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/uowdumper.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/uowdumper.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/uowdumper.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -2,7 +2,7 @@ </span><span class="cx"> """dumps out a string representation of a UOWTask structure""" </span><span class="cx"> </span><span class="cx"> class UOWDumper(object): </span><del>- def __init__(self, task, buf, verbose=False): </del><ins>+ def __init__(self, task, buf, verbose=True): </ins><span class="cx"> self.verbose = verbose </span><span class="cx"> self.indent = 0 </span><span class="cx"> self.task = task </span></span></pre></div> <a id="sqlalchemytrunktestalltestspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/alltests.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/alltests.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/test/alltests.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -40,6 +40,7 @@ </span><span class="cx"> 'objectstore', </span><span class="cx"> 'cascade', </span><span class="cx"> 'relationships', </span><ins>+ 'association', </ins><span class="cx"> </span><span class="cx"> # cyclical ORM persistence </span><span class="cx"> 'cycles', </span><span class="lines">@@ -49,7 +50,7 @@ </span><span class="cx"> 'manytomany', </span><span class="cx"> 'onetoone', </span><span class="cx"> 'inheritance', </span><del>- 'polymorph', </del><ins>+ 'polymorph', </ins><span class="cx"> </span><span class="cx"> # extensions </span><span class="cx"> 'proxy_engine', </span></span></pre></div> <a id="sqlalchemytrunktestassociationpy"></a> <div class="addfile"><h4>Added: sqlalchemy/trunk/test/association.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/association.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/test/association.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -0,0 +1,143 @@ </span><ins>+import testbase + +from sqlalchemy import * + + +class AssociationTest(testbase.PersistTest): + def setUpAll(self): + global items, item_keywords, keywords, metadata, Item, Keyword, KeywordAssociation + metadata = BoundMetaData(testbase.db) + items = Table('items', metadata, + Column('item_id', Integer, primary_key=True), + Column('name', String(40)), + ) + item_keywords = Table('item_keywords', metadata, + Column('item_id', Integer, ForeignKey('items.item_id')), + Column('keyword_id', Integer, ForeignKey('keywords.keyword_id')), + Column('data', String(40)) + ) + keywords = Table('keywords', metadata, + Column('keyword_id', Integer, primary_key=True), + Column('name', String(40)) + ) + metadata.create_all() + + class Item(object): + def __init__(self, name): + self.name = name + def __repr__(self): + return "Item id=%d name=%s keywordassoc=%s" % (self.item_id, self.name, repr(self.keywords)) + class Keyword(object): + def __init__(self, name): + self.name = name + def __repr__(self): + return "Keyword id=%d name=%s" % (self.keyword_id, self.name) + class KeywordAssociation(object): + def __init__(self, keyword, data): + self.keyword = keyword + self.data = data + def __repr__(self): + return "KeywordAssociation itemid=%d keyword=%s data=%s" % (self.item_id, repr(self.keyword), self.data) + + mapper(Keyword, keywords) + mapper(KeywordAssociation, item_keywords, properties={ + 'keyword':relation(Keyword, lazy=False) + }, primary_key=[item_keywords.c.item_id, item_keywords.c.keyword_id], order_by=[item_keywords.c.data]) + mapper(Item, items, properties={ + 'keywords' : relation(KeywordAssociation, association=Keyword) + }) + + def tearDown(self): + for t in metadata.table_iterator(reverse=True): + t.delete().execute() + def tearDownAll(self): + clear_mappers() + metadata.drop_all() + + def testinsert(self): + sess = create_session() + item1 = Item('item1') + item2 = Item('item2') + item1.keywords.append(KeywordAssociation(Keyword('blue'), 'blue_assoc')) + item1.keywords.append(KeywordAssociation(Keyword('red'), 'red_assoc')) + item2.keywords.append(KeywordAssociation(Keyword('green'), 'green_assoc')) + sess.save(item1) + sess.save(item2) + sess.flush() + saved = repr([item1, item2]) + sess.clear() + l = sess.query(Item).select() + loaded = repr(l) + print saved + print loaded + self.assert_(saved == loaded) + + def testreplace(self): + sess = create_session() + item1 = Item('item1') + item1.keywords.append(KeywordAssociation(Keyword('blue'), 'blue_assoc')) + item1.keywords.append(KeywordAssociation(Keyword('red'), 'red_assoc')) + sess.save(item1) + sess.flush() + + red_keyword = item1.keywords[1].keyword + del item1.keywords[1] + item1.keywords.append(KeywordAssociation(red_keyword, 'new_red_assoc')) + sess.flush() + saved = repr([item1]) + sess.clear() + l = sess.query(Item).select() + loaded = repr(l) + print saved + print loaded + self.assert_(saved == loaded) + + def testmodify(self): + sess = create_session() + item1 = Item('item1') + item2 = Item('item2') + item1.keywords.append(KeywordAssociation(Keyword('blue'), 'blue_assoc')) + item1.keywords.append(KeywordAssociation(Keyword('red'), 'red_assoc')) + item2.keywords.append(KeywordAssociation(Keyword('green'), 'green_assoc')) + sess.save(item1) + sess.save(item2) + sess.flush() + + red_keyword = item1.keywords[1].keyword + del item1.keywords[0] + del item1.keywords[0] + purple_keyword = Keyword('purple') + item1.keywords.append(KeywordAssociation(red_keyword, 'new_red_assoc')) + item2.keywords.append(KeywordAssociation(purple_keyword, 'purple_item2_assoc')) + item1.keywords.append(KeywordAssociation(purple_keyword, 'purple_item1_assoc')) + item1.keywords.append(KeywordAssociation(Keyword('yellow'), 'yellow_assoc')) + + sess.flush() + saved = repr([item1, item2]) + sess.clear() + l = sess.query(Item).select() + loaded = repr(l) + print saved + print loaded + self.assert_(saved == loaded) + + def testdelete(self): + sess = create_session() + item1 = Item('item1') + item2 = Item('item2') + item1.keywords.append(KeywordAssociation(Keyword('blue'), 'blue_assoc')) + item1.keywords.append(KeywordAssociation(Keyword('red'), 'red_assoc')) + item2.keywords.append(KeywordAssociation(Keyword('green'), 'green_assoc')) + sess.save(item1) + sess.save(item2) + sess.flush() + self.assert_(item_keywords.count().scalar() == 3) + + sess.delete(item1) + sess.delete(item2) + sess.flush() + self.assert_(item_keywords.count().scalar() == 0) + + +if __name__ == "__main__": + testbase.main() </ins></span></pre></div> <a id="sqlalchemytrunktestrelationshipspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/relationships.py (1522 => 1523)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/relationships.py 2006-05-27 17:51:30 UTC (rev 1522) +++ sqlalchemy/trunk/test/relationships.py 2006-05-27 19:44:05 UTC (rev 1523) </span><span class="lines">@@ -1,5 +1,3 @@ </span><del>-"""Test complex relationships""" - </del><span class="cx"> import testbase </span><span class="cx"> import unittest, sys, datetime </span><span class="cx"> </span></span></pre> </div> </div> </body> </html> |