From: proto-atlas <278522736+proto-atlas@users.noreply.github.com> Date: Mon, 25 May 2026 19:14:40 +0000 (-0400) Subject: Fix Session bulk mappings typing for mapped classes X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=e00937ec549ecc1d2cae49d4fe84fac3d758b6fb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fix Session bulk mappings typing for mapped classes Fixes #9256. This updates the annotations for Session.bulk_insert_mappings() and Session.bulk_update_mappings(). The docstrings and runtime behavior already allow either a mapped class or a Mapper object, but the previous annotations only accepted Mapper[Any]. This patch switches those arguments to the existing _EntityBindKey alias, which matches the inputs accepted by _class_to_mapper(): mapped classes and Mapper objects, but not AliasedClass or AliasedInsp. I also updated the internal _bulk_save_mappings() annotation so the public methods and the private helper stay consistent. The scoped_session proxy output has been kept in sync with tools/generate_proxy_methods.py, and the generator check passes. I added a typing regression test covering both mapped classes and Mapper objects for the two bulk mapping methods. I confirmed that the mapped-class cases fail with the old annotation and pass with this change. Checked locally: python -m pytest -m mypy test/typing/test_mypy.py -k "session.py" -q python -m mypy ./lib/sqlalchemy python tools/generate_proxy_methods.py --check python -m pytest test/orm/dml/test_bulk.py -q python -m pytest -m mypy test/typing/test_mypy.py -k "not typed_queries.py" -q I could not run the full typing suite locally because my local Python 3.12 environment does not include string.templatelib. I only skipped typed_queries.py; that file is expected to be covered by SQLAlchemy's Python 3.14 mypy CI job. Closes: #13322 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13322 Pull-request-sha: bb730f34275a7c40c94e668ecda1131804ba3084 Change-Id: I4c5d516b3933b4e7fae9c844881a61f557a8bb5e --- diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index 4d0a034872..bbebee5c42 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -43,7 +43,6 @@ if TYPE_CHECKING: from ._typing import OrmExecuteOptionsParameter from .identity import IdentityMap from .interfaces import ORMOption - from .mapper import Mapper from .query import Query from .query import RowReturningQuery from .session import _BindArguments @@ -1392,7 +1391,7 @@ class scoped_session(Generic[_S]): def bulk_insert_mappings( self, - mapper: Mapper[Any], + mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]], return_defaults: bool = False, render_nulls: bool = False, @@ -1478,7 +1477,7 @@ class scoped_session(Generic[_S]): ) def bulk_update_mappings( - self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] + self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] ) -> None: r"""Perform a bulk update of the given list of mapping dictionaries. diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index d71d295e38..eae68db6e6 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -4742,7 +4742,7 @@ class Session(_SessionClassMethods, EventTarget): def bulk_insert_mappings( self, - mapper: Mapper[Any], + mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]], return_defaults: bool = False, render_nulls: bool = False, @@ -4824,7 +4824,7 @@ class Session(_SessionClassMethods, EventTarget): ) def bulk_update_mappings( - self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]] + self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]] ) -> None: """Perform a bulk update of the given list of mapping dictionaries. @@ -4873,7 +4873,7 @@ class Session(_SessionClassMethods, EventTarget): def _bulk_save_mappings( self, - mapper: Mapper[_O], + mapper: _EntityBindKey[_O], mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]], *, isupdate: bool, diff --git a/test/typing/plain_files/orm/session.py b/test/typing/plain_files/orm/session.py index 311c522a6d..e614a39c29 100644 --- a/test/typing/plain_files/orm/session.py +++ b/test/typing/plain_files/orm/session.py @@ -10,6 +10,7 @@ from typing import Unpack from sqlalchemy import Column from sqlalchemy import create_engine from sqlalchemy import ForeignKey +from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Result @@ -120,6 +121,12 @@ with Session(e) as sess: with sess.begin() as tx: assert_type(tx, SessionTransaction) + # test #9256 + sess.bulk_insert_mappings(User, [{"id": 1, "name": "u1"}]) + sess.bulk_update_mappings(User, [{"id": 1, "name": "u1"}]) + sess.bulk_insert_mappings(inspect(User), [{"id": 2, "name": "u2"}]) + sess.bulk_update_mappings(inspect(User), [{"id": 2, "name": "u2"}]) + # more result tests in typed_results.py