{"record":{"id":"daa6541c1d1eca29","repo":"sqlalchemy/alembic","slug":"a-plugin-named-name-is-already-registered","errorCode":null,"errorMessage":"A plugin named {name} is already registered","messagePattern":"A plugin named (.+?) is already registered","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/runtime/plugins.py","lineNumber":39,"sourceCode":"log = logging.getLogger(__name__)\n\n\nclass Plugin:\n    \"\"\"Describe a series of functions that are pulled in as a plugin.\n\n    This is initially to provide for portable lists of autogenerate\n    comparison functions, however the setup for a plugin can run any\n    other kinds of global registration as well.\n\n    .. versionadded:: 1.18.0\n\n    \"\"\"\n\n    def __init__(self, name: str):\n        self.name = name\n        log.info(\"setup plugin %s\", name)\n        if name in _all_plugins:\n            raise ValueError(f\"A plugin named {name} is already registered\")\n        _all_plugins[name] = self\n        self.autogenerate_comparators = PriorityDispatcher()\n\n    def remove(self) -> None:\n        \"\"\"remove this plugin\"\"\"\n\n        del _all_plugins[self.name]\n\n    def add_autogenerate_comparator(\n        self,\n        fn: Callable[..., PriorityDispatchResult],\n        compare_target: str,\n        compare_element: str | None = None,\n        *,\n        qualifier: str = \"default\",\n        priority: DispatchPriority = DispatchPriority.MEDIUM,\n    ) -> None:\n        \"\"\"Register an autogenerate comparison function.","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/runtime/plugins.py#L21-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Uninstall or dedupe the package that registers the duplicate entry point (pip list | grep <plugin>).","Rename your custom plugin so its name is unique across all installed distributions.","Call Plugin.remove(name) before re-registering if you intentionally reload a plugin in-process."],"exampleFix":"// before\n# packageA entry_points: alembic.plugins = myplugin = mypkg.plugin\n# packageB entry_points: alembic.plugins = myplugin = otherpkg.plugin  # collides\n// after\n# rename one\n# packageB entry_points: alembic.plugins = myplugin_v2 = otherpkg.plugin","handlingStrategy":"validation","validationCode":"# Detect duplicate plugin entry points before they conflict.\nimport importlib.metadata as md\nfrom collections import Counter\n\ndef find_duplicate_plugins() -> list[str]:\n    names = [ep.name for ep in md.entry_points(group='alembic.plugins')]\n    return [n for n, c in Counter(names).items() if c > 1]\n\ndups = find_duplicate_plugins()\nif dups:\n    raise RuntimeError(f'Duplicate alembic plugins registered: {dups}')","typeGuard":"def plugin_name_is_unique(name: str, seen: set[str]) -> bool:\n    return name not in seen","tryCatchPattern":null,"preventionTips":["Use unique entry-point names across all installed plugin packages.","Uninstall superseded plugin packages to remove duplicate dist-info entry points.","Call Plugin.remove(name) before re-registering in long-lived processes."],"tags":["alembic","plugins","entry-points","configuration"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}