diff --git a/alembic/autogenerate/render.py b/alembic/autogenerate/render.py index 61d56acf..38bdbfca 100644 --- a/alembic/autogenerate/render.py +++ b/alembic/autogenerate/render.py @@ -279,6 +279,9 @@ def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str: prefixes = ", ".join("'%s'" % p for p in table._prefixes) text += ",\nprefixes=[%s]" % prefixes + if op.if_not_exists is not None: + text += ",\nif_not_exists=%r" % bool(op.if_not_exists) + text += "\n)" return text @@ -291,6 +294,10 @@ def _drop_table(autogen_context: AutogenContext, op: ops.DropTableOp) -> str: } if op.schema: text += ", schema=%r" % _ident(op.schema) + + if op.if_exists is not None: + text += ", if_exists=%r" % bool(op.if_exists) + text += ")" return text @@ -324,6 +331,8 @@ def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str: assert index.table is not None opts = _render_dialect_kwargs_items(autogen_context, index) + if op.if_not_exists is not None: + opts.append("if_not_exists=%r" % bool(op.if_not_exists)) text = tmpl % { "prefix": _alembic_autogenerate_prefix(autogen_context), "name": _render_gen_name(autogen_context, index.name), @@ -356,6 +365,8 @@ def _drop_index(autogen_context: AutogenContext, op: ops.DropIndexOp) -> str: "table_name=%(table_name)r%(schema)s%(kwargs)s)" ) opts = _render_dialect_kwargs_items(autogen_context, index) + if op.if_exists is not None: + opts.append("if_exists=%r" % bool(op.if_exists)) text = tmpl % { "prefix": _alembic_autogenerate_prefix(autogen_context), "name": _render_gen_name(autogen_context, op.index_name), diff --git a/docs/build/unreleased/1446.rst b/docs/build/unreleased/1446.rst new file mode 100644 index 00000000..fb54481c --- /dev/null +++ b/docs/build/unreleased/1446.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: usecase, autogenerate + + Render ``if_exists`` and ``if_not_exists`` parameters in + :class:`.CreateTableOp`, :class:`.CreateIndexOp`, :class:`.DropTableOp` and + :class:`.DropIndexOp` in an autogenerate context. While Alembic does not + set these parameters during an autogenerate run, they can be enabled using + a custom :class:`.Rewriter` in the ``env.py`` file, where they will now be + part of the rendered Python code in revision files. Pull request courtesy + of Louis-Amaury Chaib (@lachaib). diff --git a/tests/test_autogen_render.py b/tests/test_autogen_render.py index 7907f7ec..14a33194 100644 --- a/tests/test_autogen_render.py +++ b/tests/test_autogen_render.py @@ -93,6 +93,20 @@ def test_render_add_index(self): "['active', 'code'], unique=False)", ) + def test_render_add_index_if_not_exists(self): + """ + autogenerate.render._add_index + """ + t = self.table() + idx = Index("test_active_code_idx", t.c.active, t.c.code) + op_obj = ops.CreateIndexOp.from_index(idx) + op_obj.if_not_exists = True + eq_ignore_whitespace( + autogenerate.render_op_text(self.autogen_context, op_obj), + "op.create_index('test_active_code_idx', 'test', " + "['active', 'code'], unique=False, if_not_exists=True)", + ) + @testing.emits_warning("Can't validate argument ") def test_render_add_index_custom_kwarg(self): t = self.table() @@ -212,6 +226,20 @@ def test_drop_index(self): "op.drop_index('test_active_code_idx', table_name='test')", ) + def test_drop_index_if_exists(self): + """ + autogenerate.render._drop_index + """ + t = self.table() + idx = Index("test_active_code_idx", t.c.active, t.c.code) + op_obj = ops.DropIndexOp.from_index(idx) + op_obj.if_exists = True + eq_ignore_whitespace( + autogenerate.render_op_text(self.autogen_context, op_obj), + "op.drop_index('test_active_code_idx', table_name='test', " + "if_exists=True)", + ) + def test_drop_index_text(self): """ autogenerate.render._drop_index @@ -989,6 +1017,19 @@ def test_render_addtl_args(self): "mysql_engine='InnoDB',sqlite_autoincrement=True)", ) + def test_render_if_not_exists(self): + t = self.table() + op_obj = ops.CreateTableOp.from_table(t) + op_obj.if_not_exists = True + eq_ignore_whitespace( + autogenerate.render_op_text(self.autogen_context, op_obj), + "op.create_table('test'," + "sa.Column('id', sa.Integer(), nullable=False)," + "sa.Column('active', sa.Boolean(), nullable=True)," + "sa.Column('code', sa.String(length=255), nullable=True)," + "sa.PrimaryKeyConstraint('id'),if_not_exists=True)", + ) + def test_render_drop_table(self): op_obj = ops.DropTableOp.from_table(Table("sometable", MetaData())) eq_ignore_whitespace( @@ -1005,6 +1046,15 @@ def test_render_drop_table_w_schema(self): "op.drop_table('sometable', schema='foo')", ) + def test_render_drop_table_if_exists(self): + t = self.table() + op_obj = ops.DropTableOp.from_table(t) + op_obj.if_exists = True + eq_ignore_whitespace( + autogenerate.render_op_text(self.autogen_context, op_obj), + "op.drop_table('test', if_exists=True)", + ) + def test_render_table_no_implicit_check(self): m = MetaData() t = Table("test", m, Column("x", Boolean()))