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

  1. Wrap a single row in a list: op.bulk_insert(table, [{'col': 1}]).
  2. Convert any iterable to a list first: op.bulk_insert(table, list(rows)).
  3. 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

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


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)