From: Mike Bayer Date: Thu, 20 Jan 2022 14:31:42 +0000 (-0500) Subject: repair mapper sort X-Git-Tag: rel_2_0_0b1~528^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=769dc1d2cb217c66918bec01718aa657a0239040;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git repair mapper sort Fixed issue in :meth:`_orm.Session.bulk_save_mappings` where the sorting that takes place when the ``preserve_order`` parameter is set to False would sort partially on ``Mapper`` objects, which is rejected in Python 3.11. Also uses typing_extensions for NotRequired as this symbol does not seem to be in Python 3.11.0a4 yet. For interim 3.11 support, adds the git main build of greenlet for Python 3.11 Fixes: #7591 Change-Id: I24a62f2322ad7dac5d8e4a00853f8a9408877c9c --- diff --git a/doc/build/changelog/unreleased_14/7591.rst b/doc/build/changelog/unreleased_14/7591.rst new file mode 100644 index 0000000000..4ecf983d11 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7591.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 7591 + + Fixed issue in :meth:`_orm.Session.bulk_save_mappings` where the sorting + that takes place when the ``preserve_order`` parameter is set to False + would sort partially on ``Mapper`` objects, which is rejected in Python + 3.11. + diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index aa642e65ee..cf47ee7299 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -3389,14 +3389,24 @@ class Session(_SessionClassMethods): """ - def key(state): - return (state.mapper, state.key is not None) - obj_states = (attributes.instance_state(obj) for obj in objects) + if not preserve_order: - obj_states = sorted(obj_states, key=key) + # the purpose of this sort is just so that common mappers + # and persistence states are grouped together, so that groupby + # will return a single group for a particular type of mapper. + # it's not trying to be deterministic beyond that. + obj_states = sorted( + obj_states, + key=lambda state: (id(state.mapper), state.key is not None), + ) - for (mapper, isupdate), states in itertools.groupby(obj_states, key): + def grouping_key(state): + return (state.mapper, state.key is not None) + + for (mapper, isupdate), states in itertools.groupby( + obj_states, grouping_key + ): self._bulk_save_mappings( mapper, states, diff --git a/lib/sqlalchemy/util/typing.py b/lib/sqlalchemy/util/typing.py index e2d42db6e0..5767d258b0 100644 --- a/lib/sqlalchemy/util/typing.py +++ b/lib/sqlalchemy/util/typing.py @@ -6,6 +6,8 @@ from typing import Type from typing import TypeVar from typing import Union +from typing_extensions import NotRequired # noqa + from . import compat _T = TypeVar("_T", bound=Any) @@ -26,11 +28,6 @@ else: from typing_extensions import Concatenate # noqa from typing_extensions import ParamSpec # noqa -if compat.py311: - from typing import NotRequired # noqa -else: - from typing_extensions import NotRequired # noqa - _T = TypeVar("_T") diff --git a/setup.cfg b/setup.cfg index c88cd96d5c..e841348cec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ package_dir = install_requires = importlib-metadata;python_version<"3.8" greenlet != 0.4.17;(platform_machine=='aarch64' or (platform_machine=='ppc64le' or (platform_machine=='x86_64' or (platform_machine=='amd64' or (platform_machine=='AMD64' or (platform_machine=='win32' or platform_machine=='WIN32')))))) - typing-extensions >= 4;python_version<"3.11" + typing-extensions >= 4 [options.extras_require] asyncio = diff --git a/tox.ini b/tox.ini index def2108260..89bac9cc26 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,9 @@ deps= pytest>=7.0.0rc1,<8 pytest-xdist + # note cython not working for 3.11 at all right now + git+https://github.com/python-greenlet/greenlet.git#egg=greenlet; python_version >= '3.11' + sqlite: .[aiosqlite] sqlite_file: .[aiosqlite] sqlite_file: .[sqlcipher]; python_version < '3.10'