def test_nochange_onupdate_restrict(self): """test the RESTRICT option which MySQL doesn't report on""" diffs = self._fk_opts_fixture( {"onupdate": "restrict"}, {"onupdate": "restrict"} ) eq_(diffs, [])
def test_rename_col_pk(self): impl = self._simple_fixture() impl.alter_column('tname', 'id', name='foobar') new_table = self._assert_impl( impl, ddl_contains="PRIMARY KEY (foobar)") eq_(new_table.c.id.name, 'foobar') eq_(list(new_table.primary_key), [new_table.c.id])
def test_custom_type_compare(self): class MyType(TypeDecorator): impl = Integer def compare_against_backend(self, dialect, conn_type): return isinstance(conn_type, Integer) diff = [] autogenerate.compare._compare_type(None, "sometable", "somecol", Column("somecol", INTEGER()), Column("somecol", MyType()), diff, self.autogen_context ) assert not diff diff = [] autogenerate.compare._compare_type(None, "sometable", "somecol", Column("somecol", String()), Column("somecol", MyType()), diff, self.autogen_context ) eq_( diff[0][0:4], ('modify_type', None, 'sometable', 'somecol') )
def test_nochange_onupdate_noaction(self): """test the NO ACTION option which generally comes back as None""" diffs = self._fk_opts_fixture( {"onupdate": "no action"}, {"onupdate": "no action"} ) eq_(diffs, [])
def test_no_version_table(self): diffs = [] ctx = self.autogen_context.copy() ctx['metadata'] = self.m2 autogenerate._produce_net_changes(ctx, diffs) eq_(diffs, [])
def _test_selfref_fk(self, recreate): bar = Table( 'bar', self.metadata, Column('id', Integer, primary_key=True), Column('bar_id', Integer, ForeignKey('bar.id')), Column('data', String(50)), mysql_engine='InnoDB' ) bar.create(self.conn) self.conn.execute(bar.insert(), {'id': 1, 'data': 'x', 'bar_id': None}) self.conn.execute(bar.insert(), {'id': 2, 'data': 'y', 'bar_id': 1}) with self.op.batch_alter_table("bar", recreate=recreate) as batch_op: batch_op.alter_column( 'data', new_column_name='newdata', existing_type=String(50)) insp = Inspector.from_engine(self.conn) insp = Inspector.from_engine(self.conn) eq_( [(key['referred_table'], key['referred_columns'], key['constrained_columns']) for key in insp.get_foreign_keys('bar')], [('bar', ['id'], ['bar_id'])] )
def test_include_symbol(self): diffs = [] def include_symbol(name, schema=None): return name in ('address', 'order') context = MigrationContext.configure( connection=self.bind.connect(), opts={ 'compare_type': True, 'compare_server_default': True, 'target_metadata': self.m2, 'include_symbol': include_symbol, } ) diffs = autogenerate.compare_metadata( context, context.opts['target_metadata']) alter_cols = set([ d[2] for d in self._flatten_diffs(diffs) if d[0].startswith('modify') ]) eq_(alter_cols, set(['order']))
def test_needs_flag(self): a = util.rev_id() script = ScriptDirectory.from_config(self.cfg) script.generate_revision(a, None, refresh=True) write_script(script, a, """ revision = '%s' down_revision = None from alembic import op def upgrade(): op.execute("CREATE TABLE foo(id integer)") def downgrade(): op.execute("DROP TABLE foo") """ % a, sourceless=True) script = ScriptDirectory.from_config(self.cfg) eq_(script.get_heads(), []) self.cfg.set_main_option("sourceless", "true") script = ScriptDirectory.from_config(self.cfg) eq_(script.get_heads(), [a])
def test_attributes_attr(self): m1 = Mock() cfg = config.Config() cfg.attributes['connection'] = m1 eq_( cfg.attributes['connection'], m1 )
def test_render_nothing(self): context = MigrationContext.configure( connection=self.bind.connect(), opts={ 'compare_type': True, 'compare_server_default': True, 'target_metadata': self.m1, 'upgrade_token': "upgrades", 'downgrade_token': "downgrades", 'alembic_module_prefix': 'op.', 'sqlalchemy_module_prefix': 'sa.', } ) template_args = {} autogenerate._produce_migration_diffs( context, template_args, set(), include_symbol=lambda name, schema: False ) eq_(re.sub(r"u'", "'", template_args['upgrades']), """### commands auto generated by Alembic - please adjust! ### pass ### end Alembic commands ###""") eq_(re.sub(r"u'", "'", template_args['downgrades']), """### commands auto generated by Alembic - please adjust! ### pass ### end Alembic commands ###""")
def test_option(self): self.cfg.set_main_option("file_template", "myfile_%%(slug)s") script = ScriptDirectory.from_config(self.cfg) a = util.rev_id() script.generate_revision(a, "some message", refresh=True) write_script(script, a, """ revision = '%s' down_revision = None from alembic import op def upgrade(): op.execute("CREATE TABLE foo(id integer)") def downgrade(): op.execute("DROP TABLE foo") """ % a) script = ScriptDirectory.from_config(self.cfg) rev = script.get_revision(a) eq_(rev.revision, a) eq_(os.path.basename(rev.path), "myfile_some_message.py")
def test_change_unique(self): m1 = MetaData() m2 = MetaData() Table( 't', m1, Column('x', Integer), Column('y', Integer), Column('z', Integer), UniqueConstraint('x', name='uq1'), UniqueConstraint('y', name='uq2') ) Table( 't', m2, Column('x', Integer), Column('y', Integer), Column('z', Integer), UniqueConstraint('x', 'z', name='uq1'), UniqueConstraint('y', 'z', name='uq2') ) def include_object(object_, name, type_, reflected, compare_to): if type_ == 'index': return False return not ( isinstance(object_, UniqueConstraint) and type_ == 'unique_constraint' and not reflected and name == 'uq1' and isinstance(compare_to, UniqueConstraint)) diffs = self._fixture(m1, m2, object_filters=[include_object]) eq_(diffs[0][0], 'remove_constraint') eq_(diffs[0][1].name, 'uq2') eq_(diffs[1][0], 'add_constraint') eq_(diffs[1][1].name, 'uq2') eq_(len(diffs), 2)
def test_all_traverse(self): writer = autogenerate.Rewriter() mocker = mock.Mock(side_effect=lambda context, revision, op: op) writer.rewrites(ops.MigrateOperation)(mocker) addcolop = ops.AddColumnOp( 't1', sa.Column('x', sa.Integer()) ) directives = [ ops.MigrationScript( util.rev_id(), ops.UpgradeOps(ops=[ ops.ModifyTableOps('t1', ops=[ addcolop ]) ]), ops.DowngradeOps(ops=[ ]), ) ] ctx, rev = mock.Mock(), mock.Mock() writer(ctx, rev, directives) eq_( mocker.mock_calls, [ mock.call(ctx, rev, directives[0]), mock.call(ctx, rev, directives[0].upgrade_ops), mock.call(ctx, rev, directives[0].upgrade_ops.ops[0]), mock.call(ctx, rev, addcolop), mock.call(ctx, rev, directives[0].downgrade_ops), ] )
def test_unnamed_cols_changed(self): m1 = MetaData() m2 = MetaData() Table('col_change', m1, Column('x', Integer), Column('y', Integer), UniqueConstraint('x') ) Table('col_change', m2, Column('x', Integer), Column('y', Integer), UniqueConstraint('x', 'y') ) diffs = self._fixture(m1, m2) diffs = set((cmd, ('x' in obj.name) if obj.name is not None else False) for cmd, obj in diffs) if self.reports_unnamed_constraints: if self.reports_unique_constraints_as_indexes: eq_( diffs, set([("remove_index", True), ("add_constraint", False)]) ) else: eq_( diffs, set([("remove_constraint", True), ("add_constraint", False)]) )
def test_change_index(self): m1 = MetaData() m2 = MetaData() t1 = Table( 't', m1, Column('x', Integer), Column('y', Integer), Column('z', Integer)) Index('ix1', t1.c.x) Index('ix2', t1.c.y) t2 = Table( 't', m2, Column('x', Integer), Column('y', Integer), Column('z', Integer)) Index('ix1', t2.c.x, t2.c.y) Index('ix2', t2.c.x, t2.c.z) def include_object(object_, name, type_, reflected, compare_to): return not ( isinstance(object_, Index) and type_ == 'index' and not reflected and name == 'ix1' and isinstance(compare_to, Index)) diffs = self._fixture(m1, m2, object_filters=[include_object]) eq_(diffs[0][0], 'remove_index') eq_(diffs[0][1].name, 'ix2') eq_(diffs[1][0], 'add_index') eq_(diffs[1][1].name, 'ix2') eq_(len(diffs), 2)
def test_nothing_changed_implicit_fk_index_named(self): m1 = MetaData() m2 = MetaData() Table("nothing_changed", m1, Column('id', Integer, primary_key=True), Column('other_id', ForeignKey('nc2.id', name='fk_my_table_other_table' ), nullable=False), Column('foo', Integer), mysql_engine='InnoDB') Table('nc2', m1, Column('id', Integer, primary_key=True), mysql_engine='InnoDB') Table("nothing_changed", m2, Column('id', Integer, primary_key=True), Column('other_id', ForeignKey('nc2.id', name='fk_my_table_other_table'), nullable=False), Column('foo', Integer), mysql_engine='InnoDB') Table('nc2', m2, Column('id', Integer, primary_key=True), mysql_engine='InnoDB') diffs = self._fixture(m1, m2) eq_(diffs, [])
def test_nothing_changed_implicit_composite_fk_index_named(self): m1 = MetaData() m2 = MetaData() Table("nothing_changed", m1, Column('id', Integer, primary_key=True), Column('other_id_1', Integer), Column('other_id_2', Integer), Column('foo', Integer), ForeignKeyConstraint( ['other_id_1', 'other_id_2'], ['nc2.id1', 'nc2.id2'], name='fk_my_table_other_table' ), mysql_engine='InnoDB') Table('nc2', m1, Column('id1', Integer, primary_key=True), Column('id2', Integer, primary_key=True), mysql_engine='InnoDB') Table("nothing_changed", m2, Column('id', Integer, primary_key=True), Column('other_id_1', Integer), Column('other_id_2', Integer), Column('foo', Integer), ForeignKeyConstraint( ['other_id_1', 'other_id_2'], ['nc2.id1', 'nc2.id2'], name='fk_my_table_other_table' ), mysql_engine='InnoDB') Table('nc2', m2, Column('id1', Integer, primary_key=True), Column('id2', Integer, primary_key=True), mysql_engine='InnoDB') diffs = self._fixture(m1, m2) eq_(diffs, [])
def get_revision(self): result = self.connection.execute(version_table.select()) rows = result.fetchall() if len(rows) == 0: return None eq_(len(rows), 1) return rows[0]['version_num']
def test_stamp_existing_downgrade(self): command.stamp(self.cfg, self.b) command.stamp(self.cfg, self.a) eq_( self.bind.scalar("select version_num from alembic_version"), self.a )
def test_create_rev_depends_on_branch_label(self): self._env_fixture() command.revision(self.cfg) rev2 = command.revision(self.cfg, branch_label="foobar") rev3 = command.revision(self.cfg, depends_on="foobar") eq_(rev3.dependencies, "foobar") eq_(rev3._resolved_dependencies, (rev2.revision,))
def test_run_cmd_args_missing(self): canary = mock.Mock() orig_revision = command.revision # the command function has "process_revision_directives" # however the ArgumentParser does not. ensure things work def revision( config, message=None, autogenerate=False, sql=False, head="head", splice=False, branch_label=None, version_path=None, rev_id=None, depends_on=None, process_revision_directives=None, ): canary(config, message=message) revision.__module__ = "alembic.command" # CommandLine() pulls the function into the ArgumentParser # and needs the full signature, so we can't patch the "revision" # command normally as ArgumentParser gives us no way to get to it. config.command.revision = revision try: commandline = config.CommandLine() options = commandline.parser.parse_args(["revision", "-m", "foo"]) commandline.run_cmd(self.cfg, options) finally: config.command.revision = orig_revision eq_(canary.mock_calls, [mock.call(self.cfg, message="foo")])
def test_functional_ix_one(self): m1 = MetaData() m2 = MetaData() t1 = Table( "foo", m1, Column("id", Integer, primary_key=True), Column("email", String(50)), ) Index("email_idx", func.lower(t1.c.email), unique=True) t2 = Table( "foo", m2, Column("id", Integer, primary_key=True), Column("email", String(50)), ) Index("email_idx", func.lower(t2.c.email), unique=True) with assertions.expect_warnings( "Skipped unsupported reflection", "autogenerate skipping functional index", ): diffs = self._fixture(m1, m2) eq_(diffs, [])
def test_upgrade_downgrade_ops_list_accessors(self): u1 = ops.UpgradeOps(ops=[]) d1 = ops.DowngradeOps(ops=[]) m1 = ops.MigrationScript( "somerev", u1, d1 ) is_( m1.upgrade_ops, u1 ) is_( m1.downgrade_ops, d1 ) u2 = ops.UpgradeOps(ops=[]) d2 = ops.DowngradeOps(ops=[]) m1._upgrade_ops.append(u2) m1._downgrade_ops.append(d2) assert_raises_message( ValueError, "This MigrationScript instance has a multiple-entry list for " "UpgradeOps; please use the upgrade_ops_list attribute.", getattr, m1, "upgrade_ops" ) assert_raises_message( ValueError, "This MigrationScript instance has a multiple-entry list for " "DowngradeOps; please use the downgrade_ops_list attribute.", getattr, m1, "downgrade_ops" ) eq_(m1.upgrade_ops_list, [u1, u2]) eq_(m1.downgrade_ops_list, [d1, d2])
def test_upgrade(self): head = HeadMaintainer(mock.Mock(), [self.a.revision]) """ upgrade a -> b2, b2 upgrade a -> b3, b3 upgrade b2, b3 -> c2, c2 upgrade c2 -> d2, d2 upgrade a -> b1, b1 upgrade b1, b2 -> c1, c1 upgrade c1 -> d1, d1 """ steps = [ (self.up_(self.b2), ('b2',)), (self.up_(self.b3), ('b2', 'b3',)), (self.up_(self.c2), ('c2',)), (self.up_(self.d2), ('d2',)), (self.up_(self.b1), ('b1', 'd2',)), (self.up_(self.c1), ('c1', 'd2')), (self.up_(self.d1), ('d1', 'd2')), ] for step, assert_ in steps: head.update_to_step(step) eq_(head.heads, set(assert_))
def test_stamp_to_heads(self): revs = self.env._stamp_revs("heads", ()) eq_(len(revs), 2) eq_( set(r.to_revisions for r in revs), set([(self.b1.revision,), (self.b2.revision,)]) )
def test_ondelete_onupdate_combo(self): diffs = self._fk_opts_fixture( {"onupdate": "CASCADE", "ondelete": "SET NULL"}, {"onupdate": "RESTRICT", "ondelete": "RESTRICT"}, ) if self._expect_opts_supported(): self._assert_fk_diff( diffs[0], "remove_fk", "user", ["tid"], "some_table", ["id"], onupdate="CASCADE", ondelete="SET NULL", conditional_name="servergenerated", ) self._assert_fk_diff( diffs[1], "add_fk", "user", ["tid"], "some_table", ["id"], onupdate="RESTRICT", ondelete="RESTRICT", ) else: eq_(diffs, [])
def test_render_quoted_server_default(self): eq_( autogenerate.render._render_server_default( "nextval('group_to_perm_group_to_perm_id_seq'::regclass)", self.autogen_context), '"nextval(\'group_to_perm_group_to_perm_id_seq\'::regclass)"' )
def test_named_cols_changed(self): m1 = MetaData() m2 = MetaData() Table( "col_change", m1, Column("x", Integer), Column("y", Integer), UniqueConstraint("x", name="nochange"), ) Table( "col_change", m2, Column("x", Integer), Column("y", Integer), UniqueConstraint("x", "y", name="nochange"), ) diffs = self._fixture(m1, m2) if self.reports_unique_constraints: eq_(diffs[0][0], "remove_constraint") eq_(diffs[0][1].name, "nochange") eq_(diffs[1][0], "add_constraint") eq_(diffs[1][1].name, "nochange") else: eq_(diffs, [])
def test_change_onupdate_from_restrict(self): """test the RESTRICT option which MySQL doesn't report on""" # note that this is impossible to detect if we change # from RESTRICT to NO ACTION on MySQL. diffs = self._fk_opts_fixture( {"onupdate": "restrict"}, {"onupdate": "cascade"} ) if self._expect_opts_supported(): self._assert_fk_diff( diffs[0], "remove_fk", "user", ["tid"], "some_table", ["id"], onupdate=mock.ANY, # MySQL reports None, PG reports RESTRICT ondelete=None, conditional_name="servergenerated", ) self._assert_fk_diff( diffs[1], "add_fk", "user", ["tid"], "some_table", ["id"], onupdate="cascade", ondelete=None, ) else: eq_(diffs, [])
def test_lookup_legacy(self): self.cfg.set_main_option("file_template", "%%(rev)s") script = ScriptDirectory.from_config(self.cfg) a = util.rev_id() script.generate_revision(a, None, refresh=True) write_script( script, a, """ down_revision = None from alembic import op def upgrade(): op.execute("CREATE TABLE foo(id integer)") def downgrade(): op.execute("DROP TABLE foo") """, ) script = ScriptDirectory.from_config(self.cfg) rev = script.get_revision(a) eq_(rev.revision, a) eq_(os.path.basename(rev.path), "%s.py" % a)
def test_what_are_the_heads(self): eq_(self.map.heads, ("b1a", "b1b", "d2", "b3"))
def test_create_script_basic(self): rev = command.revision(self.cfg, message="some message") script = ScriptDirectory.from_config(self.cfg) rev = script.get_revision(rev.revision) eq_(rev.down_revision, self.c) assert "some message" in rev.doc
def _assert_data(self, data, tablename='foo'): eq_([ dict(row) for row in self.conn.execute("select * from %s" % tablename) ], data)
def test_rename_col(self): impl = self._simple_fixture() impl.alter_column('tname', 'x', name='q') new_table = self._assert_impl(impl) eq_(new_table.c.x.name, 'q')
def _assert_impl(self, impl, colnames=None, ddl_contains=None, ddl_not_contains=None, dialect='default', schema=None): context = op_fixture(dialect=dialect) impl._create(context.impl) if colnames is None: colnames = ['id', 'x', 'y'] eq_(impl.new_table.c.keys(), colnames) pk_cols = [col for col in impl.new_table.c if col.primary_key] eq_(list(impl.new_table.primary_key), pk_cols) create_stmt = str( CreateTable(impl.new_table).compile(dialect=context.dialect)) create_stmt = re.sub(r'[\n\t]', '', create_stmt) idx_stmt = "" for idx in impl.new_table.indexes: idx_stmt += str(CreateIndex(idx).compile(dialect=context.dialect)) idx_stmt = re.sub(r'[\n\t]', '', idx_stmt) if ddl_contains: assert ddl_contains in create_stmt + idx_stmt if ddl_not_contains: assert ddl_not_contains not in create_stmt + idx_stmt expected = [ create_stmt, ] if impl.new_table.indexes: expected.append(idx_stmt) if schema: args = {"schema": "%s." % schema} else: args = {"schema": ""} args['colnames'] = ", ".join([ impl.new_table.c[name].name for name in colnames if name in impl.table.c ]) args['tname_colnames'] = ", ".join( "CAST(%(schema)stname.%(name)s AS %(type)s) AS anon_1" % { 'schema': args['schema'], 'name': name, 'type': impl.new_table.c[name].type } if ( impl.new_table.c[name].type._type_affinity is not impl.table. c[name].type._type_affinity) else "%(schema)stname.%(name)s" % { 'schema': args['schema'], 'name': name } for name in colnames if name in impl.table.c) expected.extend([ 'INSERT INTO %(schema)s_alembic_batch_temp (%(colnames)s) ' 'SELECT %(tname_colnames)s FROM %(schema)stname' % args, 'DROP TABLE %(schema)stname' % args, 'ALTER TABLE %(schema)s_alembic_batch_temp ' 'RENAME TO %(schema)stname' % args ]) context.assert_(*expected) return impl.new_table
def test_stamp_existing_downgrade(self): command.stamp(self.cfg, self.b) command.stamp(self.cfg, self.a) eq_(self.bind.scalar("select version_num from alembic_version"), self.a)
def test_stamp_creates_table(self): command.stamp(self.cfg, "head") eq_(self.bind.scalar("select version_num from alembic_version"), self.b)
def test_not_actually_a_branch(self): eq_(self.map.get_revision("e@d").revision, "d")
def test_retrieve_branch_revision(self): eq_(self.map.get_revision("abranch").revision, "a") eq_(self.map.get_revision("ebranch").revision, "e")
def test_not_actually_a_branch_partial_resolution(self): eq_(self.map.get_revision("someoth@d").revision, "d")
def test_branch_at_syntax(self): eq_(self.map.get_revision("abranch@head").revision, "c") eq_(self.map.get_revision("abranch@base"), None) eq_(self.map.get_revision("ebranch@head").revision, "f") eq_(self.map.get_revision("abranch@base"), None) eq_(self.map.get_revision("ebranch@d").revision, "d")
def test_actually_short_rev_name(self): eq_(self.map.get_revision("e").revision, "e")
def test_partial_id_resolve(self): eq_(self.map.get_revision("ebranch@some").revision, "someothername") eq_(self.map.get_revision("abranch@some").revision, "somelongername")
def test_branch_at_self(self): eq_(self.map.get_revision("ebranch@ebranch").revision, "e")
def test_get_current_named_rev(self): eq_(self.map.get_revision("ebranch@head"), self.map.get_revision("f"))
def test_branch_at_heads(self): eq_(self.map.get_revision("abranch@heads").revision, "c")
def test_filter_for_lineage_heads(self): eq_( self.map.filter_for_lineage([self.map.get_revision("f")], "heads"), [self.map.get_revision("f")], )
def test_get_base_revisions(self): eq_(self.map._get_base_revisions("base"), ["a", "d"])
def test_get_current_revision_doesnt_create_version_table(self): context = self.make_one(connection=self.connection, opts={'version_table': 'version_table'}) eq_(context.get_current_revision(), None) insp = Inspector(self.connection) assert ('version_table' not in insp.get_table_names())
def test_get_base_revisions_labeled(self): eq_(self.map._get_base_revisions("somelongername@base"), ["a"])
def test_config_explicit_no_pk(self): context = self.make_one(dialect_name='sqlite', opts={'version_table_pk': False}) eq_(len(context._version.primary_key), 0)
def test_what_are_the_heads(self): eq_(self.map.heads, ("c1", "d2", "d3"))
def test_config_explicit_version_table_name(self): context = self.make_one(dialect_name='sqlite', opts={'version_table': 'explicit'}) eq_(context._version.name, 'explicit') eq_(context._version.primary_key.name, 'explicit_pkc')
def test_config_explicit_w_pk(self): context = self.make_one(dialect_name='sqlite', opts={'version_table_pk': True}) eq_(len(context._version.primary_key), 1) eq_(context._version.primary_key.name, "alembic_version_pkc")
def _assert_heads(self, heads): eq_(set(self.context.get_current_heads()), set(heads)) eq_(self.updater.heads, set(heads))
def test_config_explicit_version_table_schema(self): context = self.make_one(dialect_name='sqlite', opts={'version_table_schema': 'explicit'}) eq_(context._version.schema, 'explicit')
def test_stamp_across_dependency(self): heads = [self.e1.revision, self.c2.revision] head = HeadMaintainer(mock.Mock(), heads) for step in self.env._stamp_revs(self.b1.revision, heads): head.update_to_step(step) eq_(head.heads, set([self.b1.revision]))
def test_config_default_version_table_name(self): context = self.make_one(dialect_name='sqlite') eq_(context._version.name, 'alembic_version')
def test_downgrade_to_dependency(self): heads = [self.c2.revision, self.d1.revision] head = HeadMaintainer(mock.Mock(), heads) head.update_to_step(self.down_(self.d1)) eq_(head.heads, set([self.c2.revision]))
def test_current_obfuscate_password(self): eq_( util.obfuscate_url_pw("postgresql://*****:*****@localhost/test"), "postgresql://*****:*****@localhost/test", )