sqlalchemy/alembic · error · ValueError

Duplicate table keys across multiple MetaData objects: %s

Error message

Duplicate table keys across multiple MetaData objects: %s

What it means

Alembic aggregates multiple MetaData objects passed to configure() into one table-key map; the table_key_to_table property refuses to silently merge two MetaData objects that declare the same (schema, table_name) key, because doing so would be ambiguous. This guards against accidentally wiring the same model metadata in twice, or two metadata sets that overlap.

Source

Thrown at alembic/autogenerate/api.py:513

        return result

    @util.memoized_property
    def table_key_to_table(self) -> dict[str, Table]:
        """Return an aggregate  of the :attr:`.MetaData.tables` dictionaries.

        The :attr:`.MetaData.tables` collection is a dictionary of table key
        to :class:`.Table`; this method aggregates the dictionary across
        multiple :class:`.MetaData` objects into one dictionary.

        Duplicate table keys are **not** supported; if two :class:`.MetaData`
        objects contain the same table key, an exception is raised.

        """
        result: dict[str, Table] = {}
        for m in util.to_list(self.metadata):
            intersect = set(result).intersection(set(m.tables))
            if intersect:
                raise ValueError(
                    "Duplicate table keys across multiple "
                    "MetaData objects: %s"
                    % (", ".join('"%s"' % key for key in sorted(intersect)))
                )

            result.update(m.tables)
        return result


class RevisionContext:
    """Maintains configuration and state that's specific to a revision
    file generation operation."""

    generated_revisions: list[MigrationScript]
    process_revision_directives: ProcessRevisionDirectiveFn | None

    def __init__(
        self,

View on GitHub (pinned to 44fb345033)

Solutions

  1. De-duplicate: ensure each table is declared in exactly one MetaData and pass the union metadata once.
  2. If duplicates are intentional (e.g. per-schema splits), rename one table or assign distinct schemas so the keys differ.
  3. Pass a single combined MetaData that already holds all tables instead of a list with overlap.

Example fix

// before
context.configure(target_metadata=[base.metadata, plugin.metadata])
# both define Table('users', ...) -> ValueError
// after
context.configure(target_metadata=base.metadata)  # plugin tables also attached here
Defensive patterns

Strategy: validation

Validate before calling

from sqlalchemy import MetaData
metas = [m for m in (target_metadata if isinstance(target_metadata, (list, tuple)) else [target_metadata])]
seen, dupes = set(), set()
for m in metas:
    for k in m.tables:
        (dupes if k in seen else seen).add(k)
assert not dupes, f"Duplicate table keys across MetaData objects: {sorted(dupes)}"

Type guard

def has_unique_table_keys(metas) -> bool:
    keys = set()
    for m in metas:
        inter = keys.intersection(m.tables)
        if inter:
            return False
        keys.update(m.tables)
    return True

Prevention

When it happens

Trigger: Passing a list/tuple of MetaData objects to EnvironmentContext.configure(target_metadata=[meta_a, meta_b]) where meta_a and meta_b both define a Table with the same key (e.g. both define 'users', or 'myschema.users').

Common situations: App registers the same declarative Base.metadata in two separate MetaData groupings; a plugin or shared library contributes a table that the app also defines; a refactor split metadata but left duplicate declarations.

Related errors


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