diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-05-21 14:21:01 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-05-21 14:21:01 -0400 |
| commit | 525cc6fe0247a76201c173e535d8309333461afc (patch) | |
| tree | 45e4e2f10b6e89620360ff19467b975d486d41bb /test/ext/test_mutable.py | |
| parent | 164f2ad2f433b3d297df709d617a1fc421495921 (diff) | |
| download | sqlalchemy-525cc6fe0247a76201c173e535d8309333461afc.tar.gz | |
- Fixed regression in the :mod:`sqlalchemy.ext.mutable` extension
as a result of the bugfix for :ticket:`3167`,
where attribute and validation events are no longer
called within the flush process. The mutable
extension was relying upon this behavior in the case where a column
level Python-side default were responsible for generating the new value
on INSERT or UPDATE, or when a value were fetched from the RETURNING
clause for "eager defaults" mode. The new value would not be subject
to any event when populated and the mutable extension could not
establish proper coercion or history listening. A new event
:meth:`.InstanceEvents.refresh_flush` is added which the mutable
extension now makes use of for this use case.
fixes #3427
- Added new event :meth:`.InstanceEvents.refresh_flush`, invoked
when an INSERT or UPDATE level default value fetched via RETURNING
or Python-side default is invoked within the flush process. This
is to provide a hook that is no longer present as a result of
:ticket:`3167`, where attribute and validation events are no longer
called within the flush process.
- Added a new semi-public method to :class:`.MutableBase`
:meth:`.MutableBase._get_listen_keys`. Overriding this method
is needed in the case where a :class:`.MutableBase` subclass needs
events to propagate for attribute keys other than the key to which
the mutable type is associated with, when intercepting the
:meth:`.InstanceEvents.refresh` or
:meth:`.InstanceEvents.refresh_flush` events. The current example of
this is composites using :class:`.MutableComposite`.
Diffstat (limited to 'test/ext/test_mutable.py')
| -rw-r--r-- | test/ext/test_mutable.py | 91 |
1 files changed, 82 insertions, 9 deletions
diff --git a/test/ext/test_mutable.py b/test/ext/test_mutable.py index bb22f0bc2..a6bcdc47f 100644 --- a/test/ext/test_mutable.py +++ b/test/ext/test_mutable.py @@ -66,23 +66,25 @@ class MyPoint(Point): return value -class _MutableDictTestBase(object): - run_define_tables = 'each' - +class _MutableDictTestFixture(object): @classmethod def _type_fixture(cls): return MutableDict - def setup_mappers(cls): - foo = cls.tables.foo - - mapper(Foo, foo) - def teardown(self): # clear out mapper events Mapper.dispatch._clear() ClassManager.dispatch._clear() - super(_MutableDictTestBase, self).teardown() + super(_MutableDictTestFixture, self).teardown() + + +class _MutableDictTestBase(_MutableDictTestFixture): + run_define_tables = 'each' + + def setup_mappers(cls): + foo = cls.tables.foo + + mapper(Foo, foo) def test_coerce_none(self): sess = Session() @@ -212,6 +214,40 @@ class _MutableDictTestBase(object): eq_(f1.non_mutable_data, {'a': 'b'}) +class MutableColumnDefaultTest(_MutableDictTestFixture, fixtures.MappedTest): + @classmethod + def define_tables(cls, metadata): + MutableDict = cls._type_fixture() + + mutable_pickle = MutableDict.as_mutable(PickleType) + Table( + 'foo', metadata, + Column( + 'id', Integer, primary_key=True, + test_needs_autoincrement=True), + Column('data', mutable_pickle, default={}), + ) + + def setup_mappers(cls): + foo = cls.tables.foo + + mapper(Foo, foo) + + def test_evt_on_flush_refresh(self): + # test for #3427 + + sess = Session() + + f1 = Foo() + sess.add(f1) + sess.flush() + assert isinstance(f1.data, self._type_fixture()) + assert f1 not in sess.dirty + f1.data['foo'] = 'bar' + assert f1 in sess.dirty + + + class MutableWithScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest): @classmethod @@ -450,6 +486,43 @@ class _CompositeTestBase(object): return Point +class MutableCompositeColumnDefaultTest(_CompositeTestBase, + fixtures.MappedTest): + @classmethod + def define_tables(cls, metadata): + Table( + 'foo', metadata, + Column('id', Integer, primary_key=True, + test_needs_autoincrement=True), + Column('x', Integer, default=5), + Column('y', Integer, default=9), + Column('unrelated_data', String(50)) + ) + + @classmethod + def setup_mappers(cls): + foo = cls.tables.foo + + cls.Point = cls._type_fixture() + + mapper(Foo, foo, properties={ + 'data': composite(cls.Point, foo.c.x, foo.c.y) + }) + + def test_evt_on_flush_refresh(self): + # this still worked prior to #3427 being fixed in any case + + sess = Session() + + f1 = Foo(data=self.Point(None, None)) + sess.add(f1) + sess.flush() + eq_(f1.data, self.Point(5, 9)) + assert f1 not in sess.dirty + f1.data.x = 10 + assert f1 in sess.dirty + + class MutableCompositesUnpickleTest(_CompositeTestBase, fixtures.MappedTest): @classmethod |
