[Sqlalchemy-commits] [2445] sqlalchemy/trunk: - improved/ fixed custom collection classes when givi
Brought to you by:
zzzeek
From: <co...@sq...> - 2007-03-26 20:00:28
|
<!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><meta http-equiv="content-type" content="text/html; charset=utf-8" /><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, #header, #footer { 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; } #header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; } #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>[2445] sqlalchemy/trunk: - improved/ fixed custom collection classes when giving it "set"/</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>2445</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2007-03-26 15:59:39 -0400 (Mon, 26 Mar 2007)</dd> </dl> <h3>Log Message</h3> <pre>- improved/fixed custom collection classes when giving it "set"/ "sets.Set" classes or subclasses (was still looking for append() methods on them during lazy loads) - moved CustomCollectionsTest from unitofwork to relationships - added more custom collections test to attributes module</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunkCHANGES">sqlalchemy/trunk/CHANGES</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormattributespy">sqlalchemy/trunk/lib/sqlalchemy/orm/attributes.py</a></li> <li><a href="#sqlalchemytrunktestormattributespy">sqlalchemy/trunk/test/orm/attributes.py</a></li> <li><a href="#sqlalchemytrunktestormrelationshipspy">sqlalchemy/trunk/test/orm/relationships.py</a></li> <li><a href="#sqlalchemytrunktestormunitofworkpy">sqlalchemy/trunk/test/orm/unitofwork.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunkCHANGES"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/CHANGES (2444 => 2445)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/CHANGES 2007-03-24 19:24:27 UTC (rev 2444) +++ sqlalchemy/trunk/CHANGES 2007-03-26 19:59:39 UTC (rev 2445) </span><span class="lines">@@ -6,7 +6,11 @@ </span><span class="cx"> on postgres. Also, the true labelname is always attached as the </span><span class="cx"> accessor on the parent Selectable so theres no need to be aware </span><span class="cx"> of the genrerated label names [ticket:512]. </span><del>- </del><ins>+- orm: + - improved/fixed custom collection classes when giving it "set"/ + "sets.Set" classes or subclasses (was still looking for append() + methods on them during lazy loads) + </ins><span class="cx"> 0.3.6 </span><span class="cx"> - sql: </span><span class="cx"> - bindparam() names are now repeatable! specify two </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/attributes.py (2444 => 2445)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/attributes.py 2007-03-24 19:24:27 UTC (rev 2444) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/attributes.py 2007-03-26 19:59:39 UTC (rev 2445) </span><span class="lines">@@ -162,15 +162,6 @@ </span><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> </span><del>- def _adapt_list(self, data): - if self.typecallable is not None: - t = self.typecallable() - if data is not None: - [t.append(x) for x in data] - return t - else: - return data - </del><span class="cx"> def initialize(self, obj): </span><span class="cx"> """Initialize this attribute on the given object instance. </span><span class="cx"> </span><span class="lines">@@ -215,7 +206,7 @@ </span><span class="cx"> return InstrumentedAttribute.PASSIVE_NORESULT </span><span class="cx"> self.logger.debug("Executing lazy callable on %s.%s" % (orm_util.instance_str(obj), self.key)) </span><span class="cx"> values = callable_() </span><del>- l = InstrumentedList(self, obj, self._adapt_list(values), init=False) </del><ins>+ l = InstrumentedList(self, obj, values, init=False) </ins><span class="cx"> </span><span class="cx"> # if a callable was executed, then its part of the "committed state" </span><span class="cx"> # if any, so commit the newly loaded data </span><span class="lines">@@ -362,6 +353,7 @@ </span><span class="cx"> </span><span class="cx"> InstrumentedAttribute.logger = logging.class_logger(InstrumentedAttribute) </span><span class="cx"> </span><ins>+ </ins><span class="cx"> class InstrumentedList(object): </span><span class="cx"> """Instrument a list-based attribute. </span><span class="cx"> </span><span class="lines">@@ -388,22 +380,43 @@ </span><span class="cx"> # and the list attribute, which interferes with immediate garbage collection. </span><span class="cx"> self.__obj = weakref.ref(obj) </span><span class="cx"> self.key = attr.key </span><del>- self.data = data or attr._blank_list() </del><span class="cx"> </span><span class="cx"> # adapt to lists or sets </span><span class="cx"> # TODO: make three subclasses of InstrumentedList that come off from a </span><span class="cx"> # metaclass, based on the type of data sent in </span><del>- if hasattr(self.data, 'append'): </del><ins>+ if attr.typecallable is not None: + self.data = attr.typecallable() + else: + self.data = data or attr._blank_list() + + if isinstance(self.data, list): </ins><span class="cx"> self._data_appender = self.data.append </span><span class="cx"> self._clear_data = self._clear_list </span><del>- elif hasattr(self.data, 'add'): </del><ins>+ elif isinstance(self.data, util.Set): </ins><span class="cx"> self._data_appender = self.data.add </span><span class="cx"> self._clear_data = self._clear_set </span><ins>+ elif isinstance(self.data, dict): + if not hasattr(self.data, 'append'): + raise exceptions.ArgumentError("Dictionary collection class '%s' must implement an append() method" % type(self.data).__name__) + self._clear_data = self._clear_dict </ins><span class="cx"> else: </span><del>- raise exceptions.ArgumentError("Collection type " + repr(type(self.data)) + " has no append() or add() method") - if isinstance(self.data, dict): - self._clear_data = self._clear_dict </del><ins>+ if hasattr(self.data, 'append'): + self._data_appender = self.data.append + elif hasattr(self.data, 'add'): + self._data_appender = self.data.add + else: + raise exceptions.ArgumentError("Collection class '%s' is not of type 'list', 'set', or 'dict' and has no append() or add() method" % type(self.data).__name__) </ins><span class="cx"> </span><ins>+ if hasattr(self.data, 'clear'): + self._clear_data = self._clear_set + else: + raise exceptions.ArgumentError("Collection class '%s' is not of type 'list', 'set', or 'dict' and has no clear() method" % type(self.data).__name__) + + if data is not None and data is not self.data: + for elem in data: + self._data_appender(elem) + + </ins><span class="cx"> if init: </span><span class="cx"> for x in self.data: </span><span class="cx"> self.__setrecord(x) </span><span class="lines">@@ -475,7 +488,7 @@ </span><span class="cx"> return repr(self.data) </span><span class="cx"> </span><span class="cx"> def __getattr__(self, attr): </span><del>- """Proxie unknown methods and attributes to the underlying </del><ins>+ """Proxy unknown methods and attributes to the underlying </ins><span class="cx"> data array. This allows custom list classes to be used. </span><span class="cx"> """ </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunktestormattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/attributes.py (2444 => 2445)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/attributes.py 2007-03-24 19:24:27 UTC (rev 2444) +++ sqlalchemy/trunk/test/orm/attributes.py 2007-03-26 19:59:39 UTC (rev 2445) </span><span class="lines">@@ -1,10 +1,11 @@ </span><span class="cx"> from testbase import PersistTest </span><span class="cx"> import sqlalchemy.util as util </span><span class="cx"> import sqlalchemy.orm.attributes as attributes </span><ins>+from sqlalchemy import exceptions </ins><span class="cx"> import unittest, sys, os </span><span class="cx"> import pickle </span><ins>+import testbase </ins><span class="cx"> </span><del>- </del><span class="cx"> class MyTest(object):pass </span><span class="cx"> class MyTest2(object):pass </span><span class="cx"> </span><span class="lines">@@ -328,6 +329,49 @@ </span><span class="cx"> </span><span class="cx"> manager = attributes.AttributeManager() </span><span class="cx"> manager.reset_class_managed(Foo) </span><ins>+ + def testcollectionclasses(self): + manager = attributes.AttributeManager() + class Foo(object):pass + manager.register_attribute(Foo, "collection", uselist=True, typecallable=set) + assert isinstance(Foo().collection.data, set) </ins><span class="cx"> </span><ins>+ manager.register_attribute(Foo, "collection", uselist=True, typecallable=dict) + try: + Foo().collection + assert False + except exceptions.ArgumentError, e: + assert str(e) == "Dictionary collection class 'dict' must implement an append() method" + + class MyDict(dict): + def append(self, item): + self[item.foo] = item + manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyDict) + assert isinstance(Foo().collection.data, MyDict) + + class MyColl(object):pass + manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl) + try: + Foo().collection + assert False + except exceptions.ArgumentError, e: + assert str(e) == "Collection class 'MyColl' is not of type 'list', 'set', or 'dict' and has no append() or add() method" + + class MyColl(object): + def __iter__(self): + return iter([]) + def append(self, item): + pass + manager.register_attribute(Foo, "collection", uselist=True, typecallable=MyColl) + try: + Foo().collection + assert False + except exceptions.ArgumentError, e: + assert str(e) == "Collection class 'MyColl' is not of type 'list', 'set', or 'dict' and has no clear() method" + + def foo(self):pass + MyColl.clear = foo + assert isinstance(Foo().collection.data, MyColl) + </ins><span class="cx"> if __name__ == "__main__": </span><del>- unittest.main() </del><ins>+ testbase.main() </ins></span></pre></div> <a id="sqlalchemytrunktestormrelationshipspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/relationships.py (2444 => 2445)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/relationships.py 2007-03-24 19:24:27 UTC (rev 2444) +++ sqlalchemy/trunk/test/orm/relationships.py 2007-03-26 19:59:39 UTC (rev 2445) </span><span class="lines">@@ -673,6 +673,50 @@ </span><span class="cx"> except exceptions.AssertionError, err: </span><span class="cx"> assert str(err) == "Attribute 'a' on class '%s' doesn't handle objects of type '%s'" % (D, B) </span><span class="cx"> </span><ins>+class CustomCollectionsTest(testbase.ORMTest): + def define_tables(self, metadata): + global sometable, someothertable + sometable = Table('sometable', metadata, + Column('col1',Integer, primary_key=True), + Column('data', String(30))) + someothertable = Table('someothertable', metadata, + Column('col1', Integer, primary_key=True), + Column('scol1', Integer, ForeignKey(sometable.c.col1)), + Column('data', String(20)) + ) + def testbasic(self): + class MyList(list): + pass + class Foo(object): + pass + class Bar(object): + pass + mapper(Foo, sometable, properties={ + 'bars':relation(Bar, collection_class=MyList) + }) + mapper(Bar, someothertable) + f = Foo() + assert isinstance(f.bars.data, MyList) + def testlazyload(self): + """test that a 'set' can be used as a collection and can lazyload.""" + class Foo(object): + pass + class Bar(object): + pass + mapper(Foo, sometable, properties={ + 'bars':relation(Bar, collection_class=set) + }) + mapper(Bar, someothertable) + f = Foo() + f.bars.add(Bar()) + f.bars.add(Bar()) + sess = create_session() + sess.save(f) + sess.flush() + sess.clear() + f = sess.query(Foo).get(f.col1) + assert len(list(f.bars)) == 2 + </ins><span class="cx"> class ViewOnlyTest(testbase.ORMTest): </span><span class="cx"> """test a view_only mapping where a third table is pulled into the primary join condition, </span><span class="cx"> using overlapping PK column names (should not produce "conflicting column" error)""" </span></span></pre></div> <a id="sqlalchemytrunktestormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/unitofwork.py (2444 => 2445)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/unitofwork.py 2007-03-24 19:24:27 UTC (rev 2444) +++ sqlalchemy/trunk/test/orm/unitofwork.py 2007-03-26 19:59:39 UTC (rev 2445) </span><span class="lines">@@ -55,33 +55,6 @@ </span><span class="cx"> u = s.query(m).select()[0] </span><span class="cx"> print u.addresses[0].user </span><span class="cx"> </span><del>-class CustomCollectionsTest(UnitOfWorkTest): - def setUpAll(self): - UnitOfWorkTest.setUpAll(self) - global sometable, metadata, someothertable - metadata = BoundMetaData(testbase.db) - sometable = Table('sometable', metadata, - Column('col1',Integer, primary_key=True)) - someothertable = Table('someothertable', metadata, - Column('col1', Integer, primary_key=True), - Column('scol1', Integer, ForeignKey(sometable.c.col1)), - Column('data', String(20)) - ) - def testbasic(self): - class MyList(list): - pass - class Foo(object): - pass - class Bar(object): - pass - mapper(Foo, sometable, properties={ - 'bars':relation(Bar, collection_class=MyList) - }) - mapper(Bar, someothertable) - f = Foo() - assert isinstance(f.bars.data, MyList) - def tearDownAll(self): - UnitOfWorkTest.tearDownAll(self) </del><span class="cx"> </span><span class="cx"> class VersioningTest(UnitOfWorkTest): </span><span class="cx"> def setUpAll(self): </span></span></pre> </div> </div> </body> </html> |