]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Use external impl for type rendering
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 May 2018 14:23:22 +0000 (10:23 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 May 2018 15:13:40 +0000 (11:13 -0400)
The render_type() method of DefaultImpl was hardcoded to
only work for SQLAlchemy types.  Most of the work here involves
adding a proof of concept suite illustrating an external dialect
fully rendering types and nested types with imports.

Change-Id: Ia1bf7bad85efdc6083388e724601d5501f5e2454
Fixes: #494
alembic/autogenerate/render.py
docs/build/unreleased/494.rst [new file with mode: 0644]
tests/test_external_dialect.py [new file with mode: 0644]

index 0459d7a64a7548f0fb8adf4f2e6479923b830efd..4fcb4b2e4ee6449679827a77c36894891a7cabf3 100644 (file)
@@ -586,6 +586,8 @@ def _repr_type(type_, autogen_context):
             return impl_rt
         else:
             return "%s.%r" % (dname, type_)
+    elif impl_rt:
+        return impl_rt
     elif mod.startswith("sqlalchemy."):
         if '_render_%s_type' % type_.__visit_name__ in globals():
             fn = globals()['_render_%s_type' % type_.__visit_name__]
@@ -604,7 +606,8 @@ def _render_ARRAY_type(type_, autogen_context):
     )
 
 
-def _render_type_w_subtype(type_, autogen_context, attrname, regexp):
+def _render_type_w_subtype(
+        type_, autogen_context, attrname, regexp, prefix=None):
     outer_repr = repr(type_)
     inner_type = getattr(type_, attrname, None)
     if inner_type is None:
@@ -618,6 +621,9 @@ def _render_type_w_subtype(type_, autogen_context, attrname, regexp):
         regexp + inner_repr,
         r"\1%s" % sub_type, outer_repr)
 
+    if prefix:
+        return "%s%s" % (prefix, outer_type)
+
     mod = type(type_).__module__
     if mod.startswith("sqlalchemy.dialects"):
         dname = re.match(r"sqlalchemy\.dialects\.(\w+)", mod).group(1)
diff --git a/docs/build/unreleased/494.rst b/docs/build/unreleased/494.rst
new file mode 100644 (file)
index 0000000..73f67df
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, autogenerate
+    :tickets: 494
+
+    Added basic capabilities for external dialects to support rendering of
+    "nested" types, like arrays, in a manner similar to that of the Postgresql
+    dialect.
diff --git a/tests/test_external_dialect.py b/tests/test_external_dialect.py
new file mode 100644 (file)
index 0000000..5db1d7e
--- /dev/null
@@ -0,0 +1,138 @@
+from alembic.testing.fixtures import TestBase
+from alembic.ddl import impl
+from alembic.testing import eq_ignore_whitespace
+from alembic import autogenerate
+from alembic.autogenerate import render
+from alembic.migration import MigrationContext
+from alembic.autogenerate import api
+from alembic.testing import eq_
+
+from sqlalchemy import types as sqla_types
+from sqlalchemy import MetaData
+from sqlalchemy.engine import default
+
+
+class CustomDialect(default.DefaultDialect):
+    name = "custom_dialect"
+
+try:
+    from sqlalchemy.dialects import registry
+except ImportError:
+    pass
+else:
+    registry.register("custom_dialect", __name__, "CustomDialect")
+
+
+class CustomDialectImpl(impl.DefaultImpl):
+    __dialect__ = 'custom_dialect'
+    transactional_ddl = False
+
+    def render_type(self, type_, autogen_context):
+        if type_.__module__ == __name__:
+            autogen_context.imports.add(
+                "from %s import custom_dialect_types" % (__name__, ))
+            is_external = True
+        else:
+            is_external = False
+
+        if is_external and \
+                hasattr(self, '_render_%s_type' % type_.__visit_name__):
+            meth = getattr(self, '_render_%s_type' % type_.__visit_name__)
+            return meth(type_, autogen_context)
+
+        if is_external:
+            return "%s.%r" % ("custom_dialect_types", type_)
+        else:
+            return None
+
+    def _render_EXT_ARRAY_type(self, type_, autogen_context):
+        return render._render_type_w_subtype(
+            type_, autogen_context, 'item_type', r'(.+?\()',
+            prefix="custom_dialect_types."
+        )
+
+
+class EXT_ARRAY(sqla_types.TypeEngine):
+    __visit_name__ = 'EXT_ARRAY'
+
+    def __init__(self, item_type):
+        if isinstance(item_type, type):
+            item_type = item_type()
+        self.item_type = item_type
+        super(EXT_ARRAY, self).__init__()
+
+
+class FOOBARTYPE(sqla_types.TypeEngine):
+    __visit_name__ = 'FOOBARTYPE'
+
+
+class ExternalDialectRenderTest(TestBase):
+    __requires__ = ('sqlalchemy_09', )
+
+    def setUp(self):
+        ctx_opts = {
+            'sqlalchemy_module_prefix': 'sa.',
+            'alembic_module_prefix': 'op.',
+            'target_metadata': MetaData(),
+            'user_module_prefix': None
+        }
+        context = MigrationContext.configure(
+            dialect_name="custom_dialect",
+            opts=ctx_opts
+        )
+
+        self.autogen_context = api.AutogenContext(context)
+
+    def test_render_type(self):
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(
+                FOOBARTYPE(), self.autogen_context),
+            "custom_dialect_types.FOOBARTYPE()"
+        )
+
+        eq_(
+            self.autogen_context.imports,
+            set([
+                'from tests.test_external_dialect import custom_dialect_types'
+            ])
+        )
+
+    def test_external_nested_render_sqla_type(self):
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(
+                EXT_ARRAY(sqla_types.Integer), self.autogen_context),
+            "custom_dialect_types.EXT_ARRAY(sa.Integer())"
+        )
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(
+                EXT_ARRAY(
+                    sqla_types.DateTime(timezone=True)
+                ),
+                self.autogen_context),
+            "custom_dialect_types.EXT_ARRAY(sa.DateTime(timezone=True))"
+        )
+
+        eq_(
+            self.autogen_context.imports,
+            set([
+                'from tests.test_external_dialect import custom_dialect_types'
+            ])
+        )
+
+    def test_external_nested_render_external_type(self):
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(
+                EXT_ARRAY(FOOBARTYPE),
+                self.autogen_context),
+            "custom_dialect_types.EXT_ARRAY(custom_dialect_types.FOOBARTYPE())"
+        )
+
+        eq_(
+            self.autogen_context.imports,
+            set([
+                'from tests.test_external_dialect import custom_dialect_types'
+            ])
+        )