Skip to content

read_sql and read_sql_query reject a params dict that mixes a scalar and a sequence #1890

Description

@notatallshaw-gts

Describe the bug

params accepts two kinds of dict, Mapping[str, Scalar] and Mapping[str, tuple[Scalar, ...]]. A dict with a date under one key and a tuple under another matches neither. So filtering by a date and an IN list in one query does not type check.

Positional params have the same gap: [date, (1, 2)] matches neither list[Scalar] nor tuple[tuple[Scalar, ...], ...].

pandas itself annotates params as list[Any] | Mapping[str, Any] | None. #997 added tuple support for #996 as a second option instead of widening the value type, which is why no type fits the mixed dict.

To Reproduce

Runs on sqlite and prints [1, 2]. The read_sql_query call is rejected.

import datetime as dt

import pandas as pd
import sqlalchemy as sa

engine = sa.create_engine("sqlite://")
with engine.begin() as con:
    con.execute(sa.text("CREATE TABLE t (id INTEGER, d DATE)"))
    con.execute(sa.text("INSERT INTO t VALUES (1, '2026-08-11'), (2, '2026-08-11')"))

statement = sa.text("SELECT id FROM t WHERE d = :d AND id IN :ids").bindparams(
    sa.bindparam("ids", expanding=True)
)
with engine.connect() as con:
    df = pd.read_sql_query(statement, con, params={"d": dt.date(2026, 8, 11), "ids": (1, 2)})

print(df["id"].tolist())

pyright 1.1.406:

error: Argument of type "dict[str, date | tuple[int, int]]" cannot be assigned to parameter
"params" of type "list[Scalar] | ... | Mapping[str, tuple[Scalar, ...]] | None"
in function "read_sql_query"
    Type parameter "_VT_co@Mapping" is covariant, but "date | tuple[int, int]" is not a
    subtype of "Scalar"

mypy 1.19.1, pyrefly 1.2.0 and ty 0.0.69 reject it the same way.

System specifications

  • OS and its version: Rocky Linux 9 / Linux 6.1.29
  • python version: 3.12.13
  • type checker and its version: pyright 1.1.406, mypy 1.19.1, pyrefly 1.2.0, ty 0.0.69
  • version of installed pandas-stubs: 3.0.5.260730 (pandas 3.0.5, SQLAlchemy 2.0.51)

Additional context

One type for the bind value fixes both cases:

_SQLBindValue: TypeAlias = Scalar | None | Sequence[Scalar | None]

params: Sequence[_SQLBindValue] | Mapping[str, _SQLBindValue] | None = None,

This covers all five current options, so nothing that passes today starts failing. None is included because binding NULL is common and Scalar omits it. Sets and nested sequences stay rejected.

#997's tuple test is skipped because it needed Postgres; the example above runs on sqlite, so it can be unskipped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    IO SQLto_sql, read_sql, read_sql_query

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions