summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Kirtland <jek@discorporate.us>2008-01-05 19:11:58 +0000
committerJason Kirtland <jek@discorporate.us>2008-01-05 19:11:58 +0000
commit2bc1c28c44ff6e5ccbca793123488919864dcce3 (patch)
treeb3074969fd79a15577103440537245cd157856bf
parentf9fd5bfb8693c7ea877fd09ec4fd2c042eb6a689 (diff)
downloadsqlalchemy-2bc1c28c44ff6e5ccbca793123488919864dcce3.tar.gz
More overloads: fix cascades for += on a list relation, added operator support to association proxied lists.
-rw-r--r--CHANGES4
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py31
-rw-r--r--lib/sqlalchemy/orm/collections.py15
-rw-r--r--test/ext/associationproxy.py38
-rw-r--r--test/orm/collection.py25
5 files changed, 113 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
index ea84b24c0..f63c50a42 100644
--- a/CHANGES
+++ b/CHANGES
@@ -5,7 +5,11 @@ CHANGES
0.4.2b
------
+- orm
+ - Fixed cascades on a += assignment to a list-based relation.
+- ext
+ - '+', '*', '+=' and '*=' support for association proxied lists.
0.4.2a
------
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index c5a2b4d07..fefc289f8 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -402,6 +402,37 @@ class _AssociationList(object):
def __ge__(self, other): return list(self) >= other
def __cmp__(self, other): return cmp(list(self), other)
+ def __add__(self, iterable):
+ try:
+ other = list(iterable)
+ except TypeError:
+ return NotImplemented
+ return list(self) + other
+ __radd__ = __add__
+
+ def __mul__(self, n):
+ if not isinstance(n, int):
+ return NotImplemented
+ return list(self) * n
+ __rmul__ = __mul__
+
+ def __iadd__(self, iterable):
+ self.extend(iterable)
+ return self
+
+ def __imul__(self, n):
+ # unlike a regular list *=, proxied __imul__ will generate unique
+ # backing objects for each copy. *= on proxied lists is a bit of
+ # a stretch anyhow, and this interpretation of the __imul__ contract
+ # is more plausibly useful than copying the backing objects.
+ if not isinstance(n, int):
+ return NotImplemented
+ if n == 0:
+ self.clear()
+ elif n > 1:
+ self.extend(list(self) * (n - 1))
+ return self
+
def copy(self):
return list(self)
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 106601640..c63c4dc8c 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -968,6 +968,16 @@ def _list_decorators():
_tidy(extend)
return extend
+ def __iadd__(fn):
+ def __iadd__(self, iterable):
+ # list.__iadd__ takes any iterable and seems to let TypeError raise
+ # as-is instead of returning NotImplemented
+ for value in iterable:
+ self.append(value)
+ return self
+ _tidy(__iadd__)
+ return __iadd__
+
def pop(fn):
def pop(self, index=-1):
__before_delete(self)
@@ -977,6 +987,11 @@ def _list_decorators():
_tidy(pop)
return pop
+ # __imul__ : not wrapping this. all members of the collection are already
+ # present, so no need to fire appends... wrapping it with an explicit
+ # decorator is still possible, so events on *= can be had if they're
+ # desired. hard to imagine a use case for __imul__, though.
+
l = locals().copy()
l.pop('_tidy')
return l
diff --git a/test/ext/associationproxy.py b/test/ext/associationproxy.py
index b3ce69a97..cc4020339 100644
--- a/test/ext/associationproxy.py
+++ b/test/ext/associationproxy.py
@@ -189,6 +189,44 @@ class _CollectionOperations(PersistTest):
self.assertRaises(TypeError, set, [p1.children])
+ p1.children *= 0
+ after = []
+ self.assert_(p1.children == after)
+ self.assert_([c.name for c in p1._children] == after)
+
+ p1.children += ['a', 'b']
+ after = ['a', 'b']
+ self.assert_(p1.children == after)
+ self.assert_([c.name for c in p1._children] == after)
+
+ p1.children *= 1
+ after = ['a', 'b']
+ self.assert_(p1.children == after)
+ self.assert_([c.name for c in p1._children] == after)
+
+ p1.children *= 2
+ after = ['a', 'b', 'a', 'b']
+ self.assert_(p1.children == after)
+ self.assert_([c.name for c in p1._children] == after)
+
+ p1.children = ['a']
+ after = ['a']
+ self.assert_(p1.children == after)
+ self.assert_([c.name for c in p1._children] == after)
+
+ self.assert_((p1.children * 2) == ['a', 'a'])
+ self.assert_((2 * p1.children) == ['a', 'a'])
+ self.assert_((p1.children * 0) == [])
+ self.assert_((0 * p1.children) == [])
+
+ self.assert_((p1.children + ['a']) == ['a', 'a'])
+ self.assert_((['a'] + p1.children) == ['a', 'a'])
+
+ try:
+ p1.children + 123
+ assert False
+ except TypeError:
+ assert True
class DefaultTest(_CollectionOperations):
def __init__(self, *args, **kw):
diff --git a/test/orm/collection.py b/test/orm/collection.py
index fb4dbf199..60a0a240f 100644
--- a/test/orm/collection.py
+++ b/test/orm/collection.py
@@ -230,6 +230,31 @@ class CollectionsTest(PersistTest):
control.extend(values)
assert_eq()
+ if hasattr(direct, '__iadd__'):
+ values = [creator(), creator(), creator()]
+
+ direct += values
+ control += values
+ assert_eq()
+
+ direct += []
+ control += []
+ assert_eq()
+
+ values = [creator(), creator()]
+ obj.attr += values
+ control += values
+ assert_eq()
+
+ if hasattr(direct, '__imul__'):
+ direct *= 2
+ control *= 2
+ assert_eq()
+
+ obj.attr *= 2
+ control *= 2
+ assert_eq()
+
def _test_list_bulk(self, typecallable, creator=entity_maker):
class Foo(object):
pass