sqlalchemy/alembic · error · TypeError
List of dictionaries expected
Error message
List of dictionaries expected
What it means
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.
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].
Example fix
# before op.bulk_insert(table, [(1, 'a'), (2, 'b')]) # after cols = ['id', 'name'] op.bulk_insert(table, [dict(zip(cols, t)) for t in [(1, 'a'), (2, 'b')]])
Defensive patterns
Strategy: type-guard
Validate before calling
def safe_bulk_insert(op, table, rows):
if not isinstance(rows, list) or any(not isinstance(r, dict) for r in rows):
raise TypeError('rows must be a list of dicts')
op.bulk_insert(table, rows) Type guard
def is_list_of_dicts(value) -> bool:
return isinstance(value, list) and all(isinstance(r, dict) for r in value) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Migrating from executemany-style code that used tuples; passing ORM query results (list of model instances) directly into bulk_insert.
Related errors
- List expected
- Can't create table in batch mode
- Can't send params and multiparams at the same time
- Don't know how to comma-format %r
- Individual alter column constructs not supported by MySQL
AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11).
Data as JSON: /api/errors/24882bcfc2e57033.
Report an issue: GitHub.
Appendix: 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 5551b5d35f)