sqlalchemy/alembic · error · ValueError

Duplicate table keys across multiple MetaData objects

Error message

Duplicate table keys across multiple MetaData objects: %s

What it means

The table_key_to_table aggregated property raises ValueError when two MetaData objects passed to configure() contain tables with identical keys (schema-qualified name). Alembic builds a single lookup dict across all metadata and cannot disambiguate duplicate keys, so it refuses rather than silently dropping one.

Solutions

  1. Rename one of the conflicting tables so each MetaData key is unique, or consolidate both into a single MetaData object.
  2. Use distinct schema names per MetaData so the keys (schema.table) differ.
  3. Pass only one MetaData to target_metadata, or deduplicate the list before configure().
  4. Inspect the reported keys in the error message to locate which metadata objects overlap.

Example fix

# before
context.configure(target_metadata=[meta_a, meta_b])  # both have 'users'

# after: single metadata
context.configure(target_metadata=meta_a)
# or give each a distinct schema
Table('users', meta_b, schema='billing')
Defensive patterns

Strategy: validation

Validate before calling

from collections import Counter

def metadata_keys_overlap(metadata_list) -> list[str]:
    keys = []
    for m in metadata_list:
        keys.extend(m.tables.keys())
    return [k for k, c in Counter(keys).items() if c > 1]

conflicts = metadata_keys_overlap([meta_a, meta_b])
assert not conflicts, f'Duplicate table keys: {conflicts}'

Type guard

from sqlalchemy import MetaData

def unique_table_keys(metadata_list: list[MetaData]) -> bool:
    seen = set()
    for m in metadata_list:
        for k in m.tables:
            if k in seen:
                return False
            seen.add(k)
    return True

Prevention

When it happens

Trigger: Passing target_metadata=[metadata_a, metadata_b] to context.configure() where both metadata objects define a table named 'users' (or same schema.table). Also triggered by re-importing the same MetaData instance under different names in a list, or model packages that redefine shared tables.

Common situations: Multi-database or modular apps that register models into separate MetaData objects but share table names; accidental duplicate imports of model modules; merging two SQLAlchemy model bases that both declare a base table.

Related errors


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

Appendix: 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 5551b5d35f)