]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix Session bulk mappings typing for mapped classes
authorproto-atlas <278522736+proto-atlas@users.noreply.github.com>
Mon, 25 May 2026 19:14:40 +0000 (15:14 -0400)
committerFederico Caselli <cfederico87@gmail.com>
Mon, 25 May 2026 19:18:00 +0000 (21:18 +0200)
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

lib/sqlalchemy/orm/scoping.py
lib/sqlalchemy/orm/session.py
test/typing/plain_files/orm/session.py

index 4d0a034872cfbccd036b7574637e78f33106c13a..bbebee5c4278edf34558a58e3f90dc57f8243f53 100644 (file)
@@ -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.
 
index d71d295e388042f92199d13de45364a4cfb125ca..eae68db6e6e96ab030fff02dab62485aca92a707 100644 (file)
@@ -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,
index 311c522a6d8a5f53641853c009f83aabd61c0a7e..e614a39c290394082ea020af8e68155a0cedaaeb 100644 (file)
@@ -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