diff options
| -rw-r--r-- | doc/build/changelog/changelog_11.rst | 17 | ||||
| -rw-r--r-- | doc/build/changelog/migration_11.rst | 29 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/type_api.py | 15 | ||||
| -rw-r--r-- | test/dialect/postgresql/test_types.py | 28 | ||||
| -rw-r--r-- | test/sql/test_types.py | 10 |
5 files changed, 90 insertions, 9 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 7d076a35c..4fff4ab64 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -22,6 +22,23 @@ :version: 1.1.0b1 .. change:: + :tags: bug, sql + :tickets: 2919 + + The :class:`.TypeDecorator` type extender will now work in conjunction + with a :class:`.SchemaType` implementation, typically :class:`.Enum` + or :class:`.Boolean` with regards to ensuring that the per-table + events are propagated from the implementation type to the outer type. + These events are used + to ensure that the constraints or Postgresql types (e.g. ENUM) + are correctly created (and possibly dropped) along with the parent + table. + + .. seealso:: + + :ref:`change_2919` + + .. change:: :tags: feature, sql :tickets: 1370 diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index 3a0666dcc..412f42d27 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -318,6 +318,35 @@ and :class:`.cume_dist`. :ticket:`3132` :ticket:`1370` +.. _change_2919: + +TypeDecorator now works with Enum, Boolean, "schema" types automatically +------------------------------------------------------------------------ + +The :class:`.SchemaType` types include types such as :class:`.Enum` +and :class:`.Boolean` which, in addition to corresponding to a database +type, also generate either a CHECK constraint or in the case of Postgresql +ENUM a new CREATE TYPE statement, will now work automatically with +:class:`.TypeDecorator` recipes. Previously, a :class:`.TypeDecorator` for +an :class:`.postgresql.ENUM` had to look like this:: + + # old way + class MyEnum(TypeDecorator, SchemaType): + impl = postgresql.ENUM('one', 'two', 'three', name='myenum') + + def _set_table(self, table): + self.impl._set_table(table) + +The :class:`.TypeDecorator` now propagates those additional events so it +can be done like any other type:: + + # new way + class MyEnum(TypeDecorator): + impl = postgresql.ENUM('one', 'two', 'three', name='myenum') + + +:ticket:`2919` + Key Behavioral Changes - ORM ============================ diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index b9826e585..f5ab1a8d3 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -13,6 +13,7 @@ from .. import exc, util from . import operators from .visitors import Visitable, VisitableType +from .base import SchemaEventTarget # these are back-assigned by sqltypes. BOOLEANTYPE = None @@ -592,7 +593,7 @@ class UserDefinedType(util.with_metaclass(VisitableCheckKWArg, TypeEngine)): return self -class TypeDecorator(TypeEngine): +class TypeDecorator(SchemaEventTarget, TypeEngine): """Allows the creation of types which add additional functionality to an existing type. @@ -772,6 +773,18 @@ class TypeDecorator(TypeEngine): """ return self.impl._type_affinity + def _set_parent(self, column): + """Support SchemaEentTarget""" + + if isinstance(self.impl, SchemaEventTarget): + self.impl._set_parent(column) + + def _set_parent_with_dispatch(self, parent): + """Support SchemaEentTarget""" + + if isinstance(self.impl, SchemaEventTarget): + self.impl._set_parent_with_dispatch(parent) + def type_engine(self, dialect): """Return a dialect-specific :class:`.TypeEngine` instance for this :class:`.TypeDecorator`. diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py index 8eab9d4b9..7aad23255 100644 --- a/test/dialect/postgresql/test_types.py +++ b/test/dialect/postgresql/test_types.py @@ -499,6 +499,34 @@ class EnumTest(fixtures.TestBase, AssertsExecutionResults): finally: metadata.drop_all() + @testing.provide_metadata + def test_custom_subclass(self): + class MyEnum(TypeDecorator): + impl = Enum('oneHI', 'twoHI', 'threeHI', name='myenum') + + def process_bind_param(self, value, dialect): + if value is not None: + value += "HI" + return value + + def process_result_value(self, value, dialect): + if value is not None: + value += "THERE" + return value + + t1 = Table( + 'table1', self.metadata, + Column('data', MyEnum()) + ) + self.metadata.create_all(testing.db) + + with testing.db.connect() as conn: + conn.execute(t1.insert(), {"data": "two"}) + eq_( + conn.scalar(select([t1.c.data])), + "twoHITHERE" + ) + class OIDTest(fixtures.TestBase): __only_on__ = 'postgresql' diff --git a/test/sql/test_types.py b/test/sql/test_types.py index e32126a18..288482392 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -1178,16 +1178,13 @@ class EnumTest(AssertsCompiledSQL, fixtures.TestBase): def __init__(self, name): self.name = name - class MyEnum(types.SchemaType, TypeDecorator): + class MyEnum(TypeDecorator): def __init__(self, values): self.impl = Enum( *[v.name for v in values], name="myenum", native_enum=False) - def _set_table(self, table, column): - self.impl._set_table(table, column) - # future method def process_literal_param(self, value, dialect): return value.name @@ -2007,12 +2004,9 @@ class BooleanTest( def __init__(self, value): self.value = value - class MyBool(types.SchemaType, TypeDecorator): + class MyBool(TypeDecorator): impl = Boolean() - def _set_table(self, table, column): - self.impl._set_table(table, column) - # future method def process_literal_param(self, value, dialect): return value.value |
