diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-19 11:09:38 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-19 11:09:38 -0400 |
commit | b9a2b58dd74757184ef94206f09e9db1f536e4cb (patch) | |
tree | 58dd9631ac97aa1b0d5e335f3f3eab5c7c872625 | |
parent | fd8dbf8e78981187d091dcc25e63ddfb4e56a5f8 (diff) | |
download | sqlalchemy-b9a2b58dd74757184ef94206f09e9db1f536e4cb.tar.gz |
- Fixed bug in mutable extension as well as
:func:`.attributes.flag_modified` where the change event would not be
propagated if the attribute had been reassigned to itself.
fixes #2997
-rw-r--r-- | doc/build/changelog/changelog_08.rst | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/mutable.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/state.py | 4 | ||||
-rw-r--r-- | test/ext/test_mutable.py | 11 | ||||
-rw-r--r-- | test/orm/test_attributes.py | 15 |
6 files changed, 41 insertions, 3 deletions
diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 08e44a0fd..b88a53a88 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -12,6 +12,15 @@ :version: 0.8.6 .. change:: + :tags: bug, ext + :versions: 0.9.4 + :tickets: 2997 + + Fixed bug in mutable extension as well as + :func:`.attributes.flag_modified` where the change event would not be + propagated if the attribute had been reassigned to itself. + + .. change:: :tags: bug, orm :versions: 0.9.4 diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py index 4e574935d..7869e888c 100644 --- a/lib/sqlalchemy/ext/mutable.py +++ b/lib/sqlalchemy/ext/mutable.py @@ -437,6 +437,9 @@ class MutableBase(object): outgoing. """ + if value is oldvalue: + return value + if not isinstance(value, cls): value = cls.coerce(key, value) if value is not None: diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 7647bf9d0..3a786c73d 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1538,4 +1538,4 @@ def flag_modified(instance, key): """ state, dict_ = instance_state(instance), instance_dict(instance) impl = state.manager[key].impl - state._modified_event(dict_, impl, NO_VALUE) + state._modified_event(dict_, impl, NO_VALUE, force=True) diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 9712dd055..fb5db1fc9 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -414,10 +414,10 @@ class InstanceState(interfaces._InspectionAttr): def _instance_dict(self): return None - def _modified_event(self, dict_, attr, previous, collection=False): + def _modified_event(self, dict_, attr, previous, collection=False, force=False): if not attr.send_modified_events: return - if attr.key not in self.committed_state: + if attr.key not in self.committed_state or force: if collection: if previous is NEVER_SET: if attr.key in dict_: diff --git a/test/ext/test_mutable.py b/test/ext/test_mutable.py index ee1b8075e..39cfa7ac5 100644 --- a/test/ext/test_mutable.py +++ b/test/ext/test_mutable.py @@ -96,6 +96,17 @@ class _MutableDictTestBase(object): sess.commit() eq_(f1.data, {'b': 'c'}) + def test_replace_itself_still_ok(self): + sess = Session() + f1 = Foo(data={'a': 'b'}) + sess.add(f1) + sess.flush() + + f1.data = f1.data + f1.data['b'] = 'c' + sess.commit() + eq_(f1.data, {'a': 'b', 'b': 'c'}) + def test_pickle_parent(self): sess = Session() diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py index 4f8092f31..a7c675175 100644 --- a/test/orm/test_attributes.py +++ b/test/orm/test_attributes.py @@ -1852,6 +1852,21 @@ class HistoryTest(fixtures.TestBase): f.someattr = ['a'] eq_(self._someattr_history(f), ([['a']], (), ())) + def test_scalar_inplace_mutation_replace_self_flag_modified_set(self): + Foo = self._fixture(uselist=False, useobject=False, + active_history=False) + f = Foo() + f.someattr = {'a': 'b'} + self._commit_someattr(f) + eq_(self._someattr_history(f), ((), [{'a': 'b'}], ())) + + # set the attribute to itself; this places a copy + # in committed_state + f.someattr = f.someattr + + attributes.flag_modified(f, 'someattr') + eq_(self._someattr_history(f), ([{'a': 'b'}], (), ())) + def test_use_object_init(self): Foo, Bar = self._two_obj_fixture(uselist=False) |