]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- The rendering of any construct during autogenerate
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Mar 2013 23:56:50 +0000 (18:56 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Mar 2013 23:56:50 +0000 (18:56 -0500)
can be customized, in particular to allow special rendering
for user-defined column, constraint subclasses, using new
``render_item`` argument to
:meth:`.EnvironmentContext.configure`.
#108

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

index b50e16cbd8549e2c6b8654d9067a993813569dae..9fb0ce29c44405a1036574a118d0a7f7e30c890a 100644 (file)
@@ -445,7 +445,9 @@ def _add_table(table, autogen_context):
         'tablename': table.name,
         'prefix': _alembic_autogenerate_prefix(autogen_context),
         'args': ',\n'.join(
-            [_render_column(col, autogen_context) for col in table.c] +
+            [col for col in
+                [_render_column(col, autogen_context) for col in table.c]
+            if col] +
             sorted([rcons for rcons in
                 [_render_constraint(cons, autogen_context) for cons in
                     table.constraints]
@@ -509,9 +511,10 @@ def _modify_col(tname, cname,
     text += ",\n%sexisting_type=%s" % (indent,
                     _repr_type(sqla_prefix, existing_type, autogen_context))
     if server_default is not False:
-        text += ",\n%sserver_default=%s" % (indent,
-                        _render_server_default(
-                                server_default, autogen_context),)
+        rendered = _render_server_default(
+                                server_default, autogen_context)
+        text += ",\n%sserver_default=%s" % (indent, rendered)
+
     if type_ is not None:
         text += ",\n%stype_=%s" % (indent,
                         _repr_type(sqla_prefix, type_, autogen_context))
@@ -522,12 +525,11 @@ def _modify_col(tname, cname,
         text += ",\n%sexisting_nullable=%r" % (
                         indent, existing_nullable)
     if existing_server_default:
-        text += ",\n%sexisting_server_default=%s" % (
-                        indent,
-                        _render_server_default(
+        rendered = _render_server_default(
                             existing_server_default,
-                            autogen_context),
-                    )
+                            autogen_context)
+        text += ",\n%sexisting_server_default=%s" % (
+                        indent, rendered)
     if schema:
         text += ",\n%sschema=%r" % (indent, schema)
     text += ")"
@@ -539,13 +541,30 @@ def _sqlalchemy_autogenerate_prefix(autogen_context):
 def _alembic_autogenerate_prefix(autogen_context):
     return autogen_context['opts']['alembic_module_prefix'] or ''
 
+
+def _user_defined_render(type_, object_, autogen_context):
+    if 'opts' in autogen_context and \
+            'render_item' in autogen_context['opts']:
+        render = autogen_context['opts']['render_item']
+        if render:
+            rendered = render(type_, object_, autogen_context)
+            if rendered is not False:
+                return rendered
+    return False
+
 def _render_column(column, autogen_context):
+    rendered = _user_defined_render("column", column, autogen_context)
+    if rendered is not False:
+        return rendered
+
     opts = []
     if column.server_default:
-        opts.append(("server_default",
-                    _render_server_default(
+        rendered = _render_server_default(
                             column.server_default, autogen_context
-                    )))
+                    )
+        if rendered:
+            opts.append(("server_default", rendered))
+
     if not column.autoincrement:
         opts.append(("autoincrement", column.autoincrement))
 
@@ -562,6 +581,10 @@ def _render_column(column, autogen_context):
     }
 
 def _render_server_default(default, autogen_context):
+    rendered = _user_defined_render("server_default", default, autogen_context)
+    if rendered is not False:
+        return rendered
+
     if isinstance(default, sa_schema.DefaultClause):
         if isinstance(default.arg, basestring):
             default = default.arg
@@ -578,6 +601,10 @@ def _render_server_default(default, autogen_context):
         return None
 
 def _repr_type(prefix, type_, autogen_context):
+    rendered = _user_defined_render("type", type_, autogen_context)
+    if rendered is not False:
+        return rendered
+
     mod = type(type_).__module__
     imports = autogen_context.get('imports', None)
     if mod.startswith("sqlalchemy.dialects"):
@@ -596,6 +623,10 @@ def _render_constraint(constraint, autogen_context):
         return None
 
 def _render_primary_key(constraint, autogen_context):
+    rendered = _user_defined_render("primary_key", constraint, autogen_context)
+    if rendered is not False:
+        return rendered
+
     opts = []
     if constraint.name:
         opts.append(("name", repr(constraint.name)))
@@ -626,6 +657,10 @@ def _fk_colspec(fk, metadata_schema):
             return fk._colspec
 
 def _render_foreign_key(constraint, autogen_context):
+    rendered = _user_defined_render("foreign_key", constraint, autogen_context)
+    if rendered is not False:
+        return rendered
+
     opts = []
     if constraint.name:
         opts.append(("name", repr(constraint.name)))
@@ -651,6 +686,10 @@ def _render_foreign_key(constraint, autogen_context):
     }
 
 def _render_check_constraint(constraint, autogen_context):
+    rendered = _user_defined_render("check", constraint, autogen_context)
+    if rendered is not False:
+        return rendered
+
     # detect the constraint being part of
     # a parent type which is probably in the Table already.
     # ideally SQLAlchemy would give us more of a first class
@@ -673,6 +712,10 @@ def _render_check_constraint(constraint, autogen_context):
         }
 
 def _render_unique_constraint(constraint, autogen_context):
+    rendered = _user_defined_render("unique", constraint, autogen_context)
+    if rendered is not False:
+        return rendered
+
     opts = []
     if constraint.name:
         opts.append(("name", "'%s'" % constraint.name))
index c633c2b7d898387d104ba9150da7faf42e119f72..9d57a857ab68782e986fc5e1ccbcc7073dfa6b72 100644 (file)
@@ -212,6 +212,7 @@ class EnvironmentContext(object):
             include_schemas=False,
             compare_type=False,
             compare_server_default=False,
+            render_item=None,
             upgrade_token="upgrades",
             downgrade_token="downgrades",
             alembic_module_prefix="op.",
@@ -387,6 +388,33 @@ class EnvironmentContext(object):
          .. versionchanged:: 0.4.0  the ``include_symbol`` callable must now
             also accept a "schema" argument, which may be None.
 
+        :param render_item: Callable that can be used to override how
+         any schema item, i.e. column, constraint, type,
+         etc., is rendered for autogenerate.  The callable receives a
+         string describing the type of object, the object, and
+         the autogen context.  If it returns False, the
+         default rendering method will be used.  If it returns None,
+         the item will not be rendered in the context of a Table
+         construct, that is, can be used to skip columns or constraints
+         within op.create_table()::
+
+            def my_render_column(type_, col, autogen_context):
+                if type_ == "column" and isinstance(col, MySpecialCol):
+                    return repr(col)
+                else:
+                    return False
+
+            context.configure(
+                # ...
+                render_item = my_render_column
+            )
+
+         Available values for the type string include: ``column``,
+         ``primary_key``, ``foreign_key``, ``unique``, ``check``,
+         ``type``, ``server_default``.
+
+         .. versionadded:: 0.5.0
+
         :param upgrade_token: When autogenerate completes, the text of the
          candidate upgrade operations will be present in this template
          variable when ``script.py.mako`` is rendered.  Defaults to
@@ -456,6 +484,8 @@ class EnvironmentContext(object):
         opts['downgrade_token'] = downgrade_token
         opts['sqlalchemy_module_prefix'] = sqlalchemy_module_prefix
         opts['alembic_module_prefix'] = alembic_module_prefix
+        if render_item is not None:
+            opts['render_item'] = render_item
         if compare_type is not None:
             opts['compare_type'] = compare_type
         if compare_server_default is not None:
index 7a13e937c80567b8edcfe45a364a8de7731dc82b..e06b01e2b7833565a6b4a374c62c1d64fbfa6bcf 100644 (file)
@@ -75,7 +75,6 @@ class MigrationContext(object):
         self._user_compare_server_default = opts.get(
                                             'compare_server_default',
                                             False)
-
         version_table = opts.get('version_table', 'alembic_version')
         self._version = Table(
             version_table, MetaData(),
index f3ee4a6dd1e20d590530b285ee010a9932bf6233..e4043de641d6009430661cd4f5685eaa87142f6a 100644 (file)
@@ -6,6 +6,16 @@ Changelog
 .. changelog::
     :version: 0.5.0
 
+    .. change::
+        :tags: feature
+        :tickets: 108
+
+      The rendering of any construct during autogenerate
+      can be customized, in particular to allow special rendering
+      for user-defined column, constraint subclasses, using new
+      ``render_item`` argument to
+      :meth:`.EnvironmentContext.configure`.
+
     .. change::
         :tags: bug
 
index 699d037a9d354d235461eacbff789407a5c3a6fd..1ca5c3628f3e99be7ced2eda62a99d4febc4b8b3 100644 (file)
@@ -1,7 +1,8 @@
 from sqlalchemy import MetaData, Column, Table, Integer, String, Text, \
     Numeric, CHAR, ForeignKey, DATETIME, \
     TypeDecorator, CheckConstraint, Unicode, Enum,\
-    UniqueConstraint, Boolean, ForeignKeyConstraint
+    UniqueConstraint, Boolean, ForeignKeyConstraint,\
+    PrimaryKeyConstraint
 from sqlalchemy.types import NULLTYPE, TIMESTAMP
 from sqlalchemy.dialects import mysql
 from sqlalchemy.engine.reflection import Inspector
@@ -952,6 +953,37 @@ class AutogenRenderTest(TestCase):
                 'nullable=False)'
         )
 
+    def test_render_custom(self):
+
+        def render(type_, obj, context):
+            if type_ == "foreign_key":
+                return None
+            if type_ == "column":
+                if obj.name == "y":
+                    return None
+                else:
+                    return "col(%s)" % obj.name
+            return "render:%s" % type_
+
+        autogen_context = {"opts": {
+            'render_item': render,
+            'alembic_module_prefix': 'sa.'
+        }}
+
+        t = Table('t', MetaData(),
+                Column('x', Integer),
+                Column('y', Integer),
+                PrimaryKeyConstraint('x'),
+                ForeignKeyConstraint(['x'], ['y'])
+            )
+        result = autogenerate._add_table(
+                    t, autogen_context
+                )
+        eq_(
+            result, """sa.create_table('t',
+col(x),
+render:primary_key\n)"""
+        )
 
     def test_render_modify_type(self):
         eq_ignore_whitespace(