]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- Added new argument
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 1 Feb 2014 17:29:03 +0000 (12:29 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 1 Feb 2014 17:29:03 +0000 (12:29 -0500)
:paramref:`.EnvironmentContext.configure.user_module_prefix`.
This prefix is applied when autogenerate renders a user-defined type,
which here is defined as any type that is from a module outside of the
``sqlalchemy.`` hierarchy.   This prefix defaults to ``None``, in
which case the :paramref:`.EnvironmentContext.configure.sqlalchemy_module_prefix`
is used, thus preserving the current behavior. #171
- added new documentation sections regarding rendering of types
- improved CSS so that deprecations/version changes are highlighted
- cleanup of some configure paramter docs

alembic/autogenerate/render.py
alembic/environment.py
docs/build/_static/nature_override.css [new file with mode: 0644]
docs/build/changelog.rst
docs/build/conf.py
docs/build/tutorial.rst
tests/test_autogen_render.py

index 38e953c6fbff021f00b1a759a829bdb7119740b3..4c7b4f5e4094e0026d58009a5bdbd5a7fed782ed 100644 (file)
@@ -204,7 +204,6 @@ def _modify_col(tname, cname,
                 existing_nullable=None,
                 existing_server_default=False,
                 schema=None):
-    sqla_prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
     indent = " " * 11
     text = "%(prefix)salter_column(%(tname)r, %(cname)r" % {
                             'prefix': _alembic_autogenerate_prefix(
@@ -212,7 +211,7 @@ def _modify_col(tname, cname,
                             'tname': tname,
                             'cname': cname}
     text += ",\n%sexisting_type=%s" % (indent,
-                    _repr_type(sqla_prefix, existing_type, autogen_context))
+                    _repr_type(existing_type, autogen_context))
     if server_default is not False:
         rendered = _render_server_default(
                                 server_default, autogen_context)
@@ -220,7 +219,7 @@ def _modify_col(tname, cname,
 
     if type_ is not None:
         text += ",\n%stype_=%s" % (indent,
-                        _repr_type(sqla_prefix, type_, autogen_context))
+                        _repr_type(type_, autogen_context))
     if nullable is not None:
         text += ",\n%snullable=%r" % (
                         indent, nullable,)
@@ -238,6 +237,13 @@ def _modify_col(tname, cname,
     text += ")"
     return text
 
+def _user_autogenerate_prefix(autogen_context):
+    prefix = autogen_context['opts']['user_module_prefix']
+    if prefix is None:
+        return _sqlalchemy_autogenerate_prefix(autogen_context)
+    else:
+        return prefix
+
 def _sqlalchemy_autogenerate_prefix(autogen_context):
     return autogen_context['opts']['sqlalchemy_module_prefix'] or ''
 
@@ -277,8 +283,7 @@ def _render_column(column, autogen_context):
     return "%(prefix)sColumn(%(name)r, %(type)s, %(kw)s)" % {
         'prefix': _sqlalchemy_autogenerate_prefix(autogen_context),
         'name': column.name,
-        'type': _repr_type(_sqlalchemy_autogenerate_prefix(autogen_context),
-                                column.type, autogen_context),
+        'type': _repr_type(column.type, autogen_context),
         'kw': ", ".join(["%s=%s" % (kwname, val) for kwname, val in opts])
     }
 
@@ -302,7 +307,7 @@ def _render_server_default(default, autogen_context):
     else:
         return None
 
-def _repr_type(prefix, type_, autogen_context):
+def _repr_type(type_, autogen_context):
     rendered = _user_defined_render("type", type_, autogen_context)
     if rendered is not False:
         return rendered
@@ -314,7 +319,11 @@ def _repr_type(prefix, type_, autogen_context):
         if imports is not None:
             imports.add("from sqlalchemy.dialects import %s" % dname)
         return "%s.%r" % (dname, type_)
+    elif mod.startswith("sqlalchemy"):
+        prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
+        return "%s%r" % (prefix, type_)
     else:
+        prefix = _user_autogenerate_prefix(autogen_context)
         return "%s%r" % (prefix, type_)
 
 def _render_constraint(constraint, autogen_context):
index ea231e58c057cabf2613d3dc6bc89368584ea05c..aa59fce9f880d71f57fe8ade1c626e0697c5debb 100644 (file)
@@ -273,6 +273,7 @@ class EnvironmentContext(object):
             downgrade_token="downgrades",
             alembic_module_prefix="op.",
             sqlalchemy_module_prefix="sa.",
+            user_module_prefix=None,
             **kw
         ):
         """Configure a :class:`.MigrationContext` within this
@@ -469,15 +470,18 @@ class EnvironmentContext(object):
 
          .. seealso::
 
-            ``include_schemas``, ``include_symbol``
+            :ref:`autogen_render_types`
 
+            :paramref:`.EnvironmentContext.configure.include_schemas`
 
         :param include_symbol: A callable function which, given a table name
          and schema name (may be ``None``), returns ``True`` or ``False``, indicating
          if the given table should be considered in the autogenerate sweep.
 
-         .. deprecated:: 0.6.0 ``include_symbol`` is superceded by the
-            more generic ``include_object`` parameter.
+         .. deprecated:: 0.6.0 :paramref:`.EnvironmentContext.configure.include_symbol`
+            is superceded by the more generic
+            :paramref:`.EnvironmentContext.configure.include_object`
+            parameter.
 
          E.g.::
 
@@ -489,26 +493,11 @@ class EnvironmentContext(object):
                 include_symbol = include_symbol
             )
 
-         To limit autogenerate to a certain set of schemas when using the
-         ``include_schemas`` option::
-
-            def include_symbol(tablename, schema):
-                return schema in (None, "schema1", "schema2")
-
-            context.configure(
-                # ...
-                include_schemas = True,
-                include_symbol = include_symbol
-            )
-
-         .. versionadded:: 0.3.6
-
-         .. versionchanged:: 0.4.0  the ``include_symbol`` callable must now
-            also accept a "schema" argument, which may be None.
-
          .. seealso::
 
-            ``include_schemas``, ``include_object``
+            :paramref:`.EnvironmentContext.configure.include_schemas`
+
+            :paramref:`.EnvironmentContext.configure.include_object`
 
         :param include_schemas: If True, autogenerate will scan across
          all schemas located by the SQLAlchemy
@@ -522,7 +511,7 @@ class EnvironmentContext(object):
 
          .. seealso::
 
-            ``include_symbol``, ``include_object``
+            :paramref:`.EnvironmentContext.configure.include_object`
 
         :param render_item: Callable that can be used to override how
          any schema item, i.e. column, constraint, type,
@@ -575,6 +564,20 @@ class EnvironmentContext(object):
          will render them using the dialect module name, i.e. ``mssql.BIT()``,
          ``postgresql.UUID()``.
 
+        :param user_module_prefix: When autogenerate refers to a SQLAlchemy
+         type (e.g. :class:`.TypeEngine`) where the module name is not
+         under the ``sqlalchemy`` namespace, this prefix will be used
+         within autogenerate, if non-``None``; if left at its default of
+         ``None``, the
+         :paramref:`.EnvironmentContext.configure.sqlalchemy_module_prefix`
+         is used instead.
+
+         .. versionadded:: 0.6.3 added
+            :paramref:`.EnvironmentContext.configure.user_module_prefix`
+
+         .. seealso::
+
+            :ref:`autogen_module_prefix`
 
         Parameters specific to individual backends:
 
@@ -612,6 +615,7 @@ class EnvironmentContext(object):
         opts['downgrade_token'] = downgrade_token
         opts['sqlalchemy_module_prefix'] = sqlalchemy_module_prefix
         opts['alembic_module_prefix'] = alembic_module_prefix
+        opts['user_module_prefix'] = user_module_prefix
         if render_item is not None:
             opts['render_item'] = render_item
         if compare_type is not None:
diff --git a/docs/build/_static/nature_override.css b/docs/build/_static/nature_override.css
new file mode 100644 (file)
index 0000000..2d5608f
--- /dev/null
@@ -0,0 +1,14 @@
+@import url("nature.css");
+
+
+.versionadded, .versionchanged, .deprecated {
+    background-color: #FFFFCC;
+    border: 1px solid #FFFF66;
+    margin-bottom: 10px;
+    margin-top: 10px;
+    padding: 7px;
+}
+
+.versionadded > p > span, .versionchanged > p > span, .deprecated > p > span{
+       font-style: italic;
+}
index 4d24d96c329b297170023aa37208902fe59b3c1c..a3f7793e579216cb4c99c29ae19714868ce633f2 100644 (file)
@@ -5,6 +5,18 @@ Changelog
 .. changelog::
     :version: 0.6.3
 
+    .. change::
+      :tags: feature
+      :tickets: 171
+
+      Added new argument
+      :paramref:`.EnvironmentContext.configure.user_module_prefix`.
+      This prefix is applied when autogenerate renders a user-defined type,
+      which here is defined as any type that is from a module outside of the
+      ``sqlalchemy.`` hierarchy.   This prefix defaults to ``None``, in
+      which case the :paramref:`.EnvironmentContext.configure.sqlalchemy_module_prefix`
+      is used, thus preserving the current behavior.
+
     .. change::
       :tags: bug
       :tickets: 170
index 54fa6b69ccce5e969cb71a4da224db6b5728c7c9..586d61cf9a83a9ca3bb9de7b59ca60d2e14bcb6a 100644 (file)
@@ -118,6 +118,8 @@ pygments_style = 'sphinx'
 # Sphinx are currently 'default' and 'sphinxdoc'.
 html_theme = 'nature'
 
+html_style = "nature_override.css"
+
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
index 3493a9f9de3a9cd216f1983ad76f4606b52ac7d9..b224e26c3132f81bfce207c6fd0371be8cb0ddca 100644 (file)
@@ -607,6 +607,8 @@ Autogenerate can't currently, but will *eventually* detect:
   like CHECK and FOREIGN KEY - these are not fully implemented.
 * Sequence additions, removals - not yet implemented.
 
+.. _autogen_render_types:
+
 Rendering Custom Types in Autogenerate
 --------------------------------------
 
@@ -622,7 +624,8 @@ it is often necessary to explicitly give the type a ``__repr__()`` that will
 faithfully reproduce the constructor for that type.   But beyond that, it
 also is usually necessary to change how the enclosing module or package
 is rendered as well;
-this is accomplished using the ``render_item`` configuration option::
+this is accomplished using the :paramref:`.EnvironmentContext.configure.render_item`
+configuration option::
 
     def render_item(type_, obj, autogen_context):
         """Apply custom rendering for selected items."""
@@ -648,6 +651,75 @@ this is accomplished using the ``render_item`` configuration option::
 Above, we also need to make sure our ``MySpecialType`` includes an appropriate
 ``__repr__()`` method, which is invoked when we call it against ``"%r"``.
 
+The callable we use for :paramref:`.EnvironmentContext.configure.render_item`
+can also add imports to our migration script.  The ``autogen_context`` passed in
+contains an entry called ``autogen_context['imports']``, which is a Python
+``set()`` for which we can add new imports.  For example, if ``MySpecialType``
+were in a module called ``mymodel.types``, we can add the import for it
+as we encounter the type::
+
+    def render_item(type_, obj, autogen_context):
+        """Apply custom rendering for selected items."""
+
+        if type_ == 'type' and isinstance(obj, MySpecialType):
+            # add import for this type
+            autogen_context['imports'].add("from mymodel import types")
+            return "types.%r" % obj
+
+        # default rendering for other objects
+        return False
+
+The finished migration script will include our imports where the
+``${imports}`` expression is used, producing output such as::
+
+  from alembic import op
+  import sqlalchemy as sa
+  from mymodel import types
+
+  def upgrade():
+      op.add_column('sometable', Column('mycolumn', types.MySpecialType()))
+
+.. _autogen_module_prefix:
+
+Controlling the Module Prefix
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When using :paramref:`.EnvironmentContext.configure.render_item`, note that
+we deliver not just the reproduction of the type, but we can also deliver the
+"module prefix", which is a module namespace from which our type can be found
+within our migration script.  When Alembic renders SQLAlchemy types, it will
+typically use the value of :paramref:`.EnvironmentContext.configure.sqlalchemy_module_prefix`,
+which defaults to ``"sa."``, to achieve this::
+
+    Column("my_column", sa.Integer())
+
+When we use a custom type that is not within the ``sqlalchemy.`` module namespace,
+by default Alembic will still use the ``"sa."`` prefix::
+
+    Column("my_column", sa.MyCustomType())
+
+We can provide an alternate prefix here using the :paramref:`.EnvironmentContext.configure.user_module_prefix`
+option::
+
+
+    def run_migrations_online():
+        # ...
+
+        context.configure(
+                    connection=connection,
+                    target_metadata=target_metadata,
+                    user_module_prefix="mymodel.types",
+                    # ...
+                    )
+
+        # ...
+
+Where we'd get a migration like::
+
+  Column("my_column", mymodel.types.MyCustomType())
+
+.. versionadded:: 0.6.3 Added :paramref:`.EnvironmentContext.configure.user_module_prefix`.
+
 
 Generating SQL Scripts (a.k.a. "Offline Mode")
 ==============================================
index bd69cc33f03fd673ddc1854cd3e10b8c200a3262..0d3e2b1fbed253cb6fe6acb53ed50aa16b75cfc8 100644 (file)
@@ -129,7 +129,7 @@ class AutogenRenderTest(TestCase):
             autogenerate.render._drop_index(idx, self.autogen_context),
             "op.drop_index('test_active_code_idx', table_name='test')"
         )
-        
+
     def test_drop_index_schema(self):
         """
         autogenerate.render._drop_index using schema
@@ -163,7 +163,7 @@ class AutogenRenderTest(TestCase):
             autogenerate.render._add_unique_constraint(uq, self.autogen_context),
             "op.create_unique_constraint('uq_test_code', 'test', ['code'])"
         )
-        
+
     def test_add_unique_constraint_schema(self):
         """
         autogenerate.render._add_unique_constraint using schema
@@ -196,7 +196,7 @@ class AutogenRenderTest(TestCase):
             autogenerate.render._drop_constraint(uq, self.autogen_context),
             "op.drop_constraint('uq_test_code', 'test')"
         )
-        
+
     def test_drop_constraint_schema(self):
         """
         autogenerate.render._drop_constraint using schema
@@ -615,17 +615,91 @@ render:primary_key\n)"""
     def test_render_enum(self):
         eq_ignore_whitespace(
             autogenerate.render._repr_type(
-                        "sa.",
                         Enum("one", "two", "three", name="myenum"),
                         self.autogen_context),
             "sa.Enum('one', 'two', 'three', name='myenum')"
         )
         eq_ignore_whitespace(
             autogenerate.render._repr_type(
-                        "sa.",
                         Enum("one", "two", "three"),
                         self.autogen_context),
             "sa.Enum('one', 'two', 'three')"
         )
 
-# TODO: tests for dialect-specific type rendering + imports
+    def test_repr_plain_sqla_type(self):
+        type_ = Integer()
+        autogen_context = {
+            'opts': {
+                'sqlalchemy_module_prefix': 'sa.',
+                'alembic_module_prefix': 'op.',
+            },
+            'dialect': mysql.dialect()
+        }
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(type_, autogen_context),
+            "sa.Integer()"
+        )
+
+    def test_repr_user_type_user_prefix_None(self):
+        from sqlalchemy.types import UserDefinedType
+        class MyType(UserDefinedType):
+            def get_col_spec(self):
+                return "MYTYPE"
+
+        type_ = MyType()
+        autogen_context = {
+            'opts': {
+                'sqlalchemy_module_prefix': 'sa.',
+                'alembic_module_prefix': 'op.',
+                'user_module_prefix': None
+            },
+            'dialect': mysql.dialect()
+        }
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(type_, autogen_context),
+            "sa.MyType()"
+        )
+
+    def test_repr_user_type_user_prefix_present(self):
+        from sqlalchemy.types import UserDefinedType
+        class MyType(UserDefinedType):
+            def get_col_spec(self):
+                return "MYTYPE"
+
+        type_ = MyType()
+        autogen_context = {
+            'opts': {
+                'sqlalchemy_module_prefix': 'sa.',
+                'alembic_module_prefix': 'op.',
+                'user_module_prefix': 'user.',
+            },
+            'dialect': mysql.dialect()
+        }
+
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(type_, autogen_context),
+            "user.MyType()"
+        )
+
+    def test_repr_dialect_type(self):
+        from sqlalchemy.dialects.mysql import VARCHAR
+
+        type_ = VARCHAR(20, charset='utf8', national=True)
+        autogen_context = {
+            'opts': {
+                'sqlalchemy_module_prefix': 'sa.',
+                'alembic_module_prefix': 'op.',
+                'user_module_prefix': None,
+            },
+            'imports': set(),
+            'dialect': mysql.dialect()
+        }
+        eq_ignore_whitespace(
+            autogenerate.render._repr_type(type_, autogen_context),
+            "mysql.VARCHAR(charset='utf8', national=True, length=20)"
+        )
+        eq_(autogen_context['imports'],
+                set(['from sqlalchemy.dialects import mysql'])
+            )