]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Skip on slice assignment to self
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 19 Nov 2019 14:30:31 +0000 (09:30 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Nov 2019 17:50:03 +0000 (12:50 -0500)
Fixed issue where when assigning a collection to itself as a slice, the
mutation operation would fail as it would first erase the assigned
collection inadvertently.   As an assignment that does not change  the
contents should not generate events, the operation is now a no-op. Note
that the fix only applies to Python 3; in Python 2, the ``__setitem__``
hook isn't called in this case; ``__setslice__`` is used instead which
recreates the list item-by-item in all cases.

Fixes: #4990
Change-Id: I08727880f70f4fe188de53a4dcd36746b62c7233
(cherry picked from commit 560044748a8ff5488769f8ebfa8a353a8d0115fa)

doc/build/changelog/unreleased_13/4990.rst [new file with mode: 0644]
lib/sqlalchemy/orm/collections.py
test/orm/test_collection.py

diff --git a/doc/build/changelog/unreleased_13/4990.rst b/doc/build/changelog/unreleased_13/4990.rst
new file mode 100644 (file)
index 0000000..702403d
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, orm, py3k
+    :tickets: 4990
+
+    Fixed issue where when assigning a collection to itself as a slice, the
+    mutation operation would fail as it would first erase the assigned
+    collection inadvertently.   As an assignment that does not change  the
+    contents should not generate events, the operation is now a no-op. Note
+    that the fix only applies to Python 3; in Python 2, the ``__setitem__``
+    hook isn't called in this case; ``__setslice__`` is used instead which
+    recreates the list item-by-item in all cases.
\ No newline at end of file
index b9297e15c9b041c8125a6295696f1cf7a4f8e1ef..45baff2c48b9d336fefa9c0c5ef8807767a161fa 100644 (file)
@@ -1155,6 +1155,8 @@ def _list_decorators():
                     stop += len(self)
 
                 if step == 1:
+                    if value is self:
+                        return
                     for i in range(start, stop, step):
                         if len(self) > start:
                             del self[start]
index 83f4f44511fdbde710855f845b9c630303cfb9fd..3003445ebefb107a2105957711a3c64c3661af5e 100644 (file)
@@ -1,3 +1,4 @@
+import contextlib
 from operator import and_
 
 from sqlalchemy import event
@@ -28,6 +29,15 @@ class Canary(object):
         self.data = set()
         self.added = set()
         self.removed = set()
+        self.dupe_check = True
+
+    @contextlib.contextmanager
+    def defer_dupe_check(self):
+        self.dupe_check = False
+        try:
+            yield
+        finally:
+            self.dupe_check = True
 
     def listen(self, attr):
         event.listen(attr, "append", self.append)
@@ -35,15 +45,17 @@ class Canary(object):
         event.listen(attr, "set", self.set)
 
     def append(self, obj, value, initiator):
-        assert value not in self.added
+        if self.dupe_check:
+            assert value not in self.added
+            self.added.add(value)
         self.data.add(value)
-        self.added.add(value)
         return value
 
     def remove(self, obj, value, initiator):
-        assert value not in self.removed
+        if self.dupe_check:
+            assert value not in self.removed
+            self.removed.add(value)
         self.data.remove(value)
-        self.removed.add(value)
 
     def set(self, obj, value, oldvalue, initiator):
         if isinstance(value, str):
@@ -259,6 +271,22 @@ class CollectionsTest(fixtures.ORMTest):
             control[:] = values
             assert_eq()
 
+            # test slice assignment where we slice assign to self,
+            # currently a no-op, issue #4990
+            # note that in py2k, the bug does not exist but it recreates
+            # the collection which breaks our fixtures here
+            with canary.defer_dupe_check():
+                direct[:] = direct
+                control[:] = control
+            assert_eq()
+
+            # we dont handle assignment of self to slices, as this
+            # implies duplicate entries.  behavior here is not well defined
+            # and perhaps should emit a warning
+            # direct[0:1] = list(direct)
+            # control[0:1] = list(control)
+            # assert_eq()
+
             # test slice assignment where
             # slice size goes over the number of items
             values = [creator(), creator()]