{"record":{"id":"24882bcfc2e57033","repo":"sqlalchemy/alembic","slug":"list-of-dictionaries-expected","errorCode":null,"errorMessage":"List of dictionaries expected","messagePattern":"List of dictionaries expected","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"alembic/ddl/impl.py","lineNumber":486,"sourceCode":"    def drop_table_comment(self, table: Table) -> None:\n        self._exec(schema.DropTableComment(table))\n\n    def create_column_comment(self, column: Column[Any]) -> None:\n        self._exec(schema.SetColumnComment(column))\n\n    def drop_index(self, index: Index, **kw: Any) -> None:\n        self._exec(schema.DropIndex(index, **kw))\n\n    def bulk_insert(\n        self,\n        table: TableClause | Table,\n        rows: list[dict],\n        multiinsert: bool = True,\n    ) -> None:\n        if not isinstance(rows, list):\n            raise TypeError(\"List expected\")\n        elif rows and not isinstance(rows[0], dict):\n            raise TypeError(\"List of dictionaries expected\")\n        if self.as_sql:\n            for row in rows:\n                self._exec(\n                    table.insert()\n                    .inline()\n                    .values(\n                        **{\n                            k: (\n                                sqla_compat._literal_bindparam(\n                                    k, v, type_=table.c[k].type\n                                )\n                                if not isinstance(\n                                    v, sqla_compat._literal_bindparam\n                                )\n                                else v\n                            )\n                            for k, v in row.items()\n                        }","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/ddl/impl.py#L468-L504","documentation":"DefaultImpl.bulk_insert raises TypeError when rows is a list but its first element is not a dict. Each row must be a dictionary of column-name to value so Alembic can build the INSERT value maps; tuples, lists, or ORM objects are not accepted.","triggerScenarios":"Calling op.bulk_insert(table, [(1, 'a'), (2, 'b')]) (list of tuples); passing a list of ORM model instances; passing a list of named tuples.","commonSituations":"Migrating from executemany-style code that used tuples; passing ORM query results (list of model instances) directly into bulk_insert.","solutions":["Provide each row as a dict: op.bulk_insert(table, [{'id': 1, 'name': 'a'}, ...]).","Convert ORM instances: [model_to_dict(m) for m in instances] or use m.__dict__ sans _sa_instance_state.","Convert tuples using the column order: [dict(zip(columns, t)) for t in tuples]."],"exampleFix":"# before\nop.bulk_insert(table, [(1, 'a'), (2, 'b')])\n\n# after\ncols = ['id', 'name']\nop.bulk_insert(table, [dict(zip(cols, t)) for t in [(1, 'a'), (2, 'b')]])","handlingStrategy":"type-guard","validationCode":"def safe_bulk_insert(op, table, rows):\n    if not isinstance(rows, list) or any(not isinstance(r, dict) for r in rows):\n        raise TypeError('rows must be a list of dicts')\n    op.bulk_insert(table, rows)","typeGuard":"def is_list_of_dicts(value) -> bool:\n    return isinstance(value, list) and all(isinstance(r, dict) for r in value)","tryCatchPattern":null,"preventionTips":["Ensure every element of the rows list is a dict mapping column names to values.","Convert ORM instances to dicts before bulk_insert.","Write a small helper that validates rows once and reuses it across migrations."],"tags":["bulk-insert","ddl","type-error","data-migration"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}