{"record":{"id":"b0d2938a8131b6a9","repo":"sqlalchemy/alembic","slug":"duplicate-table-keys-across-multiple-metadata-obje","errorCode":null,"errorMessage":"Duplicate table keys across multiple MetaData objects: %s","messagePattern":"Duplicate table keys across multiple MetaData objects: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"alembic/autogenerate/api.py","lineNumber":513,"sourceCode":"        return result\n\n    @util.memoized_property\n    def table_key_to_table(self) -> dict[str, Table]:\n        \"\"\"Return an aggregate  of the :attr:`.MetaData.tables` dictionaries.\n\n        The :attr:`.MetaData.tables` collection is a dictionary of table key\n        to :class:`.Table`; this method aggregates the dictionary across\n        multiple :class:`.MetaData` objects into one dictionary.\n\n        Duplicate table keys are **not** supported; if two :class:`.MetaData`\n        objects contain the same table key, an exception is raised.\n\n        \"\"\"\n        result: dict[str, Table] = {}\n        for m in util.to_list(self.metadata):\n            intersect = set(result).intersection(set(m.tables))\n            if intersect:\n                raise ValueError(\n                    \"Duplicate table keys across multiple \"\n                    \"MetaData objects: %s\"\n                    % (\", \".join('\"%s\"' % key for key in sorted(intersect)))\n                )\n\n            result.update(m.tables)\n        return result\n\n\nclass RevisionContext:\n    \"\"\"Maintains configuration and state that's specific to a revision\n    file generation operation.\"\"\"\n\n    generated_revisions: list[MigrationScript]\n    process_revision_directives: ProcessRevisionDirectiveFn | None\n\n    def __init__(\n        self,","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/autogenerate/api.py#L495-L531","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rename one of the conflicting tables so each MetaData key is unique, or consolidate both into a single MetaData object.","Use distinct schema names per MetaData so the keys (schema.table) differ.","Pass only one MetaData to target_metadata, or deduplicate the list before configure().","Inspect the reported keys in the error message to locate which metadata objects overlap."],"exampleFix":"# before\ncontext.configure(target_metadata=[meta_a, meta_b])  # both have 'users'\n\n# after: single metadata\ncontext.configure(target_metadata=meta_a)\n# or give each a distinct schema\nTable('users', meta_b, schema='billing')","handlingStrategy":"validation","validationCode":"from collections import Counter\n\ndef metadata_keys_overlap(metadata_list) -> list[str]:\n    keys = []\n    for m in metadata_list:\n        keys.extend(m.tables.keys())\n    return [k for k, c in Counter(keys).items() if c > 1]\n\nconflicts = metadata_keys_overlap([meta_a, meta_b])\nassert not conflicts, f'Duplicate table keys: {conflicts}'","typeGuard":"from sqlalchemy import MetaData\n\ndef unique_table_keys(metadata_list: list[MetaData]) -> bool:\n    seen = set()\n    for m in metadata_list:\n        for k in m.tables:\n            if k in seen:\n                return False\n            seen.add(k)\n    return True","tryCatchPattern":null,"preventionTips":["Use a single MetaData for all models when possible.","If multiple MetaData objects are required, assign distinct schemas so keys differ.","Add a startup check that scans all metadata for key collisions."],"tags":["autogenerate","metadata","duplicate-keys","configuration"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}