sqlalchemy/alembic · error · TypeError

Can't send params and multiparams at the same time

Error message

Can't send params and multiparams at the same time

What it means

The internal _exec passes either params (a single dict for one row) or multiparams (a list of dicts for executemany). SQLAlchemy's connection.execute rejects passing both at once, so Alembic pre-empts that with a clear error. This indicates a caller that built both arguments simultaneously.

Source

Thrown at alembic/ddl/impl.py:245

            else:
                compile_kw = {}

            if TYPE_CHECKING:
                assert isinstance(construct, ClauseElement)
            compiled = construct.compile(dialect=self.dialect, **compile_kw)
            self.static_output(
                str(compiled).replace("\t", "    ").strip()
                + self.command_terminator
            )
            return None
        else:
            conn = self.connection
            assert conn is not None
            if execution_options:
                conn = conn.execution_options(**execution_options)

            if params and multiparams is not None:
                raise TypeError(
                    "Can't send params and multiparams at the same time"
                )

            if multiparams:
                return conn.execute(construct, multiparams)
            else:
                return conn.execute(construct, params)

    def execute(
        self,
        sql: Executable | str,
        execution_options: dict[str, Any] | None = None,
    ) -> None:
        self._exec(sql, execution_options)

    def alter_column(
        self,
        table_name: str,

View on GitHub (pinned to 44fb345033)

Solutions

  1. Pass exactly one of params= or multiparams=. For multiple rows use multiparams=[{...},{...}].
  2. For a single row use params={'k': v} (or omit and pass the dict as the sole argument).
  3. If building dynamically, branch: if isinstance(rows, list) and rows and isinstance(rows[0], dict): use multiparams=rows else params=rows.

Example fix

// before
impl._exec(stmt, params={'a': 1}, multiparams=[{'a': 1}, {'a': 2}])
// after
impl._exec(stmt, multiparams=[{'a': 1}, {'a': 2}])
Defensive patterns

Strategy: validation

Validate before calling

def safe_exec(impl, construct, params=None, multiparams=None):
    assert not (params and multiparams is not None), "Pass params OR multiparams, not both"
    return impl._exec(construct, params=params or util.immutabledict(), multiparams=multiparams)

Prevention

When it happens

Trigger: A caller of impl._exec (internal API) or a custom Op that forwards both params=dict(...) and multiparams=[...] simultaneously; op.bulk_insert routes correctly into multiparams, so this surfaces via direct _exec use or custom operations.

Common situations: A custom op or migration that builds parameters dynamically and accidentally assigns both params and multiparams; mixing a single-row default with a multi-row list; calling impl._exec directly with both kwargs.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/82bd8acab32bea29.json. Report an issue: GitHub.