diff options
author | Jason Kirtland <jek@discorporate.us> | 2008-01-05 19:11:58 +0000 |
---|---|---|
committer | Jason Kirtland <jek@discorporate.us> | 2008-01-05 19:11:58 +0000 |
commit | 2bc1c28c44ff6e5ccbca793123488919864dcce3 (patch) | |
tree | b3074969fd79a15577103440537245cd157856bf | |
parent | f9fd5bfb8693c7ea877fd09ec4fd2c042eb6a689 (diff) | |
download | sqlalchemy-2bc1c28c44ff6e5ccbca793123488919864dcce3.tar.gz |
More overloads: fix cascades for += on a list relation, added operator support to association proxied lists.
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/associationproxy.py | 31 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/collections.py | 15 | ||||
-rw-r--r-- | test/ext/associationproxy.py | 38 | ||||
-rw-r--r-- | test/orm/collection.py | 25 |
5 files changed, 113 insertions, 0 deletions
@@ -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 |