]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- Added support for type comparison functions to be not just per
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 30 Apr 2015 15:33:58 +0000 (11:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 30 Apr 2015 15:33:58 +0000 (11:33 -0400)
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

alembic/ddl/impl.py
alembic/environment.py
docs/build/autogenerate.rst
docs/build/changelog.rst
tests/test_autogenerate.py

index 176079c2db295d938bc3138589b86f13a516572b..3cca1ef128c9a34ac4afe3b7e1689c1d680df2e6 100644 (file)
@@ -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
         ):
index 130a50fe9d1784ed3feee0da2bd15bbeabeb8fcd..860315b5e2338dda5c754444f8c047b475eb5b6c 100644 (file)
@@ -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
index 8ad79ede7a04ab3053f5fb38d9379279aa3f843e..93f6000a60fa27ac21afd1134d9a7bbf95a341d6 100644 (file)
@@ -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.
+
+
+
index dbfd3231e1a6ba61762f243f2c6c66e1cde3efab..8e3824cef489b48771780054f3e65c37c086569b 100644 (file)
@@ -6,6 +6,16 @@ Changelog
 .. 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
index e9ffe8bc66468198b088a1189dcbe123602c1b78..a089b42879f101ebd54c3a530b7158e24b170fbb 100644 (file)
@@ -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