From: Mike Bayer Date: Thu, 20 Oct 2016 14:24:40 +0000 (-0400) Subject: Ensure TypeDecorator delegates _set_parent_with_dispatch X-Git-Tag: rel_1_1_3~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=30bd28fca2091e4b2b093154a14c668c09ae3ccd;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Ensure TypeDecorator delegates _set_parent_with_dispatch Ensure TypeDecorator delegates _set_parent_with_dispatch as well as _set_parent to itself as well as its impl, as the TypeDecorator class itself may have an active SchemaType implementation as well. Fixed regression which occurred as a side effect of :ticket:`2919`, which in the less typical case of a user-defined :class:`.TypeDecorator` that was also itself an instance of :class:`.SchemaType` (rather than the implementation being such) would cause the column attachment events to be skipped for the type itself. Change-Id: I0afb498fd91ab7d948e4439e7323a89eafcce0bc Fixes: #3832 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index c8785b7661..3c00736c7b 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,6 +21,18 @@ .. changelog:: :version: 1.1.3 + .. change:: + :tags: bug, sql + :tickets: 3832 + + Fixed regression which occurred as a side effect of :ticket:`2919`, + which in the less typical case of a user-defined + :class:`.TypeDecorator` that was also itself an instance of + :class:`.SchemaType` (rather than the implementation being such) + would cause the column attachment events to be skipped for the + type itself. + + .. changelog:: :version: 1.1.2 :released: October 17, 2016 diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index cf7dcfd310..0b036847b7 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -426,8 +426,6 @@ class SchemaEventTarget(object): def _set_parent(self, parent): """Associate with this SchemaEvent's parent object.""" - raise NotImplementedError() - def _set_parent_with_dispatch(self, parent): self.dispatch.before_parent_attach(self, parent) self._set_parent(parent) diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 689b4c79bd..98ede4e664 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -860,12 +860,16 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): def _set_parent(self, column): """Support SchemaEentTarget""" + super(TypeDecorator, self)._set_parent(column) + if isinstance(self.impl, SchemaEventTarget): self.impl._set_parent(column) def _set_parent_with_dispatch(self, parent): """Support SchemaEentTarget""" + super(TypeDecorator, self)._set_parent_with_dispatch(parent) + if isinstance(self.impl, SchemaEventTarget): self.impl._set_parent_with_dispatch(parent) diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index f2df4da063..f790c2aa0c 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -1574,6 +1574,66 @@ class SchemaTypeTest(fixtures.TestBase): class MyTypeImpl(MyTypeWImpl): pass + class MyTypeDecAndSchema(TypeDecorator, sqltypes.SchemaType): + impl = String() + + evt_targets = () + + def __init__(self): + TypeDecorator.__init__(self) + sqltypes.SchemaType.__init__(self) + + def _on_table_create(self, target, bind, **kw): + self.evt_targets += (target,) + + def _on_metadata_create(self, target, bind, **kw): + self.evt_targets += (target,) + + def test_before_parent_attach_plain(self): + typ = self.MyType() + self._test_before_parent_attach(typ) + + def test_before_parent_attach_typedec_enclosing_schematype(self): + # additional test for [ticket:2919] as part of test for + # [ticket:3832] + + class MySchemaType(sqltypes.TypeEngine, sqltypes.SchemaType): + pass + + target_typ = MySchemaType() + + class MyType(TypeDecorator): + impl = target_typ + + typ = MyType() + self._test_before_parent_attach(typ, target_typ) + + def test_before_parent_attach_typedec_of_schematype(self): + class MyType(TypeDecorator, sqltypes.SchemaType): + impl = String + + typ = MyType() + self._test_before_parent_attach(typ) + + def test_before_parent_attach_schematype_of_typedec(self): + class MyType(sqltypes.SchemaType, TypeDecorator): + impl = String + + typ = MyType() + self._test_before_parent_attach(typ) + + def _test_before_parent_attach(self, typ, evt_target=None): + canary = mock.Mock() + + if evt_target is None: + evt_target = typ + + event.listen(evt_target, "before_parent_attach", canary.go) + + c = Column('q', typ) + + eq_(canary.mock_calls, [mock.call.go(evt_target, c)]) + def test_independent_schema(self): m = MetaData() type_ = self.MyType(schema="q") @@ -1709,6 +1769,13 @@ class SchemaTypeTest(fixtures.TestBase): dialect_impl = typ.dialect_impl(testing.db.dialect) eq_(dialect_impl.evt_targets, (m1, )) + def test_table_dispatch_decorator_schematype(self): + m1 = MetaData() + typ = self.MyTypeDecAndSchema() + t1 = Table('t1', m1, Column('x', typ)) + m1.dispatch.before_create(t1, testing.db) + eq_(typ.evt_targets, (t1, )) + def test_table_dispatch_no_new_impl(self): m1 = MetaData() typ = self.MyType()