{"id":"08ad7d36cea98528","repo":"sqlalchemy/alembic","slug":"list-expected","errorCode":null,"errorMessage":"List expected","messagePattern":"List expected","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"alembic/ddl/impl.py","lineNumber":484,"sourceCode":"        self._exec(schema.SetTableComment(table))\n\n    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                            )","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/ddl/impl.py#L466-L502","documentation":"Operations.bulk_insert (and the underlying impl.bulk_insert) require rows to be a Python list; passing a single dict, a tuple, a generator, or None is rejected because the implementation iterates and indexes by position to build the insert.","triggerScenarios":"Calling op.bulk_insert(table, {'id': 1, 'name': 'x'}) (a single dict) instead of a list; passing a generator expression; passing a tuple; passing None.","commonSituations":"Mistakenly passing one record as a dict because that's how you'd call table.insert().values(...); passing a tuple from a function that returns one; forgetting to wrap a single row in [] before upgrade.","solutions":["Wrap the data in a list: op.bulk_insert(table, [{'id': 1, 'name': 'x'}]).","Convert tuples/generators to a list before the call: op.bulk_insert(table, list(rows))."],"exampleFix":"// before\nop.bulk_insert(account_table, {'id': 1, 'name': 'acme'})\n// after\nop.bulk_insert(account_table, [{'id': 1, 'name': 'acme'}])","handlingStrategy":"type-guard","validationCode":"rows = list(rows) if not isinstance(rows, list) else rows\nassert isinstance(rows, list), \"bulk_insert requires a list of rows\"","typeGuard":"def is_row_list(rows) -> bool:\n    return isinstance(rows, list)","tryCatchPattern":null,"preventionTips":["Always pass bulk_insert a list literal.","Coerce generators/tuples to list() before the call.","Add a type annotation rows: list[dict] in helper wrappers."],"tags":["bulk-insert","parameters","type-check"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}