summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-11-19 09:30:31 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2019-11-20 12:49:26 -0500
commit560044748a8ff5488769f8ebfa8a353a8d0115fa (patch)
tree43dc7c5f5127e263ce6b315a5c97f3daa96d5ec2
parent23b62c72ce436e32633f93c80a83db42bf5d60c7 (diff)
downloadsqlalchemy-560044748a8ff5488769f8ebfa8a353a8d0115fa.tar.gz
Skip on slice assignment to self
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
-rw-r--r--doc/build/changelog/unreleased_13/4990.rst11
-rw-r--r--lib/sqlalchemy/orm/collections.py2
-rw-r--r--test/orm/test_collection.py36
3 files changed, 45 insertions, 4 deletions
diff --git a/doc/build/changelog/unreleased_13/4990.rst b/doc/build/changelog/unreleased_13/4990.rst
new file mode 100644
index 000000000..702403dd5
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/4990.rst
@@ -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
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 4b096c152..5008f5727 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -1201,6 +1201,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]
diff --git a/test/orm/test_collection.py b/test/orm/test_collection.py
index 34b7fc5fe..03b6c8758 100644
--- a/test/orm/test_collection.py
+++ b/test/orm/test_collection.py
@@ -1,3 +1,4 @@
+import contextlib
from operator import and_
from sqlalchemy import event
@@ -30,6 +31,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)
@@ -37,15 +47,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):
@@ -299,6 +311,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()]