sqlalchemy/alembic · error · TypeError
List of dictionaries expected
Error message
List of dictionaries expected
What it means
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.
Source
Thrown at alembic/ddl/impl.py:486
def drop_table_comment(self, table: Table) -> None:
self._exec(schema.DropTableComment(table))
def create_column_comment(self, column: Column[Any]) -> None:
self._exec(schema.SetColumnComment(column))
def drop_index(self, index: Index, **kw: Any) -> None:
self._exec(schema.DropIndex(index, **kw))
def bulk_insert(
self,
table: TableClause | Table,
rows: list[dict],
multiinsert: bool = True,
) -> None:
if not isinstance(rows, list):
raise TypeError("List expected")
elif rows and not isinstance(rows[0], dict):
raise TypeError("List of dictionaries expected")
if self.as_sql:
for row in rows:
self._exec(
table.insert()
.inline()
.values(
**{
k: (
sqla_compat._literal_bindparam(
k, v, type_=table.c[k].type
)
if not isinstance(
v, sqla_compat._literal_bindparam
)
else v
)
for k, v in row.items()
}View on GitHub (pinned to 44fb345033)
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().
Example fix
// before
op.bulk_insert(table, [(1, 'a'), (2, 'b')])
// after
op.bulk_insert(table, [{'id': 1, 'name': 'a'}, {'id': 2, 'name': 'b'}]) Defensive patterns
Strategy: type-guard
Validate before calling
rows = list(rows) assert all(isinstance(r, dict) for r in rows), "bulk_insert requires a list of dicts"
Type guard
def is_dict_list(rows) -> bool:
return isinstance(rows, list) and (not rows or isinstance(rows[0], dict)) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/24882bcfc2e57033.json.
Report an issue: GitHub.