sqlalchemy/alembic · error · TypeError
List expected
Error message
List expected
What it means
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.
Source
Thrown at alembic/ddl/impl.py:484
self._exec(schema.SetTableComment(table))
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
)View on GitHub (pinned to 44fb345033)
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)).
Example fix
// before
op.bulk_insert(account_table, {'id': 1, 'name': 'acme'})
// after
op.bulk_insert(account_table, [{'id': 1, 'name': 'acme'}]) Defensive patterns
Strategy: type-guard
Validate before calling
rows = list(rows) if not isinstance(rows, list) else rows assert isinstance(rows, list), "bulk_insert requires a list of rows"
Type guard
def is_row_list(rows) -> bool:
return isinstance(rows, list) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/08ad7d36cea98528.json.
Report an issue: GitHub.