- 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
This commit is contained in:
Mike Bayer 2015-04-30 11:33:58 -04:00
parent a192c345fc
commit dabc7f0932
5 changed files with 124 additions and 33 deletions

View 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
):

View 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

View 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.

View 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

View 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