diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-11-19 09:30:31 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-11-20 12:49:26 -0500 |
| commit | 560044748a8ff5488769f8ebfa8a353a8d0115fa (patch) | |
| tree | 43dc7c5f5127e263ce6b315a5c97f3daa96d5ec2 | |
| parent | 23b62c72ce436e32633f93c80a83db42bf5d60c7 (diff) | |
| download | sqlalchemy-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.rst | 11 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/collections.py | 2 | ||||
| -rw-r--r-- | test/orm/test_collection.py | 36 |
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()] |
