Plugins#

Added in version 1.18.0.

Alembic provides a plugin system that allows third-party extensions to integrate with Alembic’s functionality. Plugins can register custom operations, operation implementations, autogenerate comparison functions, and other extension points to add new capabilities to Alembic.

The plugin system provides a structured way to organize and distribute these extensions, allowing them to be discovered automatically using Python entry points.

Overview#

The Plugin class provides the foundation for creating plugins. A plugin’s setup() function can perform various types of registrations:

  • Custom operations - Register new operation directives using Operations.register_operation() (e.g., op.create_view())

  • Operation implementations - Provide database-specific implementations using Operations.implementation_for()

  • Autogenerate comparators - Add comparison functions for detecting schema differences during autogeneration

  • Other extensions - Register any other global handlers or customizations

A single plugin can register handlers across all of these categories. For example, a plugin for custom database objects might register both the operations to create/drop those objects and the autogenerate logic to detect changes to them.

See also

Replaceable Objects - Cookbook recipe demonstrating custom operations and implementations that would be suitable for packaging as a plugin

Installing and Using Plugins#

Third-party plugins are typically distributed as Python packages that can be installed via pip or other package managers:

pip install mycompany-alembic-plugin

Once installed, plugins that use Python’s entry point system are automatically discovered and loaded by Alembic at startup, which calls the plugin’s setup() function to perform any registrations.

Enable Autogenerate Plugins#

For plugins that provide autogenerate comparison functions via the Plugin.add_autogenerate_comparator() hook, the specific autogenerate functionality registered by the plugin must be enabled with EnvironmentContext.configure.autogenerate_plugins parameter, which by default indicates that only Alembic’s built-in plugins should be used. Note that this step does not apply to older plugins that may be registering autogenerate comparison functions globally.

See the section Enabling Autogenerate Plugins in env.py for background on enabling autogenerate comparison plugins per environment.

Using Plugins without entry points (such as local plugin code)#

Plugins do not need to be published with entry points to be used. A plugin can be manually registered by calling Plugin.setup_plugin_from_module() in the env.py file:

from alembic.runtime.plugins import Plugin
import myproject.alembic_plugin

# Register the plugin manually
Plugin.setup_plugin_from_module(
    myproject.alembic_plugin,
    "myproject.custom_operations"
)

This approach is useful for project-specific plugins that are not intended for distribution, or for testing plugins during development.

Enabling Autogenerate Plugins in env.py#

If a plugin provides autogenerate functionality that’s registered via the Plugin.add_autogenerate_comparator() hook, it can be selectively enabled or disabled using the EnvironmentContext.configure.autogenerate_plugins parameter in the EnvironmentContext.configure() call, typically as used within the env.py file. This parameter is passed as a list of strings each naming a specific plugin or a matching wildcard. The default value is ["alembic.autogenerate.*"] which indicates that the full set of Alembic’s internal plugins should be used.

The EnvironmentContext.configure.autogenerate_plugins parameter accepts a list of string patterns:

  • Simple names match plugin names exactly: "alembic.autogenerate.tables"

  • Wildcards match multiple plugins: "alembic.autogenerate.*" matches all built-in plugins

  • Negation patterns exclude plugins: "~alembic.autogenerate.comments" excludes the comments plugin

For example, to use all built-in plugins except comments, plus a custom plugin:

context.configure(
    # ...
    autogenerate_plugins=[
        "alembic.autogenerate.*",
        "~alembic.autogenerate.comments",
        "mycompany.custom_types",
    ]
)

The wildcard syntax using * indicates that tokens in that segment of the name (separated by period characters) will match any name. For Alembic’s alembic.autogenerate.* namespace, the built in names being invoked are:

  • alembic.autogenerate.schemas - Schema creation and dropping

  • alembic.autogenerate.tables - Table creation, dropping, and modification. This plugin depends on the schemas plugin in order to iterate through tables.

  • alembic.autogenerate.types - Column type changes. This plugin depends on the tables plugin in order to iterate through columns.

  • alembic.autogenerate.constraints - Constraint creation and dropping. This plugin depends on the tables plugin in order to iterate through columns.

  • alembic.autogenerate.defaults - Server default changes. This plugin depends on the tables plugin in order to iterate through columns.

  • alembic.autogenerate.comments - Table and column comment changes. This plugin depends on the tables plugin in order to iterate through columns.

While these names can be specified individually, they are subject to change as Alembic evolves. Using the wildcard pattern is recommended.

Omitting the built-in plugins entirely would prevent autogeneration from proceeding, unless other plugins were provided that replaced its functionality (which is possible!). Additionally, as noted above, the column-oriented plugins rely on the table- and schema- oriented plugins in order to receive iterated columns.

The EnvironmentContext.configure.autogenerate_plugins parameter only controls which plugins participate in autogenerate operations. Other plugin functionality, such as custom operations registered with Operations.register_operation(), is available regardless of this setting.

Writing a Plugin#

Creating a Plugin Module#

A plugin module must define a setup() function that accepts a Plugin instance. This function is called when the plugin is loaded, either automatically via entry points or manually via Plugin.setup_plugin_from_module():

from alembic import op
from alembic.operations import Operations
from alembic.runtime.plugins import Plugin
from alembic.util import DispatchPriority

def setup(plugin: Plugin) -> None:
    """Setup function called by Alembic when loading the plugin."""

    # Register custom operations
    Operations.register_operation("create_view")(CreateViewOp)
    Operations.implementation_for(CreateViewOp)(create_view_impl)

    # Register autogenerate comparison functions
    plugin.add_autogenerate_comparator(
        _compare_views,
        "view",
        qualifier="default",
        priority=DispatchPriority.MEDIUM,
    )

The setup() function serves as the entry point for all plugin registrations. It can call various Alembic APIs to extend functionality.

Publishing a Plugin#

To make a plugin available for installation via pip, create a package with an entry point in pyproject.toml:

[project.entry-points."alembic.plugins"]
mycompany.plugin_name = "mycompany.alembic_plugin"

Where mycompany.alembic_plugin is the module containing the setup() function.

When the package is installed, Alembic automatically discovers and loads the plugin through the entry point system. If the plugin provides autogenerate functionality, users can then enable it by adding its name mycompany.plugin_name to the autogenerate_plugins list in their env.py.

Registering Custom Operations#

Plugins can register new operation directives that become available as op.custom_operation() in migration scripts. This is done using Operations.register_operation() and Operations.implementation_for().

Example from the Replaceable Objects recipe:

from alembic.operations import Operations, MigrateOperation

class CreateViewOp(MigrateOperation):
    def __init__(self, view_name, select_stmt):
        self.view_name = view_name
        self.select_stmt = select_stmt

@Operations.register_operation("create_view")
class CreateViewOp(CreateViewOp):
    pass

@Operations.implementation_for(CreateViewOp)
def create_view(operations, operation):
    operations.execute(
        f"CREATE VIEW {operation.view_name} AS {operation.select_stmt}"
    )

These registrations can be performed in the plugin’s setup() function, making the custom operations available globally.

See also

Replaceable Objects - Complete example of registering custom operations

Operation Plugins - Documentation on the operations plugin system

Registering Autogenerate Comparators at the Plugin Level#

Plugins can register comparison functions that participate in the autogenerate process, detecting differences between database schema and SQLAlchemy metadata. These functions may be registered globally, where they take place unconditionally as documented at Registering a Comparison Function Globally; for older versions of Alembic prior to 1.18.0 this is the only registration system available. However when targeting Alembic 1.18.0 or higher, the Plugin approach provides a more configurable version of these registration hooks.

Plugin level comparison functions are registered using Plugin.add_autogenerate_comparator(). Each comparison function establishes itself as part of a named “target”, which is invoked by a parent handler. For example, if a handler establishes itself as part of the "column" target, it will be invoked when the alembic.autogenerate.tables plugin proceeds through SQLAlchemy Table objects and invokes comparison operations for pairs of same-named columns.

For an example of a complete comparison function, see the example at Registering a Comparison Function Globally.

The current levels of comparison are the same between global and plugin-level comparison functions, and include:

  • "autogenerate" - this target is invoked at the top of the autogenerate chain. These hooks are passed a AutogenContext and an UpgradeOps collection. Functions that subscribe to the autogenerate target should look like:

    from alembic.autogenerate.api import AutogenContext
    from alembic.operations.ops import UpgradeOps
    from alembic.runtime.plugins import Plugin
    from alembic.util import PriorityDispatchResult
    
    def autogen_toplevel(
        autogen_context: AutogenContext, upgrade_ops: UpgradeOps
    ) -> PriorityDispatchResult:
        #  ...
    
    
    def setup(plugin: Plugin) -> None:
        plugin.add_autogenerate_comparator(autogen_toplevel, "autogenerate")
    

    The function should return either PriorityDispatchResult.CONTINUE or PriorityDispatchResult.STOP to halt any further comparisons from proceeding, and should respond to detected changes by mutating the given UpgradeOps collection in place (the DowngradeOps version is produced later by reversing the UpgradeOps).

    An autogenerate compare function that seeks to run entirely independently of Alembic’s built-in autogenerate plugins, or to replace them completely, would register at the "autogenerate" level. The remaining levels indicated below are all invoked from within Alembic’s own autogenerate plugins and will not take place if alembic.autogenerate.* is not enabled.

    Added in version 1.18.0: The "autogenerate" comparison scope was introduced, replacing "schema" as the topmost comparison scope.

  • "schema" - this target is invoked for each individual “schema” being compared, and hooks are passed a AutogenContext, an UpgradeOps collection, and a set of schema names, featuring the value None for the “default” schema. Functions that subscribe to the "schema" target should look like:

    from alembic.autogenerate.api import AutogenContext
    from alembic.operations.ops import UpgradeOps
    from alembic.runtime.plugins import Plugin
    from alembic.util import PriorityDispatchResult
    
    def autogen_for_tables(
        autogen_context: AutogenContext,
        upgrade_ops: UpgradeOps,
        schemas: set[str | None],
    ) -> PriorityDispatchResult:
        # ...
    
    def setup(plugin: Plugin) -> None:
        plugin.add_autogenerate_comparator(
            autogen_for_tables,
            "schema",
            "tables",
        )
    

    The function should normally return PriorityDispatchResult.CONTINUE and should respond to detected changes by mutating the given UpgradeOps collection in place (the DowngradeOps version is produced later by reversing the UpgradeOps).

    The registration example above includes the "tables" “compare element”, which is optional. This indicates that the comparison function is part of a chain called “tables”, which is what Alembic’s own alembic.autogenerate.tables plugin uses. If our custom comparison function were to return the value PriorityDispatchResult.STOP, further comparison functions in the "tables" chain would not be called. Similarly, if another plugin in the "tables" chain returned PriorityDispatchResult.STOP, then our plugin would not be called. Making use of PriorityDispatchResult.STOP in terms of other plugins in the same “compare element” may be assisted by placing our function in the comparator chain using DispatchPriority.FIRST or DispatchPriority.LAST when registering.

  • "table" - this target is invoked per Table being compared between a database autoloaded version and the local metadata version. These hooks are passed an AutogenContext, a ModifyTableOps collection, a schema name, table name, a Table reflected from the database if any or None, and a Table present in the local MetaData. If the ModifyTableOps collection contains changes after all hooks are run, it is included in the migration script:

    from sqlalchemy import quoted_name
    from sqlalchemy import Table
    
    from alembic.autogenerate.api import AutogenContext
    from alembic.operations.ops import ModifyTableOps
    from alembic.runtime.plugins import Plugin
    from alembic.util import PriorityDispatchResult
    
    def compare_tables(
        autogen_context: AutogenContext,
        modify_table_ops: ModifyTableOps,
        schema: str | None,
        tname: quoted_name | str,
        conn_table: Table | None,
        metadata_table: Table | None,
    ) -> PriorityDispatchResult:
        # ...
    
    
    def setup(plugin: Plugin) -> None:
        plugin.add_autogenerate_comparator(compare_tables, "table")
    

    This hook may be used to compare elements of tables, such as comments or database-specific storage configurations. It should mutate the given ModifyTableOps object in place to add new change operations.

  • "column" - this target is invoked per Column being compared between a database autoloaded version and the local metadata version. These hooks are passed an AutogenContext, an AlterColumnOp object, a schema name, table name, column name, a Column reflected from the database and a Column present in the local table. If the AlterColumnOp contains changes after all hooks are run, it is included in the migration script; a “change” is considered to be present if any of the modify_ attributes are set to a non-default value, or there are any keys in the .kw collection with the prefix "modify_":

    from typing import Any
    from sqlalchemy import quoted_name
    from sqlalchemy import Table
    
    from alembic.autogenerate.api import AutogenContext
    from alembic.operations.ops import AlterColumnOp
    from alembic.runtime.plugins import Plugin
    from alembic.util import PriorityDispatchResult
    
    def compare_columns(
        autogen_context: AutogenContext,
        alter_column_op: AlterColumnOp,
        schema: str | None,
        tname: quoted_name | str,
        cname: quoted_name | str,
        conn_col: Column[Any],
        metadata_col: Column[Any],
    ) -> PriorityDispatchResult:
      # ...
    
    
    def setup(plugin: Plugin) -> None:
        plugin.add_autogenerate_comparator(compare_columns, "column")
    

    Pre-existing compare chains within the "column" target include "comment", "server_default", and "types". Comparison functions here should mutate the given AlterColumnOp object in place to add new change operations.

See also

Autogeneration - Detailed documentation on the autogenerate system

Registering a Comparison Function Globally - a companion section to this one which explains autogenerate comparison functions in terms of the older “global” dispatch, but also includes a complete example of a comparison function.

Customizing Revision Generation - Customizing autogenerate behavior

Plugin API Reference#

class alembic.runtime.plugins.Plugin(name: str)#

Describe a series of functions that are pulled in as a plugin.

This is initially to provide for portable lists of autogenerate comparison functions, however the setup for a plugin can run any other kinds of global registration as well.

Added in version 1.18.0.

add_autogenerate_comparator(fn: Callable[..., PriorityDispatchResult], compare_target: str, compare_element: str | None = None, *, qualifier: str = 'default', priority: DispatchPriority = DispatchPriority.MEDIUM) None#

Register an autogenerate comparison function.

See the section Registering Autogenerate Comparators at the Plugin Level for detailed examples on how to use this method.

Parameters:
  • fn – The comparison function to register. The function receives arguments specific to the type of comparison being performed and should return a PriorityDispatchResult value.

  • compare_target – The type of comparison being performed (e.g., "table", "column", "type").

  • compare_element – Optional sub-element being compared within the target type.

  • qualifier – Database dialect qualifier. Use "default" for all dialects, or specify a dialect name like "postgresql" to register a dialect-specific handler. Defaults to "default".

  • priority – Execution priority for this comparison function. Functions are executed in priority order from DispatchPriority.FIRST to DispatchPriority.LAST. Defaults to DispatchPriority.MEDIUM.

classmethod populate_autogenerate_priority_dispatch(comparators: PriorityDispatcher, include_plugins: list[str]) None#

Populate all current autogenerate comparison functions into a given PriorityDispatcher.

remove() None#

remove this plugin

classmethod setup_plugin_from_module(module: ModuleType, name: str) None#

Call the setup() function of a plugin module, identified by passing the module object itself.

E.g.:

from alembic.runtime.plugins import Plugin
import myproject.alembic_plugin

# Register the plugin manually
Plugin.setup_plugin_from_module(
    myproject.alembic_plugin,
    "myproject.custom_operations"
)

This will generate a new Plugin object with the given name, which will register itself in the global list of plugins. Then the module’s setup() function is invoked, passing that Plugin object.

This exact process is invoked automatically at import time for any plugin module that is published via the alembic.plugins entrypoint.

class alembic.util.langhelpers.PriorityDispatchResult(*values)#

indicate an action after running a function within a PriorityDispatcher

Added in version 1.18.0.

CONTINUE = 1#

Continue running more functions.

Any return value that is not PriorityDispatchResult.STOP is equivalent to this.

STOP = 2#

Stop running any additional functions within the subgroup

class alembic.util.langhelpers.DispatchPriority(*values)#

Indicate which of three sub-collections a function inside a PriorityDispatcher should be placed.

Added in version 1.18.0.

FIRST = 50#

Run the funciton in the first batch of functions (highest priority)

LAST = 10#

Run the function in the last batch of functions

MEDIUM = 25#

Run the function at normal priority (this is the default)

See also

EnvironmentContext.configure.autogenerate_plugins - Configuration parameter for enabling autogenerate plugins

Operation Plugins - Documentation on custom operations

Replaceable Objects - Example of custom operations suitable for a plugin

Customizing Revision Generation - General information on customizing autogenerate behavior