summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-04-30 11:33:58 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-04-30 11:33:58 -0400
commitdabc7f0932dd13104411469fdc5327474b2bc8e3 (patch)
tree6ff3538746340f90996b85057f209f65ae9ee3e8
parenta192c345fcfcf4d5dd817f898ec883d7fa3b0ac4 (diff)
downloadalembic-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.py6
-rw-r--r--alembic/environment.py34
-rw-r--r--docs/build/autogenerate.rst81
-rw-r--r--docs/build/changelog.rst10
-rw-r--r--tests/test_autogenerate.py26
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