From: Jason Kirtland Date: Wed, 25 Jul 2007 22:02:49 +0000 (+0000) Subject: Really finish proxied list methods. Either these last couple (count, remove, setitem... X-Git-Tag: rel_0_4_6~23 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1ef7de461b9708e6d41aa21240e37fcf245447d5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Really finish proxied list methods. Either these last couple (count, remove, setitem w/ slice) weren't possible in 0.3 and/or I spaced these. Improved messaging on flubbed stepped slice assignment in collection decorators. --- diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index 0330a679f8..2dd8072228 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -6,10 +6,10 @@ transparent proxied access to the endpoint of an association object. See the example ``examples/association/proxied_association.py``. """ +import weakref, itertools import sqlalchemy.exceptions as exceptions import sqlalchemy.orm as orm import sqlalchemy.util as util -import weakref def association_proxy(targetcollection, attr, **kw): """Convenience function for use in mapped classes. Implements a Python @@ -260,7 +260,33 @@ class _AssociationList(object): return self._get(self.col[index]) def __setitem__(self, index, value): - self._set(self.col[index], value) + if not isinstance(index, slice): + self._set(self.col[index], value) + else: + if index.stop is None: + stop = len(self) + elif index.stop < 0: + stop = len(self) + index.stop + else: + stop = index.stop + step = index.step or 1 + + rng = range(index.start or 0, stop, step) + if step == 1: + for i in rng: + del self[index.start] + i = index.start + for item in value: + self.insert(i, item) + i += 1 + else: + if len(value) != len(rng): + raise ValueError( + "attempt to assign sequence of size %s to " + "extended slice of size %s" % (len(value), + len(rng))) + for i, item in zip(rng, value): + self._set(self.col[i], item) def __delitem__(self, index): del self.col[index] @@ -282,9 +308,13 @@ class _AssociationList(object): del self.col[start:end] def __iter__(self): - """Iterate over proxied values. For the actual domain objects, - iterate over .col instead or just use the underlying collection - directly from its property on the parent.""" + """Iterate over proxied values. + + For the actual domain objects, iterate over .col instead or + just use the underlying collection directly from its property + on the parent. + """ + for member in self.col: yield self._get(member) raise StopIteration @@ -295,6 +325,10 @@ class _AssociationList(object): item = self._create(value, **kw) self.col.append(item) + def count(self, value): + return sum([1 for _ in + itertools.ifilter(lambda v: v == value, iter(self))]) + def extend(self, values): for v in values: self.append(v) @@ -305,6 +339,23 @@ class _AssociationList(object): def pop(self, index=-1): return self.getter(self.col.pop(index)) + def remove(self, value): + for i, val in enumerate(self): + if val == value: + del self.col[i] + return + raise ValueError("value not in list") + + def reverse(self): + """Not supported, use reversed(mylist)""" + + raise NotImplementedError + + def sort(self): + """Not supported, use sorted(mylist)""" + + raise NotImplementedError + def clear(self): del self.col[0:len(self.col)] diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index f51ea3b31d..7ade882f5c 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -834,7 +834,10 @@ def _list_decorators(): i += 1 else: if len(value) != len(rng): - raise ValueError + raise ValueError( + "attempt to assign sequence of size %s to " + "extended slice of size %s" % (len(value), + len(rng))) for i, item in zip(rng, value): self.__setitem__(i, item) _tidy(__setitem__) diff --git a/test/ext/associationproxy.py b/test/ext/associationproxy.py index 60362501e0..f602871c2c 100644 --- a/test/ext/associationproxy.py +++ b/test/ext/associationproxy.py @@ -149,9 +149,43 @@ class _CollectionOperations(PersistTest): self.assert_(p1.children[1] == 'changed-in-place') assert p1._children[1].id == inplace_id + p1.children.append('changed-in-place') + self.assert_(p1.children.count('changed-in-place') == 2) + + p1.children.remove('changed-in-place') + self.assert_(p1.children.count('changed-in-place') == 1) + + p1 = self.roundtrip(p1) + self.assert_(p1.children.count('changed-in-place') == 1) + p1._children = [] self.assert_(len(p1.children) == 0) + after = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] + p1.children = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] + self.assert_(len(p1.children) == 10) + self.assert_([c.name for c in p1._children] == after) + + p1.children[2:6] = ['x'] * 4 + after = ['a', 'b', 'x', 'x', 'x', 'x', 'g', 'h', 'i', 'j'] + self.assert_(p1.children == after) + self.assert_([c.name for c in p1._children] == after) + + p1.children[2:6] = ['y'] + after = ['a', 'b', 'y', 'g', 'h', 'i', 'j'] + self.assert_(p1.children == after) + self.assert_([c.name for c in p1._children] == after) + + p1.children[2:3] = ['z'] * 4 + after = ['a', 'b', 'z', 'z', 'z', 'z', 'g', 'h', 'i', 'j'] + self.assert_(p1.children == after) + self.assert_([c.name for c in p1._children] == after) + + p1.children[2::2] = ['O'] * 4 + after = ['a', 'b', 'O', 'z', 'O', 'z', 'O', 'h', 'O', 'j'] + self.assert_(p1.children == after) + self.assert_([c.name for c in p1._children] == after) + class DefaultTest(_CollectionOperations): def __init__(self, *args, **kw): super(DefaultTest, self).__init__(*args, **kw) @@ -289,7 +323,8 @@ class SetTest(_CollectionOperations): self.assert_(len(p1.children) == 2) self.assert_(len(p1._children) == 2) - self.assert_(set([o.name for o in p1._children]) == set(['regular', 'proxied'])) + self.assert_(set([o.name for o in p1._children]) == + set(['regular', 'proxied'])) ch2 = None for o in p1._children: