From: Mike Bayer Date: Mon, 4 Mar 2013 23:56:50 +0000 (-0500) Subject: - The rendering of any construct during autogenerate X-Git-Tag: rel_0_5_0~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35f8e319b07882dab83f144688d872a9fe507a46;p=thirdparty%2Fsqlalchemy%2Falembic.git - 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`. #108 --- diff --git a/alembic/autogenerate.py b/alembic/autogenerate.py index b50e16cb..9fb0ce29 100644 --- a/alembic/autogenerate.py +++ b/alembic/autogenerate.py @@ -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)) diff --git a/alembic/environment.py b/alembic/environment.py index c633c2b7..9d57a857 100644 --- a/alembic/environment.py +++ b/alembic/environment.py @@ -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: diff --git a/alembic/migration.py b/alembic/migration.py index 7a13e937..e06b01e2 100644 --- a/alembic/migration.py +++ b/alembic/migration.py @@ -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(), diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index f3ee4a6d..e4043de6 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -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 diff --git a/tests/test_autogenerate.py b/tests/test_autogenerate.py index 699d037a..1ca5c362 100644 --- a/tests/test_autogenerate.py +++ b/tests/test_autogenerate.py @@ -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(