sqlalchemy/alembic · error · ValueError

Can not set dispatch function for object {target!r}: key alr

Error message

Can not set dispatch function for object {target!r}: key already exists. To replace existing function, use replace=True.

What it means

Raised as ValueError by Dispatcher.dispatch_for (langhelpers.py:312) when registering a function for a (target, qualifier) pair that already exists in the registry and replace=False (the default). The dispatcher forbids silent overrides to prevent two plugins/impls from unknowingly clobbering each other's handler for the same type.

Source

Thrown at alembic/util/langhelpers.py:312

    LAST = 10
    """Run the function in the last batch of functions"""


class Dispatcher:
    def __init__(self) -> None:
        self._registry: dict[tuple[Any, ...], Any] = {}

    def dispatch_for(
        self,
        target: Any,
        *,
        qualifier: str = "default",
        replace: bool = False,
    ) -> Callable[[_C], _C]:
        def decorate(fn: _C) -> _C:
            if (target, qualifier) in self._registry and not replace:
                raise ValueError(
                    "Can not set dispatch function for object "
                    f"{target!r}: key already exists. To replace "
                    "existing function, use replace=True."
                )
            self._registry[(target, qualifier)] = fn
            return fn

        return decorate

    def dispatch(self, obj: Any, qualifier: str = "default") -> Any:
        if isinstance(obj, str):
            targets: Sequence[Any] = [obj]
        elif isinstance(obj, type):
            targets = obj.__mro__
        else:
            targets = type(obj).__mro__

        if qualifier != "default":

View on GitHub (pinned to 44fb345033)

Solutions

  1. If you intend to override, pass replace=True: dispatcher.dispatch_for(SomeType, replace=True)(fn).
  2. Otherwise branch the dispatcher first (dispatcher.branch()) to get an independent writable copy.
  3. Check 'if (target, qualifier) not in dispatcher._registry' before registering to avoid collisions.
  4. Ensure you are not double-loading the same plugin/impl module.

Example fix

# before
from alembic.util.langhelpers import Dispatcher
d = Dispatcher()
@d.dispatch_for(int)
def h(x): return x
@d.dispatch_for(int)  # ValueError: key already exists
def h2(x): return x*2

# after: explicitly replace
d.dispatch_for(int, replace=True)
def h2(x): return x*2
Defensive patterns

Strategy: validation

Validate before calling

from alembic.util.langhelpers import Dispatcher

def safe_register(d: Dispatcher, target, fn, qualifier: str = 'default'):
    key = (target, qualifier)
    if key in d._registry:
        raise ValueError(f'{key} already registered; pass replace=True to override')
    d.dispatch_for(target, qualifier=qualifier)(fn)

Try / catch

from alembic.util.langhelpers import Dispatcher
try:
    d.dispatch_for(MyType)(fn)
except ValueError as e:
    if 'key already exists' in str(e):
        d.dispatch_for(MyType, replace=True)(fn)
    else:
        raise

Prevention

When it happens

Trigger: Calling dispatcher.dispatch_for(SomeType)(fn) twice; a plugin or impl registering a dispatch for a type that Alembic (or another plugin) already registered; calling dispatch_for on a branched dispatcher that inherited a parent's entry.

Common situations: Writing a custom Operations implementation or ddl impl that registers a dispatch for a dialect/type Alembic already handles; loading two plugins that both target the same type; calling dispatch_for in a loop without deduping.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/884cba357d607838.json. Report an issue: GitHub.