{"id":"24882bcfc2e57033","repo":"sqlalchemy/alembic","slug":"list-of-dictionaries-expected","errorCode":null,"errorMessage":"List of dictionaries expected","messagePattern":"List of dictionaries expected","errorType":"validation","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/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/ddl/impl.py#L468-L504","documentation":"Even when rows is a list, each element must be a dict mapping column name to value, because bulk_insert builds an executemany insert. A list whose first element is not a dict (e.g. a list of tuples, a list of model instances, or a list of strings) cannot be mapped to columns.","triggerScenarios":"Calling op.bulk_insert(table, [(1, 'a'), (2, 'b')]) (list of tuples); a list of ORM instances; a list of lists; a list whose first row happens to be None or a non-dict.","commonSituations":"Porting a raw cursor.executemany call that used positional tuples; passing ORM objects directly instead of their __dict__; an empty-but-typed list followed by malformed first row.","solutions":["Convert each row to a dict keyed by column name, e.g. [{'id': i, 'name': n} for i, n in rows].","If you have ORM instances, extract their dict: [{c.name: getattr(o, c.name) for c in table.columns} for o in objects].","For positional data, map explicitly with the column order from table.columns.keys()."],"exampleFix":"// before\nop.bulk_insert(table, [(1, 'a'), (2, 'b')])\n// after\nop.bulk_insert(table, [{'id': 1, 'name': 'a'}, {'id': 2, 'name': 'b'}])","handlingStrategy":"type-guard","validationCode":"rows = list(rows)\nassert all(isinstance(r, dict) for r in rows), \"bulk_insert requires a list of dicts\"","typeGuard":"def is_dict_list(rows) -> bool:\n    return isinstance(rows, list) and (not rows or isinstance(rows[0], dict))","tryCatchPattern":null,"preventionTips":["Map positional tuples to dicts before bulk_insert.","For ORM objects, project to dicts keyed by column name.","Unit-test bulk_insert inputs against the type guard."],"tags":["bulk-insert","parameters","type-check"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}