sqlalchemy/alembic · error · ValueError

no dispatch function for object

Error message

no dispatch function for object: %s

What it means

Raised as ValueError by Dispatcher.dispatch when no dispatch function is registered for the given object's type or any of its superclasses in the MRO (langhelpers.py:335-340). The dispatcher walks type(obj).__mro__ looking for a registered handler; if none matches for any qualifier, it raises. This indicates the object type being dispatched hasn't been registered with dispatch_for.

Solutions

  1. Register a dispatch handler for the object's type: @dispatcher.dispatch_for(MyCustomType).
  2. If the type has a parent that IS registered, ensure the custom type properly inherits from it so the MRO walk finds the parent handler.
  3. Check that the plugin/module containing the dispatch_for registration is actually imported before dispatch is called.
  4. Inspect the object's MRO: print(type(obj).__mro__) to see what types the dispatcher is checking.

Example fix

# before
class MyCustomType(sa.UserDefinedType):
    ...

# migration uses MyCustomType but autogenerate fails with 'no dispatch function'

# after
from alembic.autogenerate.api import AutogenContext
from alembic.util.langhelpers import Dispatcher

# register a comparator for the custom type
@comparator.dispatch_for(MyCustomType)
def _compare_my_type(autogen_context, ops, sqla_type):
    ...
Defensive patterns

Strategy: validation

Validate before calling

def has_dispatch_for_type(dispatcher, obj, qualifier='default'):
    targets = type(obj).__mro__ if not isinstance(obj, str) else [obj]
    if isinstance(obj, type):
        targets = obj.__mro__
    qualifiers = [qualifier, 'default'] if qualifier != 'default' else ['default']
    for t in targets:
        for q in qualifiers:
            if (t, q) in dispatcher._registry:
                return True
    return False

Try / catch

try:
    result = dispatcher.dispatch(obj)
except ValueError as e:
    if 'no dispatch function' in str(e):
        print(f"No handler for {type(obj)}. Register one with dispatch_for.")
    raise

Prevention

When it happens

Trigger: Calling dispatcher.dispatch(obj) or dispatcher.dispatch(obj, qualifier='custom') where obj's type (and all its base classes) have no registered handler. In Alembic this occurs in autogenerate comparison and operation rendering — e.g., rendering a custom SQLAlchemy type or operation class that hasn't been registered with the relevant dispatcher.

Common situations: Using a custom SQLAlchemy column type in a migration without registering a comparator/rendering impl. Third-party operation classes not registered with the operations dispatcher. Passing an unexpected object type to an internal dispatch API. Version mismatch where a plugin's registration didn't load.

Related errors


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

Appendix: source

Thrown at alembic/util/langhelpers.py:340

    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":
            qualifiers = [qualifier, "default"]
        else:
            qualifiers = ["default"]

        for spcls in targets:
            for qualifier in qualifiers:
                if (spcls, qualifier) in self._registry:
                    return self._registry[(spcls, qualifier)]
        else:
            raise ValueError("no dispatch function for object: %s" % obj)

    def branch(self) -> Dispatcher:
        """Return a copy of this dispatcher that is independently
        writable."""

        d = Dispatcher()
        d._registry.update(self._registry)
        return d


class PriorityDispatcher:
    """registers lists of functions at multiple levels of priority and provides
    a target to invoke them in priority order.

    .. versionadded:: 1.18.0 - PriorityDispatcher replaces the job
       of Dispatcher(uselist=True)

    """

View on GitHub (pinned to 5551b5d35f)