From 3b8123965bca6b854ccdeca79713b27136233d9a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 26 Feb 2023 15:34:57 -0500 Subject: [PATCH] use read-only Mapping for values dictionary type Improved typing for the mapping passed to :meth:`.UpdateBase.values` to be more open-ended about collection type, by indicating read-only ``Mapping`` instead of writeable ``Dict``, the latter of which would error out under typing tools on too limited of a key type. Fixes: #9376 Change-Id: Ib7fdbba05ca7e1082409e1b5616e6a010262f032 --- doc/build/changelog/unreleased_20/9376.rst | 8 +++++ lib/sqlalchemy/sql/_typing.py | 4 +++ lib/sqlalchemy/sql/dml.py | 3 +- test/ext/mypy/plain_files/dml.py | 35 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 doc/build/changelog/unreleased_20/9376.rst create mode 100644 test/ext/mypy/plain_files/dml.py diff --git a/doc/build/changelog/unreleased_20/9376.rst b/doc/build/changelog/unreleased_20/9376.rst new file mode 100644 index 0000000000..6d63d09d42 --- /dev/null +++ b/doc/build/changelog/unreleased_20/9376.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, typing + :tickets: 9376 + + Improved typing for the mapping passed to :meth:`.UpdateBase.values` to be + more open-ended about collection type, by indicating read-only ``Mapping`` + instead of writeable ``Dict`` which would error out on too limited of a key + type. diff --git a/lib/sqlalchemy/sql/_typing.py b/lib/sqlalchemy/sql/_typing.py index 6bf9a5a1f4..a828d6a0fb 100644 --- a/lib/sqlalchemy/sql/_typing.py +++ b/lib/sqlalchemy/sql/_typing.py @@ -11,6 +11,7 @@ import operator from typing import Any from typing import Callable from typing import Dict +from typing import Mapping from typing import Set from typing import Tuple from typing import Type @@ -238,6 +239,9 @@ the DMLColumnRole to be able to accommodate. """ +_DMLKey = TypeVar("_DMLKey", bound=_DMLColumnArgument) +_DMLColumnKeyMapping = Mapping[_DMLKey, Any] + _DDLColumnArgument = Union[str, "Column[Any]", roles.DDLConstraintColumnRole] """DDL column. diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index 9042fdff7b..dbbf09f1b9 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -72,6 +72,7 @@ if TYPE_CHECKING: from ._typing import _ColumnExpressionArgument from ._typing import _ColumnsClauseArgument from ._typing import _DMLColumnArgument + from ._typing import _DMLColumnKeyMapping from ._typing import _DMLTableArgument from ._typing import _T0 # noqa from ._typing import _T1 # noqa @@ -944,7 +945,7 @@ class ValuesBase(UpdateBase): def values( self, *args: Union[ - Dict[_DMLColumnArgument, Any], + _DMLColumnKeyMapping[Any], Sequence[Any], ], **kwargs: Any, diff --git a/test/ext/mypy/plain_files/dml.py b/test/ext/mypy/plain_files/dml.py new file mode 100644 index 0000000000..d2ffbf1e16 --- /dev/null +++ b/test/ext/mypy/plain_files/dml.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import Any +from typing import Dict + +from sqlalchemy import Column +from sqlalchemy import insert +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column + + +class Base(DeclarativeBase): + pass + + +class User(Base): + __tablename__ = "user" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] + data: Mapped[str] + + +# test #9376 +d1: dict[str, Any] = {} +stmt1 = insert(User).values(d1) + + +d2: Dict[str, Any] = {} +stmt2 = insert(User).values(d2) + + +d3: Dict[Column[str], Any] = {} +stmt3 = insert(User).values(d3) -- 2.47.2