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 pluginsNegation 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 droppingalembic.autogenerate.tables- Table creation, dropping, and modification. This plugin depends on theschemasplugin in order to iterate through tables.alembic.autogenerate.types- Column type changes. This plugin depends on thetablesplugin in order to iterate through columns.alembic.autogenerate.constraints- Constraint creation and dropping. This plugin depends on thetablesplugin in order to iterate through columns.alembic.autogenerate.defaults- Server default changes. This plugin depends on thetablesplugin in order to iterate through columns.alembic.autogenerate.comments- Table and column comment changes. This plugin depends on thetablesplugin 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 aAutogenContextand anUpgradeOpscollection. Functions that subscribe to theautogeneratetarget 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.CONTINUEorPriorityDispatchResult.STOPto halt any further comparisons from proceeding, and should respond to detected changes by mutating the givenUpgradeOpscollection in place (theDowngradeOpsversion is produced later by reversing theUpgradeOps).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 ifalembic.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 aAutogenContext, anUpgradeOpscollection, and a set of schema names, featuring the valueNonefor 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.CONTINUEand should respond to detected changes by mutating the givenUpgradeOpscollection in place (theDowngradeOpsversion is produced later by reversing theUpgradeOps).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 ownalembic.autogenerate.tablesplugin uses. If our custom comparison function were to return the valuePriorityDispatchResult.STOP, further comparison functions in the"tables"chain would not be called. Similarly, if another plugin in the"tables"chain returnedPriorityDispatchResult.STOP, then our plugin would not be called. Making use ofPriorityDispatchResult.STOPin terms of other plugins in the same “compare element” may be assisted by placing our function in the comparator chain usingDispatchPriority.FIRSTorDispatchPriority.LASTwhen registering."table"- this target is invoked perTablebeing compared between a database autoloaded version and the local metadata version. These hooks are passed anAutogenContext, aModifyTableOpscollection, a schema name, table name, aTablereflected from the database if any orNone, and aTablepresent in the localMetaData. If theModifyTableOpscollection 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
ModifyTableOpsobject in place to add new change operations."column"- this target is invoked perColumnbeing compared between a database autoloaded version and the local metadata version. These hooks are passed anAutogenContext, anAlterColumnOpobject, a schema name, table name, column name, aColumnreflected from the database and aColumnpresent in the local table. If theAlterColumnOpcontains changes after all hooks are run, it is included in the migration script; a “change” is considered to be present if any of themodify_attributes are set to a non-default value, or there are any keys in the.kwcollection 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 givenAlterColumnOpobject 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
PriorityDispatchResultvalue.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.FIRSTtoDispatchPriority.LAST. Defaults toDispatchPriority.MEDIUM.
- classmethod populate_autogenerate_priority_dispatch(comparators: PriorityDispatcher, include_plugins: list[str]) None#
Populate all current autogenerate comparison functions into a given PriorityDispatcher.
- 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
Pluginobject with the given name, which will register itself in the global list of plugins. Then the module’ssetup()function is invoked, passing thatPluginobject.This exact process is invoked automatically at import time for any plugin module that is published via the
alembic.pluginsentrypoint.
- class alembic.util.langhelpers.PriorityDispatchResult(*values)#
indicate an action after running a function within a
PriorityDispatcherAdded 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
PriorityDispatchershould 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