diff --git a/alembic/autogenerate/api.py b/alembic/autogenerate/api.py index 9a3b003b..95fdedea 100644 --- a/alembic/autogenerate/api.py +++ b/alembic/autogenerate/api.py @@ -55,36 +55,42 @@ def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any: from alembic.migration import MigrationContext from alembic.autogenerate import compare_metadata - from sqlalchemy.schema import SchemaItem - from sqlalchemy.types import TypeEngine - from sqlalchemy import (create_engine, MetaData, Column, - Integer, String, Table, text) + from sqlalchemy import ( + create_engine, + MetaData, + Column, + Integer, + String, + Table, + text, + ) import pprint engine = create_engine("sqlite://") with engine.begin() as conn: - conn.execute(text(''' - create table foo ( - id integer not null primary key, - old_data varchar, - x integer - )''')) - - conn.execute(text(''' - create table bar ( - data varchar - )''')) + conn.execute( + text( + ''' + create table foo ( + id integer not null primary key, + old_data varchar, + x integer + ) + ''' + ) + ) + conn.execute(text("create table bar (data varchar)")) metadata = MetaData() - Table('foo', metadata, - Column('id', Integer, primary_key=True), - Column('data', Integer), - Column('x', Integer, nullable=False) - ) - Table('bat', metadata, - Column('info', String) + Table( + "foo", + metadata, + Column("id", Integer, primary_key=True), + Column("data", Integer), + Column("x", Integer, nullable=False), ) + Table("bat", metadata, Column("info", String)) mc = MigrationContext.configure(engine.connect()) @@ -93,29 +99,53 @@ def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any: Output:: - [ ( 'add_table', - Table('bat', MetaData(bind=None), - Column('info', String(), table=), schema=None)), - ( 'remove_table', - Table(u'bar', MetaData(bind=None), - Column(u'data', VARCHAR(), table=), schema=None)), - ( 'add_column', - None, - 'foo', - Column('data', Integer(), table=)), - ( 'remove_column', - None, - 'foo', - Column(u'old_data', VARCHAR(), table=None)), - [ ( 'modify_nullable', - None, - 'foo', - u'x', - { 'existing_server_default': None, - 'existing_type': INTEGER()}, - True, - False)]] - + [ + ( + "add_table", + Table( + "bat", + MetaData(), + Column("info", String(), table=), + schema=None, + ), + ), + ( + "remove_table", + Table( + "bar", + MetaData(), + Column("data", VARCHAR(), table=), + schema=None, + ), + ), + ( + "add_column", + None, + "foo", + Column("data", Integer(), table=), + ), + [ + ( + "modify_nullable", + None, + "foo", + "x", + { + "existing_comment": None, + "existing_server_default": False, + "existing_type": INTEGER(), + }, + True, + False, + ) + ], + ( + "remove_column", + None, + "foo", + Column("old_data", VARCHAR(), table=), + ), + ] :param context: a :class:`.MigrationContext` instance. diff --git a/alembic/autogenerate/rewriter.py b/alembic/autogenerate/rewriter.py index 1a29b963..4209c321 100644 --- a/alembic/autogenerate/rewriter.py +++ b/alembic/autogenerate/rewriter.py @@ -66,19 +66,19 @@ def chain(self, other: Rewriter) -> Rewriter: writer1 = autogenerate.Rewriter() writer2 = autogenerate.Rewriter() + @writer1.rewrites(ops.AddColumnOp) def add_column_nullable(context, revision, op): op.column.nullable = True return op + @writer2.rewrites(ops.AddColumnOp) def add_column_idx(context, revision, op): idx_op = ops.CreateIndexOp( - 'ixc', op.table_name, [op.column.name]) - return [ - op, - idx_op - ] + "ixc", op.table_name, [op.column.name] + ) + return [op, idx_op] writer = writer1.chain(writer2) diff --git a/alembic/ddl/postgresql.py b/alembic/ddl/postgresql.py index 247838bf..5fb981dc 100644 --- a/alembic/ddl/postgresql.py +++ b/alembic/ddl/postgresql.py @@ -566,11 +566,9 @@ def create_exclude_constraint( op.create_exclude_constraint( "user_excl", "user", - - ("period", '&&'), - ("group", '='), - where=("group != 'some group'") - + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), ) Note that the expressions work the same way as that of diff --git a/alembic/op.pyi b/alembic/op.pyi index dab58568..229fff32 100644 --- a/alembic/op.pyi +++ b/alembic/op.pyi @@ -51,9 +51,7 @@ def add_column( from alembic import op from sqlalchemy import Column, String - op.add_column('organization', - Column('name', String()) - ) + op.add_column("organization", Column("name", String())) The provided :class:`~sqlalchemy.schema.Column` object can also specify a :class:`~sqlalchemy.schema.ForeignKey`, referencing @@ -64,8 +62,9 @@ def add_column( from alembic import op from sqlalchemy import Column, INTEGER, ForeignKey - op.add_column('organization', - Column('account_id', INTEGER, ForeignKey('accounts.id')) + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), ) Note that this statement uses the :class:`~sqlalchemy.schema.Column` @@ -78,8 +77,9 @@ def add_column( from sqlalchemy import Column, TIMESTAMP, func # specify "DEFAULT NOW" along with the column add - op.add_column('account', - Column('timestamp', TIMESTAMP, server_default=func.now()) + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), ) :param table_name: String name of the parent table. @@ -241,8 +241,8 @@ def batch_alter_table( are omitted. E.g.:: with op.batch_alter_table("some_table") as batch_op: - batch_op.add_column(Column('foo', Integer)) - batch_op.drop_column('bar') + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") The operations within the context manager are invoked at once when the context is ended. When run against SQLite, if the @@ -321,16 +321,18 @@ def batch_alter_table( Specify the order of all columns:: with op.batch_alter_table( - "some_table", recreate="always", - partial_reordering=[("c", "d", "a", "b")] + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], ) as batch_op: pass Ensure "d" appears before "c", and "b", appears before "a":: with op.batch_alter_table( - "some_table", recreate="always", - partial_reordering=[("d", "c"), ("b", "a")] + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], ) as batch_op: pass @@ -370,37 +372,58 @@ def bulk_insert( from sqlalchemy import String, Integer, Date # Create an ad-hoc table to use for the insert statement. - accounts_table = table('account', - column('id', Integer), - column('name', String), - column('create_date', Date) + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), ) - op.bulk_insert(accounts_table, + op.bulk_insert( + accounts_table, [ - {'id':1, 'name':'John Smith', - 'create_date':date(2010, 10, 5)}, - {'id':2, 'name':'Ed Williams', - 'create_date':date(2007, 5, 27)}, - {'id':3, 'name':'Wendy Jones', - 'create_date':date(2008, 8, 15)}, - ] + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], ) When using --sql mode, some datatypes may not render inline automatically, such as dates and other special types. When this issue is present, :meth:`.Operations.inline_literal` may be used:: - op.bulk_insert(accounts_table, + op.bulk_insert( + accounts_table, [ - {'id':1, 'name':'John Smith', - 'create_date':op.inline_literal("2010-10-05")}, - {'id':2, 'name':'Ed Williams', - 'create_date':op.inline_literal("2007-05-27")}, - {'id':3, 'name':'Wendy Jones', - 'create_date':op.inline_literal("2008-08-15")}, + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, ], - multiinsert=False + multiinsert=False, ) When using :meth:`.Operations.inline_literal` in conjunction with @@ -446,7 +469,7 @@ def create_check_constraint( op.create_check_constraint( "ck_user_name_len", "user", - func.len(column('name')) > 5 + func.len(column("name")) > 5, ) CHECK constraints are usually against a SQL expression, so ad-hoc @@ -492,11 +515,9 @@ def create_exclude_constraint( op.create_exclude_constraint( "user_excl", "user", - - ("period", '&&'), - ("group", '='), - where=("group != 'some group'") - + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), ) Note that the expressions work the same way as that of @@ -537,9 +558,14 @@ def create_foreign_key( e.g.:: from alembic import op + op.create_foreign_key( - "fk_user_address", "address", - "user", ["user_id"], ["id"]) + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) This internally generates a :class:`~sqlalchemy.schema.Table` object containing the necessary columns, then generates a new @@ -590,14 +616,16 @@ def create_index( e.g.:: from alembic import op - op.create_index('ik_test', 't1', ['foo', 'bar']) + + op.create_index("ik_test", "t1", ["foo", "bar"]) Functional indexes can be produced by using the :func:`sqlalchemy.sql.expression.text` construct:: from alembic import op from sqlalchemy import text - op.create_index('ik_test', 't1', [text('lower(foo)')]) + + op.create_index("ik_test", "t1", [text("lower(foo)")]) :param index_name: name of the index. :param table_name: name of the owning table. @@ -638,10 +666,10 @@ def create_primary_key( e.g.:: from alembic import op + op.create_primary_key( - "pk_my_table", "my_table", - ["id", "version"] - ) + "pk_my_table", "my_table", ["id", "version"] + ) This internally generates a :class:`~sqlalchemy.schema.Table` object containing the necessary columns, then generates a new @@ -683,11 +711,11 @@ def create_table( from alembic import op op.create_table( - 'account', - Column('id', INTEGER, primary_key=True), - Column('name', VARCHAR(50), nullable=False), - Column('description', NVARCHAR(200)), - Column('timestamp', TIMESTAMP, server_default=func.now()) + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), ) Note that :meth:`.create_table` accepts @@ -716,11 +744,11 @@ def create_table( from alembic import op account_table = op.create_table( - 'account', - Column('id', INTEGER, primary_key=True), - Column('name', VARCHAR(50), nullable=False), - Column('description', NVARCHAR(200)), - Column('timestamp', TIMESTAMP, server_default=func.now()) + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), ) op.bulk_insert( @@ -728,7 +756,7 @@ def create_table( [ {"name": "A1", "description": "account 1"}, {"name": "A2", "description": "account 2"}, - ] + ], ) :param table_name: Name of the table @@ -828,7 +856,7 @@ def drop_column( e.g.:: - drop_column('organization', 'account_id') + drop_column("organization", "account_id") :param table_name: name of table :param column_name: name of column @@ -1050,7 +1078,7 @@ def f(name: str) -> conv: If the :meth:`.Operations.f` is used on a constraint, the naming convention will not take effect:: - op.add_column('t', 'x', Boolean(name=op.f('ck_bool_t_x'))) + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) Above, the CHECK constraint generated will have the name ``ck_bool_t_x`` regardless of whether or not a naming convention is @@ -1062,7 +1090,7 @@ def f(name: str) -> conv: ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the output of the following: - op.add_column('t', 'x', Boolean(name='x')) + op.add_column("t", "x", Boolean(name="x")) will be:: diff --git a/alembic/operations/base.py b/alembic/operations/base.py index 82d97792..68f8c595 100644 --- a/alembic/operations/base.py +++ b/alembic/operations/base.py @@ -260,8 +260,8 @@ def batch_alter_table( are omitted. E.g.:: with op.batch_alter_table("some_table") as batch_op: - batch_op.add_column(Column('foo', Integer)) - batch_op.drop_column('bar') + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") The operations within the context manager are invoked at once when the context is ended. When run against SQLite, if the @@ -340,16 +340,18 @@ def batch_alter_table( Specify the order of all columns:: with op.batch_alter_table( - "some_table", recreate="always", - partial_reordering=[("c", "d", "a", "b")] + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], ) as batch_op: pass Ensure "d" appears before "c", and "b", appears before "a":: with op.batch_alter_table( - "some_table", recreate="always", - partial_reordering=[("d", "c"), ("b", "a")] + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], ) as batch_op: pass @@ -415,7 +417,7 @@ def f(self, name: str) -> conv: If the :meth:`.Operations.f` is used on a constraint, the naming convention will not take effect:: - op.add_column('t', 'x', Boolean(name=op.f('ck_bool_t_x'))) + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) Above, the CHECK constraint generated will have the name ``ck_bool_t_x`` regardless of whether or not a naming convention is @@ -427,7 +429,7 @@ def f(self, name: str) -> conv: ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the output of the following: - op.add_column('t', 'x', Boolean(name='x')) + op.add_column("t", "x", Boolean(name="x")) will be:: diff --git a/alembic/operations/ops.py b/alembic/operations/ops.py index 7dd65a1f..aab45d4c 100644 --- a/alembic/operations/ops.py +++ b/alembic/operations/ops.py @@ -307,10 +307,10 @@ def create_primary_key( e.g.:: from alembic import op + op.create_primary_key( - "pk_my_table", "my_table", - ["id", "version"] - ) + "pk_my_table", "my_table", ["id", "version"] + ) This internally generates a :class:`~sqlalchemy.schema.Table` object containing the necessary columns, then generates a new @@ -608,9 +608,14 @@ def create_foreign_key( e.g.:: from alembic import op + op.create_foreign_key( - "fk_user_address", "address", - "user", ["user_id"], ["id"]) + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) This internally generates a :class:`~sqlalchemy.schema.Table` object containing the necessary columns, then generates a new @@ -690,8 +695,11 @@ def batch_create_foreign_key( with batch_alter_table("address") as batch_op: batch_op.create_foreign_key( - "fk_user_address", - "user", ["user_id"], ["id"]) + "fk_user_address", + "user", + ["user_id"], + ["id"], + ) .. seealso:: @@ -790,7 +798,7 @@ def create_check_constraint( op.create_check_constraint( "ck_user_name_len", "user", - func.len(column('name')) > 5 + func.len(column("name")) > 5, ) CHECK constraints are usually against a SQL expression, so ad-hoc @@ -922,14 +930,16 @@ def create_index( e.g.:: from alembic import op - op.create_index('ik_test', 't1', ['foo', 'bar']) + + op.create_index("ik_test", "t1", ["foo", "bar"]) Functional indexes can be produced by using the :func:`sqlalchemy.sql.expression.text` construct:: from alembic import op from sqlalchemy import text - op.create_index('ik_test', 't1', [text('lower(foo)')]) + + op.create_index("ik_test", "t1", [text("lower(foo)")]) :param index_name: name of the index. :param table_name: name of the owning table. @@ -1186,11 +1196,11 @@ def create_table( from alembic import op op.create_table( - 'account', - Column('id', INTEGER, primary_key=True), - Column('name', VARCHAR(50), nullable=False), - Column('description', NVARCHAR(200)), - Column('timestamp', TIMESTAMP, server_default=func.now()) + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), ) Note that :meth:`.create_table` accepts @@ -1219,11 +1229,11 @@ def create_table( from alembic import op account_table = op.create_table( - 'account', - Column('id', INTEGER, primary_key=True), - Column('name', VARCHAR(50), nullable=False), - Column('description', NVARCHAR(200)), - Column('timestamp', TIMESTAMP, server_default=func.now()) + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), ) op.bulk_insert( @@ -1231,7 +1241,7 @@ def create_table( [ {"name": "A1", "description": "account 1"}, {"name": "A2", "description": "account 2"}, - ] + ], ) :param table_name: Name of the table @@ -2001,9 +2011,7 @@ def add_column( from alembic import op from sqlalchemy import Column, String - op.add_column('organization', - Column('name', String()) - ) + op.add_column("organization", Column("name", String())) The provided :class:`~sqlalchemy.schema.Column` object can also specify a :class:`~sqlalchemy.schema.ForeignKey`, referencing @@ -2014,8 +2022,9 @@ def add_column( from alembic import op from sqlalchemy import Column, INTEGER, ForeignKey - op.add_column('organization', - Column('account_id', INTEGER, ForeignKey('accounts.id')) + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), ) Note that this statement uses the :class:`~sqlalchemy.schema.Column` @@ -2028,8 +2037,9 @@ def add_column( from sqlalchemy import Column, TIMESTAMP, func # specify "DEFAULT NOW" along with the column add - op.add_column('account', - Column('timestamp', TIMESTAMP, server_default=func.now()) + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), ) :param table_name: String name of the parent table. @@ -2152,7 +2162,7 @@ def drop_column( e.g.:: - drop_column('organization', 'account_id') + drop_column("organization", "account_id") :param table_name: name of table :param column_name: name of column @@ -2247,37 +2257,58 @@ def bulk_insert( from sqlalchemy import String, Integer, Date # Create an ad-hoc table to use for the insert statement. - accounts_table = table('account', - column('id', Integer), - column('name', String), - column('create_date', Date) + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), ) - op.bulk_insert(accounts_table, + op.bulk_insert( + accounts_table, [ - {'id':1, 'name':'John Smith', - 'create_date':date(2010, 10, 5)}, - {'id':2, 'name':'Ed Williams', - 'create_date':date(2007, 5, 27)}, - {'id':3, 'name':'Wendy Jones', - 'create_date':date(2008, 8, 15)}, - ] + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], ) When using --sql mode, some datatypes may not render inline automatically, such as dates and other special types. When this issue is present, :meth:`.Operations.inline_literal` may be used:: - op.bulk_insert(accounts_table, + op.bulk_insert( + accounts_table, [ - {'id':1, 'name':'John Smith', - 'create_date':op.inline_literal("2010-10-05")}, - {'id':2, 'name':'Ed Williams', - 'create_date':op.inline_literal("2007-05-27")}, - {'id':3, 'name':'Wendy Jones', - 'create_date':op.inline_literal("2008-08-15")}, + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, ], - multiinsert=False + multiinsert=False, ) When using :meth:`.Operations.inline_literal` in conjunction with diff --git a/alembic/runtime/environment.py b/alembic/runtime/environment.py index f5c177e8..71a53091 100644 --- a/alembic/runtime/environment.py +++ b/alembic/runtime/environment.py @@ -108,20 +108,22 @@ class EnvironmentContext(util.ModuleClsProxy): config.set_main_option("script_location", "myapp:migrations") script = ScriptDirectory.from_config(config) + def my_function(rev, context): '''do something with revision "rev", which will be the current database revision, and "context", which is the MigrationContext that the env.py will create''' + with EnvironmentContext( config, script, - fn = my_function, - as_sql = False, - starting_rev = 'base', - destination_rev = 'head', - tag = "sometag" + fn=my_function, + as_sql=False, + starting_rev="base", + destination_rev="head", + tag="sometag", ): script.run_env() diff --git a/alembic/runtime/migration.py b/alembic/runtime/migration.py index cfba0e3e..e67434d0 100644 --- a/alembic/runtime/migration.py +++ b/alembic/runtime/migration.py @@ -99,6 +99,7 @@ class MigrationContext: # from within env.py script from alembic import context + migration_context = context.get_context() For usage outside of an ``env.py`` script, such as for @@ -124,6 +125,7 @@ class MigrationContext: # in any application, outside of the normal Alembic environment from alembic.operations import Operations + op = Operations(context) op.alter_column("mytable", "somecolumn", nullable=True)