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

DefaultImpl._exec raises TypeError when both params (a single parameter dict) and multiparams (a list of parameter dicts) are passed simultaneously to the same execution call. SQLAlchemy's execute() accepts one form or the other, not both, so Alembic rejects the combination early.

Solutions

  1. Pass only one parameter style: use params for a single row, or multiparams for multiple rows.
  2. If you have a single row, put it in multiparams=[row] and omit params.
  3. Audit custom impl overrides of _exec to ensure they never forward both arguments.

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 call_exec(impl, construct, params=None, multiparams=None):
    if params and multiparams is not None:
        raise ValueError('Pass params XOR multiparams, not both')
    impl._exec(construct, params=params or {}, multiparams=multiparams)

Type guard

def has_single_param_style(params, multiparams) -> bool:
    return not (params and multiparams is not None)

Prevention

When it happens

Trigger: Calling impl._exec(construct, params={...}, multiparams=[...]) directly, or higher-level code that forwards both to conn.execute. Typically arises inside custom impl subclasses or patched execution helpers.

Common situations: Custom DefaultImpl subclasses overriding _exec and forwarding both; code that naively merges single-row and multi-row execution into one call.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/82bd8acab32bea29. Report an issue: GitHub.

Appendix: 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 5551b5d35f)