sqlalchemy/alembic · error · TypeError
List expected
Error message
List expected
What it means
DefaultImpl.bulk_insert raises TypeError when the rows argument is not a Python list. bulk_insert expects a list of row dictionaries so it can build an INSERT (or multi-row INSERT); passing a single dict, tuple, or other iterable is rejected.
Solutions
- Wrap a single row in a list: op.bulk_insert(table, [{'col': 1}]).
- Convert any iterable to a list first: op.bulk_insert(table, list(rows)).
- Ensure each element is a dict mapping column names to values.
Example fix
# before
op.bulk_insert(accounts_table, {'name': 'acct', 'active': True})
# after
op.bulk_insert(accounts_table, [{'name': 'acct', 'active': True}]) Defensive patterns
Strategy: type-guard
Validate before calling
def safe_bulk_insert(op, table, rows):
if not isinstance(rows, list):
raise TypeError('rows must be a list of dicts')
op.bulk_insert(table, rows) Type guard
def is_list_of_rows(value) -> bool:
return isinstance(value, list) Prevention
- Always pass a list of dicts to op.bulk_insert.
- Wrap single rows: [row].
- Add a type assertion in shared migration helpers.
When it happens
Trigger: Calling op.bulk_insert(table, {'col': 1}) (a single dict instead of a list); passing a generator, tuple, or ORM object instead of a list of dicts.
Common situations: Authors forgetting to wrap a single row in a list; passing a SQLAlchemy Result or an ORM model instance; code migrated from a function that yielded rows.
Related errors
- List of dictionaries 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/08ad7d36cea98528.
Report an issue: GitHub.
Appendix: 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 5551b5d35f)