sqlalchemy/alembic · error · ValueError

A plugin named is already registered

Error message

A plugin named {name} is already registered

What it means

Raised as ValueError by Plugin.__init__ (plugins.py:35-40) when a Plugin is constructed with a name that already exists in the module-global _all_plugins dict. Alembic's plugin registry (added in 1.18.0) requires unique names because the autogenerate comparator dispatch keys off them; a duplicate would silently shadow one plugin's comparators.

Solutions

  1. Uninstall or dedupe the package that registers the duplicate entry point (pip list | grep <plugin>).
  2. Rename your custom plugin so its name is unique across all installed distributions.
  3. Call Plugin.remove(name) before re-registering if you intentionally reload a plugin in-process.

Example fix

// before
# packageA entry_points: alembic.plugins = myplugin = mypkg.plugin
# packageB entry_points: alembic.plugins = myplugin = otherpkg.plugin  # collides
// after
# rename one
# packageB entry_points: alembic.plugins = myplugin_v2 = otherpkg.plugin
Defensive patterns

Strategy: validation

Validate before calling

# Detect duplicate plugin entry points before they conflict.
import importlib.metadata as md
from collections import Counter

def find_duplicate_plugins() -> list[str]:
    names = [ep.name for ep in md.entry_points(group='alembic.plugins')]
    return [n for n, c in Counter(names).items() if c > 1]

dups = find_duplicate_plugins()
if dups:
    raise RuntimeError(f'Duplicate alembic plugins registered: {dups}')

Type guard

def plugin_name_is_unique(name: str, seen: set[str]) -> bool:
    return name not in seen

Prevention

When it happens

Trigger: Two installed packages both declaring an `alembic.plugins` entry point with the same name; calling Plugin('myplugin') twice in the same process; registering a plugin that collides with a built-in name.

Common situations: Two virtualenvs merged; a forked/renamed package left behind alongside the new one; reinstalling a plugin package without uninstalling, leaving two dist-info directories publishing the same entry point.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/daa6541c1d1eca29. Report an issue: GitHub.

Appendix: source

Thrown at alembic/runtime/plugins.py:39

log = logging.getLogger(__name__)


class Plugin:
    """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.

    .. versionadded:: 1.18.0

    """

    def __init__(self, name: str):
        self.name = name
        log.info("setup plugin %s", name)
        if name in _all_plugins:
            raise ValueError(f"A plugin named {name} is already registered")
        _all_plugins[name] = self
        self.autogenerate_comparators = PriorityDispatcher()

    def remove(self) -> None:
        """remove this plugin"""

        del _all_plugins[self.name]

    def add_autogenerate_comparator(
        self,
        fn: Callable[..., PriorityDispatchResult],
        compare_target: str,
        compare_element: str | None = None,
        *,
        qualifier: str = "default",
        priority: DispatchPriority = DispatchPriority.MEDIUM,
    ) -> None:
        """Register an autogenerate comparison function.

View on GitHub (pinned to 5551b5d35f)