_rewriter = None
yield directive
else:
- for r_directive in util.to_list(
- _rewriter(context, revision, directive)
- ):
- yield r_directive
+ if self in directive._mutations:
+ yield directive
+ else:
+ for r_directive in util.to_list(
+ _rewriter(context, revision, directive)
+ ):
+ r_directive._mutations = r_directive._mutations.union(
+ [self]
+ )
+ yield r_directive
def __call__(self, context, revision, directives):
self.process_revision_directives(context, revision, directives)
def _traverse_script(self, context, revision, directive):
upgrade_ops_list = []
for upgrade_ops in directive.upgrade_ops_list:
- ret = self._traverse_for(context, revision, directive.upgrade_ops)
+ ret = self._traverse_for(context, revision, upgrade_ops)
if len(ret) != 1:
raise ValueError(
"Can only return single object for UpgradeOps traverse"
downgrade_ops_list = []
for downgrade_ops in directive.downgrade_ops_list:
- ret = self._traverse_for(
- context, revision, directive.downgrade_ops
- )
+ ret = self._traverse_for(context, revision, downgrade_ops)
if len(ret) != 1:
raise ValueError(
"Can only return single object for DowngradeOps traverse"
--- /dev/null
+.. change::
+ :tags: bug, autogenerate
+ :tickets: 505
+
+ Modified the logic of the :class:`.Rewriter` object such that it keeps a
+ memoization of which directives it has processed, so that it can ensure it
+ processes a particular directive only once, and additionally fixed
+ :class:`.Rewriter` so that it functions correctly for multiple-pass
+ autogenerate schemes, such as the one illustrated in the "multidb"
+ template. By tracking which directives have been processed, a
+ multiple-pass scheme which calls upon the :class:`.Rewriter` multiple times
+ for the same structure as elements are added can work without running
+ duplicate operations on the same elements more than once.
" # ### end Alembic commands ###",
)
+ def test_multiple_passes_with_mutations(self):
+ writer1 = autogenerate.Rewriter()
+
+ @writer1.rewrites(ops.CreateTableOp)
+ def rewrite_alter_column(context, revision, op):
+ op.table_name += "_pass"
+ return op
+
+ directives = [
+ ops.MigrationScript(
+ util.rev_id(),
+ ops.UpgradeOps(
+ ops=[
+ ops.CreateTableOp(
+ "test_table",
+ [sa.Column("id", sa.Integer(), primary_key=True)],
+ )
+ ]
+ ),
+ ops.DowngradeOps(ops=[]),
+ )
+ ]
+ ctx, rev = mock.Mock(), mock.Mock()
+ writer1(ctx, rev, directives)
+
+ directives[0].upgrade_ops_list.extend(
+ [
+ ops.UpgradeOps(
+ ops=[
+ ops.CreateTableOp(
+ "another_test_table",
+ [sa.Column("id", sa.Integer(), primary_key=True)],
+ )
+ ]
+ ),
+ ops.UpgradeOps(
+ ops=[
+ ops.CreateTableOp(
+ "third_test_table",
+ [sa.Column("id", sa.Integer(), primary_key=True)],
+ )
+ ]
+ ),
+ ]
+ )
+
+ writer1(ctx, rev, directives)
+
+ eq_(
+ autogenerate.render_python_code(directives[0].upgrade_ops_list[0]),
+ "# ### commands auto generated by Alembic - please adjust! ###\n"
+ " op.create_table('test_table_pass',\n"
+ " sa.Column('id', sa.Integer(), nullable=False),\n"
+ " sa.PrimaryKeyConstraint('id')\n"
+ " )\n"
+ " # ### end Alembic commands ###",
+ )
+ eq_(
+ autogenerate.render_python_code(directives[0].upgrade_ops_list[1]),
+ "# ### commands auto generated by Alembic - please adjust! ###\n"
+ " op.create_table('another_test_table_pass',\n"
+ " sa.Column('id', sa.Integer(), nullable=False),\n"
+ " sa.PrimaryKeyConstraint('id')\n"
+ " )\n"
+ " # ### end Alembic commands ###",
+ )
+ eq_(
+ autogenerate.render_python_code(directives[0].upgrade_ops_list[2]),
+ "# ### commands auto generated by Alembic - please adjust! ###\n"
+ " op.create_table('third_test_table_pass',\n"
+ " sa.Column('id', sa.Integer(), nullable=False),\n"
+ " sa.PrimaryKeyConstraint('id')\n"
+ " )\n"
+ " # ### end Alembic commands ###",
+ )
+
class MultiDirRevisionCommandTest(TestBase):
def setUp(self):