Skip to main content
`Ctrl`+`K`

# Operation Directives

Note

this section discusses the internal API of Alembic as regards the internal system of defining migration operation directives. This section is only useful for developers who wish to extend the capabilities of Alembic. For end-user guidance on Alembic migration operations, please see Operation Reference.

Within migration scripts, actual database migration operations are handled via an instance of `Operations`. The `Operations` class lists out available migration operations that are linked to a `MigrationContext`, which communicates instructions originated by the `Operations` object into SQL that is sent to a database or SQL output stream.

Most methods on the `Operations` class are generated dynamically using a “plugin” system, described in the next section Operation Plugins. Additionally, when Alembic migration scripts actually run, the methods on the current `Operations` object are proxied out to the `alembic.op` module, so that they are available using module-style access.

For an overview of how to use an `Operations` object directly in programs, as well as for reference to the standard operation methods as well as “batch” methods, see Operation Reference.

## Operation Plugins

The Operations object is extensible using a plugin system. This system allows one to add new `op.<some_operation>` methods at runtime. The steps to use this system are to first create a subclass of `MigrateOperation`, register it using the `Operations.register_operation()` class decorator, then build a default “implementation” function which is established using the `Operations.implementation_for()` decorator.

Below we illustrate a very simple operation `CreateSequenceOp` which will implement a new method `op.create_sequence()` for use in migration scripts:

    from alembic.operations import Operations, MigrateOperation

    @Operations.register_operation("create_sequence")
    class CreateSequenceOp(MigrateOperation):
        """Create a SEQUENCE."""

        def __init__(self, sequence_name, schema=None):
            self.sequence_name = sequence_name
            self.schema = schema

        @classmethod
        def create_sequence(cls, operations, sequence_name, **kw):
            """Issue a "CREATE SEQUENCE" instruction."""

            op = CreateSequenceOp(sequence_name, **kw)
            return operations.invoke(op)

        def reverse(self):
            # only needed to support autogenerate
            return DropSequenceOp(self.sequence_name, schema=self.schema)

    @Operations.register_operation("drop_sequence")
    class DropSequenceOp(MigrateOperation):
        """Drop a SEQUENCE."""

        def __init__(self, sequence_name, schema=None):
            self.sequence_name = sequence_name
            self.schema = schema

        @classmethod
        def drop_sequence(cls, operations, sequence_name, **kw):
            """Issue a "DROP SEQUENCE" instruction."""

            op = DropSequenceOp(sequence_name, **kw)
            return operations.invoke(op)

        def reverse(self):
            # only needed to support autogenerate
            return CreateSequenceOp(self.sequence_name, schema=self.schema)

Above, the `CreateSequenceOp` and `DropSequenceOp` classes represent new operations that will be available as `op.create_sequence()` and `op.drop_sequence()`. The reason the operations are represented as stateful classes is so that an operation and a specific set of arguments can be represented generically; the state can then correspond to different kinds of operations, such as invoking the instruction against a database, or autogenerating Python code for the operation into a script.

In order to establish the migrate-script behavior of the new operations, we use the `Operations.implementation_for()` decorator:

    @Operations.implementation_for(CreateSequenceOp)
    def create_sequence(operations, operation):
        if operation.schema is not None:
            name = "%s.%s" % (operation.schema, operation.sequence_name)
        else:
            name = operation.sequence_name
        operations.execute("CREATE SEQUENCE %s" % name)

    @Operations.implementation_for(DropSequenceOp)
    def drop_sequence(operations, operation):
        if operation.schema is not None:
            name = "%s.%s" % (operation.schema, operation.sequence_name)
        else:
            name = operation.sequence_name
        operations.execute("DROP SEQUENCE %s" % name)

Above, we use the simplest possible technique of invoking our DDL, which is just to call `Operations.execute()` with literal SQL. If this is all a custom operation needs, then this is fine. However, options for more comprehensive support include building out a custom SQL construct, as documented at Custom SQL Constructs and Compilation Extension.

With the above two steps, a migration script can now use new methods `op.create_sequence()` and `op.drop_sequence()` that will proxy to our object as a classmethod:

    def upgrade():
        op.create_sequence("my_sequence")

    def downgrade():
        op.drop_sequence("my_sequence")

The registration of new operations only needs to occur in time for the `env.py` script to invoke `MigrationContext.run_migrations()`; within the module level of the `env.py` script is sufficient.

See also

Autogenerating Custom Operation Directives - how to add autogenerate support to custom operations.

## Built-in Operation Objects

The migration operations present on `Operations` are themselves delivered via operation objects that represent an operation and its arguments. All operations descend from the `MigrateOperation` class, and are registered with the `Operations` class using the `Operations.register_operation()` class decorator. The `MigrateOperation` objects also serve as the basis for how the autogenerate system renders new migration scripts.

See also

Operation Plugins

Customizing Revision Generation

The built-in operation objects are listed below.

class alembic.operations.ops.AddColumnOp(table_name: str, column: Column[Any], *, schema: str | None = None, if_not_exists: bool | None = None, inline_references: bool | None = None, inline_primary_key: bool | None = None, **kw: Any)

  Represent an add column operation.

  classmethod add_column(operations: Operations, table_name: str, column: Column[Any], *, schema: str | None = None, if_not_exists: bool | None = None, inline_references: bool | None = None, inline_primary_key: bool | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.add_column()` method.

  classmethod batch_add_column(operations: BatchOperations, column: Column[Any], *, insert_before: str | None = None, insert_after: str | None = None, if_not_exists: bool | None = None, inline_references: bool | None = None, inline_primary_key: bool | None = None) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.add_column()` method.

class alembic.operations.ops.AddConstraintOp

  Represent an add constraint operation.

class alembic.operations.ops.AlterColumnOp(table_name: str, column_name: str, *, schema: str | None = None, existing_type: Any | None = None, existing_server_default: _ServerDefaultArgument = False, existing_nullable: bool | None = None, existing_comment: str | None = None, modify_nullable: bool | None = None, modify_comment: str | Literal[False] | None = False, modify_server_default: Any = False, modify_name: str | None = None, modify_type: Any | None = None, **kw: Any)

  Represent an alter column operation.

  classmethod alter_column(operations: Operations, table_name: str, column_name: str, *, nullable: bool | None = None, comment: str | Literal[False] | None = False, server_default: _ServerDefaultArgument = False, new_column_name: str | None = None, type_: TypeEngine[Any] | type[TypeEngine[Any]] | None = None, existing_type: None | TypeEngine[Any] | type[TypeEngine[Any]] = None, existing_server_default: _ServerDefaultArgument = False, existing_nullable: bool | None = None, existing_comment: str | None = None, schema: str | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.alter_column()` method.

  classmethod batch_alter_column(operations: BatchOperations, column_name: str, *, nullable: bool | None = None, comment: str | Literal[False] | None = False, server_default: _ServerDefaultArgument = False, new_column_name: str | None = None, type_: TypeEngine[Any] | type[TypeEngine[Any]] | None = None, existing_type: None | TypeEngine[Any] | type[TypeEngine[Any]] = None, existing_server_default: _ServerDefaultArgument = False, existing_nullable: bool | None = None, existing_comment: str | None = None, insert_before: str | None = None, insert_after: str | None = None, **kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.alter_column()` method.

class alembic.operations.ops.AlterTableOp(table_name: str, *, schema: str | None = None)

  Represent an alter table operation.

class alembic.operations.ops.BulkInsertOp(table: Table | TableClause, rows: list[dict[str, Any]], *, multiinsert: bool = True)

  Represent a bulk insert operation.

  classmethod bulk_insert(operations: Operations, table: Table | TableClause, rows: list[dict[str, Any]], *, multiinsert: bool = True) → None

    This method is proxied on the `Operations` class, via the `Operations.bulk_insert()` method.

class alembic.operations.ops.CreateCheckConstraintOp(constraint_name: sqla_compat._ConstraintNameDefined | None, table_name: str, condition: str | TextClause | ColumnElement[Any], *, schema: str | None = None, **kw: Any)

  Represent a create check constraint operation.

  classmethod batch_create_check_constraint(operations: BatchOperations, constraint_name: str, condition: str | ColumnElement[bool] | TextClause, **kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_check_constraint()` method.

  classmethod create_check_constraint(operations: Operations, constraint_name: str | None, table_name: str, condition: str | ColumnElement[bool] | TextClause, *, schema: str | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.create_check_constraint()` method.

class alembic.operations.ops.CreateForeignKeyOp(constraint_name: str | _NoneName | None, source_table: str, referent_table: str, local_cols: list[str], remote_cols: list[str], **kw: Any)

  Represent a create foreign key constraint operation.

  classmethod batch_create_foreign_key(operations: BatchOperations, constraint_name: str | None, referent_table: str, local_cols: list[str], remote_cols: list[str], *, referent_schema: str | None = None, onupdate: str | None = None, ondelete: str | None = None, deferrable: bool | None = None, initially: str | None = None, match: str | None = None, **dialect_kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_foreign_key()` method.

  classmethod create_foreign_key(operations: Operations, constraint_name: str | None, source_table: str, referent_table: str, local_cols: list[str], remote_cols: list[str], *, onupdate: str | None = None, ondelete: str | None = None, deferrable: bool | None = None, initially: str | None = None, match: str | None = None, source_schema: str | None = None, referent_schema: str | None = None, **dialect_kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.create_foreign_key()` method.

class alembic.operations.ops.CreateIndexOp(index_name: str | None, table_name: str, columns: Sequence[str | TextClause | ColumnElement[Any]], *, schema: str | None = None, unique: bool = False, if_not_exists: bool | None = None, **kw: Any)

  Represent a create index operation.

  classmethod batch_create_index(operations: BatchOperations, index_name: str, columns: Sequence[str | TextClause | ColumnElement[Any]], **kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_index()` method.

  classmethod create_index(operations: Operations, index_name: str | None, table_name: str, columns: Sequence[str | TextClause | ColumnElement[Any]], *, schema: str | None = None, unique: bool = False, if_not_exists: bool | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.create_index()` method.

class alembic.operations.ops.CreatePrimaryKeyOp(constraint_name: str | _NoneName | None, table_name: str, columns: Sequence[str], *, schema: str | None = None, **kw: Any)

  Represent a create primary key operation.

  classmethod batch_create_primary_key(operations: BatchOperations, constraint_name: str | None, columns: list[str]) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_primary_key()` method.

  classmethod create_primary_key(operations: Operations, constraint_name: str | None, table_name: str, columns: list[str], *, schema: str | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.create_primary_key()` method.

class alembic.operations.ops.CreateTableCommentOp(table_name: str, comment: str | None, *, schema: str | None = None, existing_comment: str | None = None)

  Represent a COMMENT ON table operation.

  classmethod batch_create_table_comment(operations: BatchOperations, comment: str | None, *, existing_comment: str | None = None) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_table_comment()` method.

  classmethod create_table_comment(operations: Operations, table_name: str, comment: str | None, *, existing_comment: str | None = None, schema: str | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.create_table_comment()` method.

  reverse() → CreateTableCommentOp | DropTableCommentOp

    Reverses the COMMENT ON operation against a table.

class alembic.operations.ops.CreateTableOp(table_name: str, columns: Sequence[SchemaItem], *, schema: str | None = None, if_not_exists: bool | None = None, _namespace_metadata: MetaData | None = None, _constraints_included: bool = False, **kw: Any)

  Represent a create table operation.

  classmethod create_table(operations: Operations, table_name: str, *columns: SchemaItem, if_not_exists: bool | None = None, **kw: Any) → Table

    This method is proxied on the `Operations` class, via the `Operations.create_table()` method.

class alembic.operations.ops.CreateUniqueConstraintOp(constraint_name: str | _NoneName | None, table_name: str, columns: Sequence[str], *, schema: str | None = None, **kw: Any)

  Represent a create unique constraint operation.

  classmethod batch_create_unique_constraint(operations: BatchOperations, constraint_name: str, columns: Sequence[str], **kw: Any) → Any

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.create_unique_constraint()` method.

  classmethod create_unique_constraint(operations: Operations, constraint_name: str | None, table_name: str, columns: Sequence[str], *, schema: str | None = None, **kw: Any) → Any

    This method is proxied on the `Operations` class, via the `Operations.create_unique_constraint()` method.

class alembic.operations.ops.DowngradeOps(ops: Sequence[MigrateOperation] = (), downgrade_token: str = 'downgrades')

  contains a sequence of operations that would apply to the ‘downgrade’ stream of a script.

  See also

  Customizing Revision Generation

class alembic.operations.ops.DropColumnOp(table_name: str, column_name: str, *, schema: str | None = None, if_exists: bool | None = None, _reverse: AddColumnOp | None = None, **kw: Any)

  Represent a drop column operation.

  classmethod batch_drop_column(operations: BatchOperations, column_name: str, **kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.drop_column()` method.

  classmethod drop_column(operations: Operations, table_name: str, column_name: str, *, schema: str | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.drop_column()` method.

class alembic.operations.ops.DropConstraintOp(constraint_name: str | _NoneName | None, table_name: str, type_: str | None = None, *, schema: str | None = None, if_exists: bool | None = None, _reverse: AddConstraintOp | None = None)

  Represent a drop constraint operation.

  classmethod batch_drop_constraint(operations: BatchOperations, constraint_name: str, type_: str | None = None) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.drop_constraint()` method.

  classmethod drop_constraint(operations: Operations, constraint_name: str, table_name: str, type_: str | None = None, *, schema: str | None = None, if_exists: bool | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.drop_constraint()` method.

class alembic.operations.ops.DropIndexOp(index_name: quoted_name | str | conv, table_name: str | None = None, *, schema: str | None = None, if_exists: bool | None = None, _reverse: CreateIndexOp | None = None, **kw: Any)

  Represent a drop index operation.

  classmethod batch_drop_index(operations: BatchOperations, index_name: str, **kw: Any) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.drop_index()` method.

  classmethod drop_index(operations: Operations, index_name: str, table_name: str | None = None, *, schema: str | None = None, if_exists: bool | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.drop_index()` method.

class alembic.operations.ops.DropTableCommentOp(table_name: str, *, schema: str | None = None, existing_comment: str | None = None)

  Represent an operation to remove the comment from a table.

  classmethod batch_drop_table_comment(operations: BatchOperations, *, existing_comment: str | None = None) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.drop_table_comment()` method.

  classmethod drop_table_comment(operations: Operations, table_name: str, *, existing_comment: str | None = None, schema: str | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.drop_table_comment()` method.

  reverse() → CreateTableCommentOp

    Reverses the COMMENT ON operation against a table.

class alembic.operations.ops.DropTableOp(table_name: str, *, schema: str | None = None, if_exists: bool | None = None, table_kw: MutableMapping[Any, Any] | None = None, _reverse: CreateTableOp | None = None)

  Represent a drop table operation.

  classmethod drop_table(operations: Operations, table_name: str, *, schema: str | None = None, if_exists: bool | None = None, **kw: Any) → None

    This method is proxied on the `Operations` class, via the `Operations.drop_table()` method.

class alembic.operations.ops.ExecuteSQLOp(sqltext: Executable | str, *, execution_options: dict[str, Any] | None = None)

  Represent an execute SQL operation.

  classmethod batch_execute(operations: Operations, sqltext: Executable | str, *, execution_options: dict[str, Any] | None = None) → None

    This method is proxied on the `BatchOperations` class, via the `BatchOperations.execute()` method.

  classmethod execute(operations: Operations, sqltext: Executable | str, *, execution_options: dict[str, Any] | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.execute()` method.

class alembic.operations.ops.MigrateOperation

  base class for migration command and organization objects.

  This system is part of the operation extensibility API.

  See also

  Built-in Operation Objects

  Operation Plugins

  Customizing Revision Generation

  info

    A dictionary that may be used to store arbitrary information along with this `MigrateOperation` object.

class alembic.operations.ops.MigrationScript(rev_id: str | None, upgrade_ops: UpgradeOps, downgrade_ops: DowngradeOps, *, message: str | None = None, imports: set[str] = {}, head: str | None = None, splice: bool | None = None, branch_label: _RevIdType | None = None, version_path: str | os.PathLike[str] | None = None, depends_on: _RevIdType | None = None)

  represents a migration script.

  E.g. when autogenerate encounters this object, this corresponds to the production of an actual script file.

  A normal `MigrationScript` object would contain a single `UpgradeOps` and a single `DowngradeOps` directive. These are accessible via the `.upgrade_ops` and `.downgrade_ops` attributes.

  In the case of an autogenerate operation that runs multiple times, such as the multiple database example in the “multidb” template, the `.upgrade_ops` and `.downgrade_ops` attributes are disabled, and instead these objects should be accessed via the `.upgrade_ops_list` and `.downgrade_ops_list` list-based attributes. These latter attributes are always available at the very least as single-element lists.

  See also

  Customizing Revision Generation

  property downgrade_ops: DowngradeOps | None

    An instance of `DowngradeOps`.

    See also

    `MigrationScript.downgrade_ops_list`

  property downgrade_ops_list: list[DowngradeOps]

    A list of `DowngradeOps` instances.

    This is used in place of the `MigrationScript.downgrade_ops` attribute when dealing with a revision operation that does multiple autogenerate passes.

  property upgrade_ops: UpgradeOps | None

    An instance of `UpgradeOps`.

    See also

    `MigrationScript.upgrade_ops_list`

  property upgrade_ops_list: list[UpgradeOps]

    A list of `UpgradeOps` instances.

    This is used in place of the `MigrationScript.upgrade_ops` attribute when dealing with a revision operation that does multiple autogenerate passes.

class alembic.operations.ops.ModifyTableOps(table_name: str, ops: Sequence[MigrateOperation], *, schema: str | None = None)

  Contains a sequence of operations that all apply to a single Table.

class alembic.operations.ops.OpContainer(ops: Sequence[MigrateOperation] = ())

  Represent a sequence of operations operation.

class alembic.operations.ops.RenameTableOp(old_table_name: str, new_table_name: str, *, schema: str | None = None)

  Represent a rename table operation.

  classmethod rename_table(operations: Operations, old_table_name: str, new_table_name: str, *, schema: str | None = None) → None

    This method is proxied on the `Operations` class, via the `Operations.rename_table()` method.

class alembic.operations.ops.UpgradeOps(ops: Sequence[MigrateOperation] = (), upgrade_token: str = 'upgrades')

  contains a sequence of operations that would apply to the ‘upgrade’ stream of a script.

  See also

  Customizing Revision Generation

## Extending Existing Operations

Added in version 1.17.2.

The `Operations.implementation_for.replace` parameter allows replacement of existing operation implementations, including built-in operations such as `CreateTableOp`. This enables customization of migration execution for purposes such as logging operations, running integrity checks, conditionally canceling operations, or adapting operations with dialect-specific options.

The example below illustrates replacing the implementation of `CreateTableOp` to log each table creation to a separate metadata table:

    from alembic import op
    from alembic.operations import Operations
    from alembic.operations.ops import CreateTableOp
    from alembic.operations.toimpl import create_table as _create_table
    from sqlalchemy import MetaData, Table, Column, String

    # Define a metadata table to track table operations
    log_table = Table(
        "table_metadata_log",
        MetaData(),
        Column("operation", String),
        Column("table_name", String),
    )

    @Operations.implementation_for(CreateTableOp, replace=True)
    def create_table_with_logging(operations, operation):
        # First, run the original CREATE TABLE implementation
        _create_table(operations, operation)

        # Then, log the operation to the metadata table
        operations.execute(
            log_table.insert().values(
                operation="create",
                table_name=operation.table_name
            )
        )

The above code can be placed in the `env.py` file to ensure it is loaded before migrations run. Once registered, all `op.create_table()` calls within migration scripts will use the augmented implementation.

The original implementation is imported from `alembic.operations.toimpl` and invoked within the replacement implementation. The `replace` parameter also enables conditional execution or complete replacement of operation behavior. The example below demonstrates skipping a `CreateTableOp` based on custom logic:

    from alembic.operations import Operations
    from alembic.operations.ops import CreateTableOp
    from alembic.operations.toimpl import create_table as _create_table

    @Operations.implementation_for(CreateTableOp, replace=True)
    def create_table_conditional(operations, operation):
        # Check if the table should be created based on custom logic
        if should_create_table(operation.table_name):
            _create_table(operations, operation)
        else:
            # Skip creation and optionally log
            operations.execute(
                "-- Skipped creation of table %s" % operation.table_name
            )

    def should_create_table(table_name):
        # Custom logic to determine if table should be created
        # For example, check a configuration or metadata table
        return table_name not in get_ignored_tables()