]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Adjusted inplace-binops on set-based collections and association proxies to
authorJason Kirtland <jek@discorporate.us>
Mon, 5 May 2008 21:33:29 +0000 (21:33 +0000)
committerJason Kirtland <jek@discorporate.us>
Mon, 5 May 2008 21:33:29 +0000 (21:33 +0000)
more closely follow builtin (2.4+) set semantics.  Formerly any set duck-type
was accepted, now only types or subtypes of set, frozenset or the collection
type itself are accepted.

CHANGES
lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/orm/collections.py
test/ext/associationproxy.py
test/orm/collection.py

diff --git a/CHANGES b/CHANGES
index 9fe24ca815c705c921a3199bca778c2724dde444..f69cecf5a91f5f656e522829891f1b4c667f9c17 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -60,6 +60,11 @@ CHANGES
     - Fixed duplicate append event emission on repeated
       instrumented set.add() operations.
 
+    - set-based collections |=, -=, ^= and &= are stricter about
+      their operands and only operate on sets, frozensets or
+      subclasses of the collection type. Previously, they would
+      accept any duck-typed set.
+
 - declarative extension
     - Joined table inheritance mappers use a slightly relaxed
       function to create the "inherit condition" to the parent
@@ -94,6 +99,12 @@ CHANGES
       the databsae state cleanup (eg. issuing a rollback()) when
       connections are returned to the pool.
 
+-ext
+    - set-based association proxies |=, -=, ^= and &= are
+      stricter about their operands and only operate on sets,
+      frozensets or other association proxies. Previously, they
+      would accept any duck-typed set.
+
 - mssql
     - Added "odbc_autotranslate" parameter to engine / dburi
       parameters. Any given string will be passed through to the
index bcbfb478004a2f464a0774e76709581c6d3a33f6..d878f7b9b002961234cd89756100e28330263df8 100644 (file)
@@ -6,10 +6,12 @@ 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 itertools
+import weakref
+from sqlalchemy import exceptions
+from sqlalchemy import orm
+from sqlalchemy import util
+from sqlalchemy.orm import collections
 
 
 def association_proxy(targetcollection, attr, **kw):
@@ -702,7 +704,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __ior__(self, other):
-        if util.duck_type_collection(other) is not util.Set:
+        if not collections._set_binops_check_strict(self, other):
             return NotImplemented
         for value in other:
             self.add(value)
@@ -726,7 +728,7 @@ class _AssociationSet(object):
             self.discard(value)
 
     def __isub__(self, other):
-        if util.duck_type_collection(other) is not util.Set:
+        if not collections._set_binops_check_strict(self, other):
             return NotImplemented
         for value in other:
             self.discard(value)
@@ -748,7 +750,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __iand__(self, other):
-        if util.duck_type_collection(other) is not util.Set:
+        if not collections._set_binops_check_strict(self, other):
             return NotImplemented
         want, have = self.intersection(other), util.Set(self)
 
@@ -776,7 +778,7 @@ class _AssociationSet(object):
             self.add(value)
 
     def __ixor__(self, other):
-        if util.duck_type_collection(other) is not util.Set:
+        if not collections._set_binops_check_strict(self, other):
             return NotImplemented
         want, have = self.symmetric_difference(other), util.Set(self)
 
index 8067a030f7bf9a7629d2a43e144e563b3abf2371..c8fc2f189a3758bebbfb5c414bf8acad7a51b972 100644 (file)
@@ -95,7 +95,11 @@ The owning object and InstrumentedCollectionAttribute are also reachable
 through the adapter, allowing for some very sophisticated behavior.
 """
 
-import copy, inspect, sys, weakref
+import copy
+import inspect
+import sets
+import sys
+import weakref
 
 from sqlalchemy import exceptions, schema, util as sautil
 from sqlalchemy.util import attrgetter, Set
@@ -1136,6 +1140,22 @@ def _dict_decorators():
     l.pop('Unspecified')
     return l
 
+
+try:
+    _set_binop_bases = (set, frozenset, sets.BaseSet)
+except NameError:
+    _set_binop_bases = (sets.BaseSet,)
+
+def _set_binops_check_strict(self, obj):
+    """Allow only set, frozenset and self.__class__-derived objects in binops."""
+    return isinstance(obj, _set_binop_bases + (self.__class__,))
+
+def _set_binops_check_loose(self, obj):
+    """Allow anything set-like to participate in set binops."""
+    return (isinstance(obj, _set_binop_bases + (self.__class__,)) or
+            sautil.duck_type_collection(obj) == sautil.Set)
+
+
 def _set_decorators():
     """Hand-turned instrumentation wrappers that can decorate any set-like
     sequence class."""
@@ -1208,7 +1228,7 @@ def _set_decorators():
 
     def __ior__(fn):
         def __ior__(self, value):
-            if sautil.duck_type_collection(value) is not Set:
+            if not _set_binops_check_strict(self, value):
                 return NotImplemented
             for item in value:
                 self.add(item)
@@ -1225,7 +1245,7 @@ def _set_decorators():
 
     def __isub__(fn):
         def __isub__(self, value):
-            if sautil.duck_type_collection(value) is not Set:
+            if not _set_binops_check_strict(self, value):
                 return NotImplemented
             for item in value:
                 self.discard(item)
@@ -1247,7 +1267,7 @@ def _set_decorators():
 
     def __iand__(fn):
         def __iand__(self, other):
-            if sautil.duck_type_collection(other) is not Set:
+            if not _set_binops_check_strict(self, other):
                 return NotImplemented
             want, have = self.intersection(other), Set(self)
             remove, add = have - want, want - have
@@ -1274,7 +1294,7 @@ def _set_decorators():
 
     def __ixor__(fn):
         def __ixor__(self, other):
-            if sautil.duck_type_collection(other) is not Set:
+            if not _set_binops_check_strict(self, other):
                 return NotImplemented
             want, have = self.symmetric_difference(other), Set(self)
             remove, add = have - want, want - have
index e9540fa2db5c1213e9488bf002e8aca695b185f7..8837b4d04cb0af0a7626c610a636d3824ee2c597 100644 (file)
@@ -534,6 +534,7 @@ class SetTest(_CollectionOperations):
                 for other in (set(['a','b','c']), set(['a','b','c','d']),
                               set(['a']), set(['a','b']),
                               set(['c','d']), set(['e', 'f', 'g']),
+                              frozenset(['e', 'f', 'g']),
                               set()):
                     p = Parent('p')
                     p.children = base[:]
index 708ba9c744aa2c1e4a738b35a38a2f5b688f06da..711dc730ba35a817aadfaf36a5110f43de795873 100644 (file)
@@ -484,6 +484,11 @@ class CollectionsTest(TestBase):
             control |= values
             assert_eq()
 
+            values = frozenset([e, creator()])
+            obj.attr |= values
+            control |= values
+            assert_eq()
+
             try:
                 direct |= [e, creator()]
                 assert False
@@ -529,6 +534,11 @@ class CollectionsTest(TestBase):
             control -= values
             assert_eq()
 
+            values = frozenset([creator()])
+            obj.attr -= values
+            control -= values
+            assert_eq()
+
             try:
                 direct -= [e, creator()]
                 assert False