{"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/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/runtime/plugins.py#L21-L57","documentation":"Raised by Plugin.__init__() when a Plugin with the given name already exists in the global _all_plugins registry. Plugin names must be unique process-wide because the autogenerate comparator dispatch and branch-label mapping key off them. The check runs at construction, which happens automatically for any module published via the alembic.plugins entry point and on manual Plugin() creation.","triggerScenarios":"Two installed packages declaring the same alembic.plugins entry point name; manually calling Plugin('name') twice; an entry point and a manual registration both using 'name'; re-importing a plugin module after a failed remove() in a long-lived process or test session.","commonSituations":"Two third-party libraries shipping alembic plugins under the same entry point name; a plugin left in _all_plugins between tests because remove() wasn't called; a local plugin whose name collides with a published one.","solutions":["Give each plugin a unique entry point / Plugin() name (namespace with package prefix).","In test teardown call plugin.remove() to deregister before re-registering.","Audit installed packages' alembic.plugins entry points and rename the duplicate."],"exampleFix":"// before\n# two packages both declare entry point name 'myplugin'\nPlugin('myplugin')  # second call raises ValueError\n\n// after\n# give unique names\nPlugin('pkg_a.myplugin')\nPlugin('pkg_b.myplugin')","handlingStrategy":"validation","validationCode":"from alembic.runtime.plugins import _all_plugins\n\ndef plugin_name_available(name) -> bool:\n    return name not in _all_plugins","typeGuard":null,"tryCatchPattern":"from alembic.runtime.plugins import Plugin, _all_plugins\ntry:\n    Plugin(name)\nexcept ValueError as e:\n    if 'already registered' in str(e):\n        existing = _all_plugins[name]\n        existing.remove()\n        Plugin(name)\n    else:\n        raise","preventionTips":["Namespace plugin names by package to avoid collisions.","Call plugin.remove() in test teardown.","Audit installed alembic.plugins entry points for duplicate names."],"tags":["alembic","plugins","entry-points","configuration"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}