diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-30 11:33:58 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-30 11:33:58 -0400 |
commit | dabc7f0932dd13104411469fdc5327474b2bc8e3 (patch) | |
tree | 6ff3538746340f90996b85057f209f65ae9ee3e8 | |
parent | a192c345fcfcf4d5dd817f898ec883d7fa3b0ac4 (diff) | |
download | alembic-dabc7f0932dd13104411469fdc5327474b2bc8e3.tar.gz |
- Added support for type comparison functions to be not just per
environment, but also present on the custom types themselves, by
supplying a method ``compare_against_backend``.
Added a new documentation section :ref:`compare_types` describing
type comparison fully.
fixes #296
-rw-r--r-- | alembic/ddl/impl.py | 6 | ||||
-rw-r--r-- | alembic/environment.py | 34 | ||||
-rw-r--r-- | docs/build/autogenerate.rst | 81 | ||||
-rw-r--r-- | docs/build/changelog.rst | 10 | ||||
-rw-r--r-- | tests/test_autogenerate.py | 26 |
5 files changed, 124 insertions, 33 deletions
diff --git a/alembic/ddl/impl.py b/alembic/ddl/impl.py index 176079c..3cca1ef 100644 --- a/alembic/ddl/impl.py +++ b/alembic/ddl/impl.py @@ -247,6 +247,12 @@ class DefaultImpl(with_metaclass(ImplMeta)): # fixed in 0.7.4 metadata_impl.__dict__.pop('_type_affinity', None) + if hasattr(metadata_impl, "compare_against_backend"): + comparison = metadata_impl.compare_against_backend( + self.dialect, conn_type) + if comparison is not None: + return not comparison + if conn_type._compare_type_affinity( metadata_impl ): diff --git a/alembic/environment.py b/alembic/environment.py index 130a50f..860315b 100644 --- a/alembic/environment.py +++ b/alembic/environment.py @@ -417,38 +417,14 @@ class EnvironmentContext(object): operation. Defaults to ``False`` which disables type comparison. Set to ``True`` to turn on default type comparison, which has varied - accuracy depending on backend. - - To customize type comparison behavior, a callable may be - specified which - can filter type comparisons during an autogenerate operation. - The format of this callable is:: - - def my_compare_type(context, inspected_column, - metadata_column, inspected_type, metadata_type): - # return True if the types are different, - # False if not, or None to allow the default implementation - # to compare these types - return None - - context.configure( - # ... - compare_type = my_compare_type - ) - - - ``inspected_column`` is a :class:`sqlalchemy.schema.Column` as - returned by - :meth:`sqlalchemy.engine.reflection.Inspector.reflecttable`, - whereas ``metadata_column`` is a - :class:`sqlalchemy.schema.Column` from the local model - environment. - - A return value of ``None`` indicates to allow default type - comparison to proceed. + accuracy depending on backend. See :ref:`compare_types` + for an example as well as information on other type + comparison options. .. seealso:: + :ref:`compare_types` + :paramref:`.EnvironmentContext.configure.compare_server_default` :param compare_server_default: Indicates server default comparison diff --git a/docs/build/autogenerate.rst b/docs/build/autogenerate.rst index 8ad79ed..93f6000 100644 --- a/docs/build/autogenerate.rst +++ b/docs/build/autogenerate.rst @@ -126,7 +126,7 @@ Autogenerate can **optionally detect**: The feature works well in most cases, but is off by default so that it can be tested on the target schema first. It can also be customized by passing a callable here; see the - function's documentation for details. + section :ref:`compare_types` for details. * Change of server default. This will occur if you set the :paramref:`.EnvironmentContext.configure.compare_server_default` parameter to ``True``, or to a custom callable function. @@ -170,10 +170,10 @@ Autogenerate can't currently, but **will eventually detect**: * Sequence additions, removals - not yet implemented. -Rendering Types ----------------- +Comparing and Rendering Types +------------------------------ -The area of autogenerate's behavior of rendering Python-based type objects +The area of autogenerate's behavior of comparing and rendering Python-based type objects in migration scripts presents a challenge, in that there's a very wide variety of types to be rendered in scripts, including those part of SQLAlchemy as well as user-defined types. A few options @@ -345,3 +345,76 @@ The finished migration script will include our imports where the op.add_column('sometable', Column('mycolumn', types.MySpecialType())) +.. _compare_types: + +Comparing Types +^^^^^^^^^^^^^^^^ + +The default type comparison logic will work for SQLAlchemy built in types as +well as basic user defined types. This logic is only enabled if the +:paramref:`.EnvironmentContext.configure.compare_type` parameter +is set to True:: + + context.configure( + # ... + compare_type = True + ) + +Alternatively, the :paramref:`.EnvironmentContext.configure.compare_type` +parameter accepts a callable function which may be used to implement custom type +comparison logic, for cases such as where special user defined types +are being used:: + + def my_compare_type(context, inspected_column, + metadata_column, inspected_type, metadata_type): + # return True if the types are different, + # False if not, or None to allow the default implementation + # to compare these types + return None + + context.configure( + # ... + compare_type = my_compare_type + ) + +Above, ``inspected_column`` is a :class:`sqlalchemy.schema.Column` as +returned by +:meth:`sqlalchemy.engine.reflection.Inspector.reflecttable`, whereas +``metadata_column`` is a :class:`sqlalchemy.schema.Column` from the +local model environment. A return value of ``None`` indicates that default +type comparison to proceed. + +Additionally, custom types that are part of imported or third party +packages which have special behaviors such as per-dialect behavior +should implement a method called ``compare_against_backend()`` +on their SQLAlchemy type. If this method is present, it will be called +where it can also return True or False to specify the types compare as +equivalent or not; if it returns None, default type comparison logic +will proceed:: + + class MySpecialType(TypeDecorator): + + # ... + + def compare_against_backend(self, dialect, conn_type): + # return True if the types are different, + # False if not, or None to allow the default implementation + # to compare these types + if dialect.name == 'postgresql': + return isinstance(conn_type, postgresql.UUID) + else: + return isinstance(conn_type, String) + +The order of precedence regarding the +:paramref:`.EnvironmentContext.configure.compare_type` callable vs. the +type itself implementing ``compare_against_backend`` is that the +:paramref:`.EnvironmentContext.configure.compare_type` callable is favored +first; if it returns ``None``, then the ``compare_against_backend`` method +will be used, if present on the metadata type. If that reutrns ``None``, +then a basic check for type equivalence is run. + +.. versionadded:: 0.7.6 - added support for the ``compare_against_backend()`` + method. + + + diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index dbfd323..8e3824c 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -7,6 +7,16 @@ Changelog :version: 0.7.6 .. change:: + :tags: feature, autogenerate + :tickets: 296 + + Added support for type comparison functions to be not just per + environment, but also present on the custom types themselves, by + supplying a method ``compare_against_backend``. + Added a new documentation section :ref:`compare_types` describing + type comparison fully. + + .. change:: :tags: feature, operations :tickets: 255 diff --git a/tests/test_autogenerate.py b/tests/test_autogenerate.py index e9ffe8b..a089b42 100644 --- a/tests/test_autogenerate.py +++ b/tests/test_autogenerate.py @@ -773,6 +773,32 @@ nullable=True)) ) assert not diff + def test_custom_type_compare(self): + class MyType(TypeDecorator): + impl = Integer + + def compare_against_backend(self, dialect, conn_type): + return isinstance(conn_type, Integer) + + diff = [] + autogenerate.compare._compare_type(None, "sometable", "somecol", + Column("somecol", INTEGER()), + Column("somecol", MyType()), + diff, self.autogen_context + ) + assert not diff + + diff = [] + autogenerate.compare._compare_type(None, "sometable", "somecol", + Column("somecol", String()), + Column("somecol", MyType()), + diff, self.autogen_context + ) + eq_( + diff[0][0:4], + ('modify_type', None, 'sometable', 'somecol') + ) + def test_affinity_typedec(self): class MyType(TypeDecorator): impl = CHAR |