]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Removed support for Python 3.8 since it's EOL.
authorFederico Caselli <cfederico87@gmail.com>
Sat, 26 Oct 2024 20:11:15 +0000 (22:11 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Tue, 5 Nov 2024 20:23:34 +0000 (21:23 +0100)
Fixes: #12029
Change-Id: Ibb4efec9bab0225d03f6bf3fed661a3f2fc72cc7

36 files changed:
.github/workflows/create-wheels.yaml
.github/workflows/run-test.yaml
doc/build/changelog/unreleased_21/10357.rst
doc/build/intro.rst
doc/build/orm/collection_api.rst
lib/sqlalchemy/engine/cursor.py
lib/sqlalchemy/testing/fixtures/mypy.py
lib/sqlalchemy/testing/requirements.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/compat.py
lib/sqlalchemy/util/langhelpers.py
lib/sqlalchemy/util/typing.py
pyproject.toml
test/base/test_utils.py
test/dialect/mysql/test_dialect.py
test/engine/test_execute.py
test/engine/test_transaction.py
test/ext/asyncio/test_engine_py3k.py
test/ext/mypy/plugin_files/mixin_not_mapped.py
test/ext/mypy/plugin_files/orderinglist1.py
test/ext/mypy/plugin_files/orderinglist2.py
test/ext/mypy/plugin_files/relationship_err2.py
test/ext/mypy/plugin_files/relationship_err3.py
test/orm/declarative/test_basic.py
test/orm/declarative/test_dc_transforms.py
test/orm/inheritance/test_relationship.py
test/orm/test_bind.py
test/orm/test_events.py
test/orm/test_transaction.py
test/orm/test_versioning.py
test/sql/test_resultset.py
test/typing/plain_files/dialects/postgresql/pg_stuff.py
test/typing/plain_files/orm/session.py
test/typing/plain_files/orm/typed_queries.py
test/typing/plain_files/sql/typed_results.py
tox.ini

index f9732bf09a3a574fc9b4f33b0c55022ffa8d767e..4c191d26789f674a4e3a2d0e12ea7b7f45db2bf6 100644 (file)
@@ -20,7 +20,7 @@ jobs:
       matrix:
         # emulated wheels on linux take too much time, split wheels into multiple runs
         python:
-          - "cp38-* cp39-*"
+          - "cp39-*"
           - "cp310-* cp311-*"
           - "cp312-* cp313-*"
         wheel_mode:
index 5e2b696e3efd844baf9ec7d51542912e1b289e6b..133997b5d31aba384f2c20cb78b5bafa9ee83d88 100644 (file)
@@ -31,12 +31,11 @@ jobs:
           - "macos-latest"
           - "macos-13"
         python-version:
-          - "3.8"
           - "3.9"
           - "3.10"
           - "3.11"
           - "3.12"
-          - "3.13.0-alpha - 3.13"
+          - "3.13"
           - "pypy-3.10"
         build-type:
           - "cext"
@@ -68,8 +67,6 @@ jobs:
             architecture: x86
           - os: "macos-latest"
             architecture: x64
-          - os: "macos-latest"
-            python-version: "3.8"
           - os: "macos-latest"
             python-version: "3.9"
           # macos 13: uses intel macs. no arm64, x86
@@ -120,7 +117,6 @@ jobs:
     strategy:
       matrix:
         python-version:
-          - cp38-cp38
           - cp39-cp39
           - cp310-cp310
           - cp311-cp311
@@ -162,12 +158,11 @@ jobs:
         os:
           - "ubuntu-latest"
         python-version:
-          - "3.8"
           - "3.9"
           - "3.10"
           - "3.11"
           - "3.12"
-          - "3.13.0-alpha - 3.13"
+          - "3.13"
         tox-env:
           - mypy
           - pep484
@@ -179,8 +174,6 @@ jobs:
             os: "ubuntu-latest"
         exclude:
           # run pep484 only on 3.10+
-          - tox-env: pep484
-            python-version: "3.8"
           - tox-env: pep484
             python-version: "3.9"
 
index 37fa158f67d7656f3f464fc2d3250d2db4f8f650..22772678fa1debff45c65a8221e84fdf545121e4 100644 (file)
@@ -1,6 +1,6 @@
 .. change::
     :tags: change, installation
-    :tickets: 10357
+    :tickets: 10357, 12029
 
-    Python 3.8 or above is now required; support for Python 3.7 is dropped as
-    this version is EOL.
+    Python 3.9 or above is now required; support for Python 3.8 and 3.7 is
+    dropped as these versions are EOL.
index ee93cc32950ac97bd43a50325dc4e0cbfca29ad3..cba95ab69e78f33764a864ec2e107b4a89b56c62 100644 (file)
@@ -96,11 +96,11 @@ Supported Platforms
 
 SQLAlchemy 2.1 supports the following platforms:
 
-* cPython 3.8 and higher
+* cPython 3.9 and higher
 * Python-3 compatible versions of `PyPy <http://pypy.org/>`_
 
 .. versionchanged:: 2.1
-   SQLAlchemy now targets Python 3.8 and above.
+   SQLAlchemy now targets Python 3.9 and above.
 
 
 Supported Installation Methods
index 07e4a4ce880a754a89e2bf33f019ee78117500a0..2d490d7e55ffd3b3e81452ff5655923e2b81720e 100644 (file)
@@ -47,7 +47,7 @@ below where ``list`` is used::
         parent_id: Mapped[int] = mapped_column(primary_key=True)
 
         # use a list
-        children: Mapped[List["Child"]] = relationship()
+        children: Mapped[list["Child"]] = relationship()
 
 
     class Child(Base):
@@ -59,7 +59,6 @@ below where ``list`` is used::
 Or for a ``set``, illustrated in the same
 ``Parent.children`` collection::
 
-    from typing import Set
     from sqlalchemy import ForeignKey
 
     from sqlalchemy.orm import DeclarativeBase
@@ -78,7 +77,7 @@ Or for a ``set``, illustrated in the same
         parent_id: Mapped[int] = mapped_column(primary_key=True)
 
         # use a set
-        children: Mapped[Set["Child"]] = relationship()
+        children: Mapped[set["Child"]] = relationship()
 
 
     class Child(Base):
@@ -87,22 +86,6 @@ Or for a ``set``, illustrated in the same
         child_id: Mapped[int] = mapped_column(primary_key=True)
         parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
 
-.. note::  If using Python 3.8, annotations for collections need
-   to use ``typing.List`` or ``typing.Set``, e.g. ``Mapped[List["Child"]]`` or
-   ``Mapped[Set["Child"]]``; the ``list`` and ``set`` Python built-ins
-   don't yet support generic annotation in these Python versions, such as::
-
-       from typing import List
-
-
-       class Parent(Base):
-           __tablename__ = "parent"
-
-           parent_id: Mapped[int] = mapped_column(primary_key=True)
-
-           # use a List, Python 3.8 and earlier
-           children: Mapped[List["Child"]] = relationship()
-
 When using mappings without the :class:`_orm.Mapped` annotation, such as when
 using :ref:`imperative mappings <orm_imperative_mapping>` or untyped
 Python code, as well as in a few special cases, the collection class for a
index 8a2a47cb897c462e20de6d53b9813bbe26d79f6f..491ef9e443d5f3871a8ed0c26b5b083415f43fdf 100644 (file)
@@ -49,7 +49,6 @@ from ..sql.compiler import RM_OBJECTS
 from ..sql.compiler import RM_RENDERED_NAME
 from ..sql.compiler import RM_TYPE
 from ..sql.type_api import TypeEngine
-from ..util import compat
 from ..util.typing import Literal
 from ..util.typing import Self
 from ..util.typing import TupleAny
@@ -325,16 +324,14 @@ class CursorResultMetaData(ResultMetaData):
 
         assert not self._tuplefilter
         return self._make_new_metadata(
-            keymap=compat.dict_union(
-                self._keymap,
-                {
-                    new: keymap_by_position[idx]
-                    for idx, new in enumerate(
-                        invoked_statement._all_selected_columns
-                    )
-                    if idx in keymap_by_position
-                },
-            ),
+            keymap=self._keymap
+            | {
+                new: keymap_by_position[idx]
+                for idx, new in enumerate(
+                    invoked_statement._all_selected_columns
+                )
+                if idx in keymap_by_position
+            },
             unpickled=self._unpickled,
             processors=self._processors,
             tuplefilter=None,
index 149df9f7d4950edc39c61a75b531e68229e597b8..5a167d2b40a33af7bbe2adeb9954409670120112 100644 (file)
@@ -203,22 +203,6 @@ class MypyTest(TestBase):
                         is_mypy = is_re = True
                         expected_msg = f'Revealed type is "{expected_msg}"'
 
-                    if mypy_14 and util.py39:
-                        # use_lowercase_names, py39 and above
-                        # https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L363  # noqa: E501
-
-                        # skip first character which could be capitalized
-                        # "List item x not found" type of message
-                        expected_msg = expected_msg[0] + re.sub(
-                            (
-                                r"\b(List|Tuple|Dict|Set)\b"
-                                if is_type
-                                else r"\b(List|Tuple|Dict|Set|Type)\b"
-                            ),
-                            lambda m: m.group(1).lower(),
-                            expected_msg[1:],
-                        )
-
                     if mypy_14 and util.py310:
                         # use_or_syntax, py310 and above
                         # https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L368  # noqa: E501
index 544f87ec9916fc66e5ffb4ab4307c68e9d7f8ef2..b1d3d0f085a46c93cf4837049bb15c9a1b061f92 100644 (file)
@@ -1516,12 +1516,6 @@ class SuiteRequirements(Requirements):
 
         return exclusions.skip_if(check)
 
-    @property
-    def python39(self):
-        return exclusions.only_if(
-            lambda: util.py39, "Python 3.9 or above required"
-        )
-
     @property
     def python310(self):
         return exclusions.only_if(
index ca3d6b8b55eb490e6790a15430ba4e330d4d1931..16c109c0bbca3b16132b79273ece27ab7344de0c 100644 (file)
@@ -66,7 +66,6 @@ from .compat import py310 as py310
 from .compat import py311 as py311
 from .compat import py312 as py312
 from .compat import py313 as py313
-from .compat import py39 as py39
 from .compat import pypy as pypy
 from .compat import win32 as win32
 from .concurrency import await_ as await_
index 01643e05c335f80638ed2c57c1c51613b2091c83..e7511c94fca3101ceac4952e4100639730aa61bd 100644 (file)
@@ -35,7 +35,6 @@ py313 = sys.version_info >= (3, 13)
 py312 = sys.version_info >= (3, 12)
 py311 = sys.version_info >= (3, 11)
 py310 = sys.version_info >= (3, 10)
-py39 = sys.version_info >= (3, 9)
 pypy = platform.python_implementation() == "PyPy"
 cpython = platform.python_implementation() == "CPython"
 
@@ -97,27 +96,10 @@ def inspect_getfullargspec(func: Callable[..., Any]) -> FullArgSpec:
     )
 
 
-if py39:
-    # python stubs don't have a public type for this. not worth
-    # making a protocol
-    def md5_not_for_security() -> Any:
-        return hashlib.md5(usedforsecurity=False)
-
-else:
-
-    def md5_not_for_security() -> Any:
-        return hashlib.md5()
-
-
-if typing.TYPE_CHECKING or py39:
-    # pep 584 dict union
-    dict_union = operator.or_  # noqa
-else:
-
-    def dict_union(a: dict, b: dict) -> dict:
-        a = a.copy()
-        a.update(b)
-        return a
+# python stubs don't have a public type for this. not worth
+# making a protocol
+def md5_not_for_security() -> Any:
+    return hashlib.md5(usedforsecurity=False)
 
 
 if py310:
index 632e6a0a5678067a5ee1054ef89cdd35c844bb15..82cfca8c557bbee4827200925d1585bd881f3b17 100644 (file)
@@ -66,15 +66,11 @@ if compat.py310:
 else:
 
     def get_annotations(obj: Any) -> Mapping[str, Any]:
-        # it's been observed that cls.__annotations__ can be non present.
-        # it's not clear what causes this, running under tox py38 it
-        # happens, running straight pytest it doesnt
-
         # https://docs.python.org/3/howto/annotations.html#annotations-howto
         if isinstance(obj, type):
             ann = obj.__dict__.get("__annotations__", None)
         else:
-            ann = getattr(obj, "__annotations__", None)
+            ann = obj.__annotations__
 
         if ann is None:
             return _collections.EMPTY_DICT
index 3366fca4993f61f5956b123e300931e7dec26e26..7510e7a3872b11bd467dadd82ac2ae551611196a 100644 (file)
@@ -368,8 +368,7 @@ def is_literal(type_: _AnnotationScanType) -> bool:
 
 def is_newtype(type_: Optional[_AnnotationScanType]) -> TypeGuard[NewType]:
     return hasattr(type_, "__supertype__")
-
-    # doesn't work in 3.8, 3.7 as it passes a closure, not an
+    # doesn't work in 3.9, 3.8, 3.7 as it passes a closure, not an
     # object instance
     # return isinstance(type_, NewType)
 
index 38867508dbdc15fb6cdc407b4a234564216f4c0a..eebbd725bc6341a3449d045fe72868ec1b1af3af 100644 (file)
@@ -19,7 +19,6 @@ classifiers = [
     "Operating System :: OS Independent",
     "Programming Language :: Python",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
@@ -29,7 +28,7 @@ classifiers = [
     "Programming Language :: Python :: Implementation :: PyPy",
     "Topic :: Database :: Front-Ends",
 ]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 dependencies = [
     "typing-extensions >= 4.6.0",
 ]
@@ -118,7 +117,7 @@ tag-build = "dev"
 
 [tool.black]
 line-length = 79
-target-version = ['py38']
+target-version = ['py39']
 
 
 [tool.zimports]
index 0ca60c793136e1222486251293c8b76f4810499d..77ab9ff222bf8f561453da5025d8d2d0c0128ac0 100644 (file)
@@ -470,7 +470,6 @@ class ImmutableDictTest(fixtures.TestBase):
         i2 = util.immutabledict({"a": 42, 42: "a"})
         eq_(str(i2), "immutabledict({'a': 42, 42: 'a'})")
 
-    @testing.requires.python39
     def test_pep584(self):
         i = util.immutabledict({"a": 2})
         with expect_raises_message(TypeError, "object is immutable"):
@@ -3644,9 +3643,12 @@ class CyExtensionTest(fixtures.TestBase):
             import setuptools  # noqa: F401
         except ImportError:
             testing.skip_test("setuptools is required")
-        with mock.patch("setuptools.setup", mock.MagicMock()), mock.patch.dict(
-            "os.environ",
-            {"DISABLE_SQLALCHEMY_CEXT": "", "REQUIRE_SQLALCHEMY_CEXT": ""},
+        with (
+            mock.patch("setuptools.setup", mock.MagicMock()),
+            mock.patch.dict(
+                "os.environ",
+                {"DISABLE_SQLALCHEMY_CEXT": "", "REQUIRE_SQLALCHEMY_CEXT": ""},
+            ),
         ):
             import setup
 
index cf74f17ad669ee2bb8ce73f70d3e56e288e15cc8..23dbd39957f8a19a804fe0f5a5386545bf914c8e 100644 (file)
@@ -40,18 +40,22 @@ class BackendDialectTest(
         """
         engine = testing_engine()
         _server_version = [None]
-        with mock.patch.object(
-            engine.dialect,
-            "_get_server_version_info",
-            lambda conn: engine.dialect._parse_server_version(
-                _server_version[0]
+        with (
+            mock.patch.object(
+                engine.dialect,
+                "_get_server_version_info",
+                lambda conn: engine.dialect._parse_server_version(
+                    _server_version[0]
+                ),
+            ),
+            mock.patch.object(
+                engine.dialect, "_set_mariadb", lambda *arg: None
+            ),
+            mock.patch.object(
+                engine.dialect,
+                "get_isolation_level",
+                lambda *arg: "REPEATABLE READ",
             ),
-        ), mock.patch.object(
-            engine.dialect, "_set_mariadb", lambda *arg: None
-        ), mock.patch.object(
-            engine.dialect,
-            "get_isolation_level",
-            lambda *arg: "REPEATABLE READ",
         ):
 
             def go(server_version):
index 148d0be1a28527a7017da5bd86c95878f91adedc..df70bac14f3971c6bdfcbbb3c1744feb8c5aa4e6 100644 (file)
@@ -427,20 +427,24 @@ class ExecuteTest(fixtures.TablesTest):
         # TODO: this test is assuming too much of arbitrary dialects and would
         # be better suited tested against a single mock dialect that does not
         # have any special behaviors
-        with patch.object(
-            testing.db.dialect, "dbapi", Mock(Error=DBAPIError)
-        ), patch.object(
-            testing.db.dialect, "loaded_dbapi", Mock(Error=DBAPIError)
-        ), patch.object(
-            testing.db.dialect, "is_disconnect", lambda *arg: False
-        ), patch.object(
-            testing.db.dialect,
-            "do_execute",
-            Mock(side_effect=NonStandardException),
-        ), patch.object(
-            testing.db.dialect.execution_ctx_cls,
-            "handle_dbapi_exception",
-            Mock(),
+        with (
+            patch.object(testing.db.dialect, "dbapi", Mock(Error=DBAPIError)),
+            patch.object(
+                testing.db.dialect, "loaded_dbapi", Mock(Error=DBAPIError)
+            ),
+            patch.object(
+                testing.db.dialect, "is_disconnect", lambda *arg: False
+            ),
+            patch.object(
+                testing.db.dialect,
+                "do_execute",
+                Mock(side_effect=NonStandardException),
+            ),
+            patch.object(
+                testing.db.dialect.execution_ctx_cls,
+                "handle_dbapi_exception",
+                Mock(),
+            ),
         ):
             with testing.db.connect() as conn:
                 assert_raises(
@@ -1001,11 +1005,14 @@ class ConvenienceExecuteTest(fixtures.TablesTest):
         engine = engines.testing_engine()
 
         close_mock = Mock()
-        with mock.patch.object(
-            engine._connection_cls,
-            "begin",
-            Mock(side_effect=Exception("boom")),
-        ), mock.patch.object(engine._connection_cls, "close", close_mock):
+        with (
+            mock.patch.object(
+                engine._connection_cls,
+                "begin",
+                Mock(side_effect=Exception("boom")),
+            ),
+            mock.patch.object(engine._connection_cls, "close", close_mock),
+        ):
             with expect_raises_message(Exception, "boom"):
                 with engine.begin():
                     pass
@@ -1894,11 +1901,12 @@ class EngineEventsTest(fixtures.TestBase):
             # as part of create
             # note we can't use an event to ensure begin() is not called
             # because create also blocks events from happening
-            with mock.patch.object(
-                e1.dialect, "initialize", side_effect=init
-            ) as m1, mock.patch.object(
-                e1._connection_cls, "begin"
-            ) as begin_mock:
+            with (
+                mock.patch.object(
+                    e1.dialect, "initialize", side_effect=init
+                ) as m1,
+                mock.patch.object(e1._connection_cls, "begin") as begin_mock,
+            ):
 
                 @event.listens_for(e1, "connect", insert=True)
                 def go1(dbapi_conn, xyz):
@@ -2536,11 +2544,14 @@ class EngineEventsTest(fixtures.TestBase):
         def conn_tracker(conn, opt):
             opt["conn_tracked"] = True
 
-        with mock.patch.object(
-            engine.dialect, "set_connection_execution_options"
-        ) as conn_opt, mock.patch.object(
-            engine.dialect, "set_engine_execution_options"
-        ) as engine_opt:
+        with (
+            mock.patch.object(
+                engine.dialect, "set_connection_execution_options"
+            ) as conn_opt,
+            mock.patch.object(
+                engine.dialect, "set_engine_execution_options"
+            ) as engine_opt,
+        ):
             e2 = engine.execution_options(e1="opt_e1")
             c1 = engine.connect()
             c2 = c1.execution_options(c1="opt_c1")
@@ -3493,11 +3504,12 @@ class OnConnectTest(fixtures.TestBase):
             nonlocal init_connection
             init_connection = connection
 
-        with mock.patch.object(
-            e._connection_cls, "begin"
-        ) as mock_begin, mock.patch.object(
-            e.dialect, "initialize", Mock(side_effect=mock_initialize)
-        ) as mock_init:
+        with (
+            mock.patch.object(e._connection_cls, "begin") as mock_begin,
+            mock.patch.object(
+                e.dialect, "initialize", Mock(side_effect=mock_initialize)
+            ) as mock_init,
+        ):
             conn = e.connect()
 
             eq_(mock_begin.mock_calls, [])
@@ -3928,12 +3940,16 @@ class SetInputSizesTest(fixtures.TablesTest):
         # "safe" datatypes so that the DBAPI does not actually need
         # setinputsizes() called in order to work.
 
-        with mock.patch.object(
-            engine.dialect, "bind_typing", BindTyping.SETINPUTSIZES
-        ), mock.patch.object(
-            engine.dialect, "do_set_input_sizes", do_set_input_sizes
-        ), mock.patch.object(
-            engine.dialect.execution_ctx_cls, "pre_exec", pre_exec
+        with (
+            mock.patch.object(
+                engine.dialect, "bind_typing", BindTyping.SETINPUTSIZES
+            ),
+            mock.patch.object(
+                engine.dialect, "do_set_input_sizes", do_set_input_sizes
+            ),
+            mock.patch.object(
+                engine.dialect.execution_ctx_cls, "pre_exec", pre_exec
+            ),
         ):
             yield engine, canary
 
index fb67c7434fed8320496d7a4533a616d2e08f20bc..182d680f0c92875ea1c39d400c8f2df3bd6416cc 100644 (file)
@@ -1263,12 +1263,13 @@ class IsolationLevelTest(fixtures.TestBase):
 
     def test_underscore_replacement(self, connection_no_trans):
         conn = connection_no_trans
-        with mock.patch.object(
-            conn.dialect, "set_isolation_level"
-        ) as mock_sil, mock.patch.object(
-            conn.dialect,
-            "_gen_allowed_isolation_levels",
-            mock.Mock(return_value=["READ COMMITTED", "REPEATABLE READ"]),
+        with (
+            mock.patch.object(conn.dialect, "set_isolation_level") as mock_sil,
+            mock.patch.object(
+                conn.dialect,
+                "_gen_allowed_isolation_levels",
+                mock.Mock(return_value=["READ COMMITTED", "REPEATABLE READ"]),
+            ),
         ):
             conn.execution_options(isolation_level="REPEATABLE_READ")
             dbapi_conn = conn.connection.dbapi_connection
@@ -1277,12 +1278,13 @@ class IsolationLevelTest(fixtures.TestBase):
 
     def test_casing_replacement(self, connection_no_trans):
         conn = connection_no_trans
-        with mock.patch.object(
-            conn.dialect, "set_isolation_level"
-        ) as mock_sil, mock.patch.object(
-            conn.dialect,
-            "_gen_allowed_isolation_levels",
-            mock.Mock(return_value=["READ COMMITTED", "REPEATABLE READ"]),
+        with (
+            mock.patch.object(conn.dialect, "set_isolation_level") as mock_sil,
+            mock.patch.object(
+                conn.dialect,
+                "_gen_allowed_isolation_levels",
+                mock.Mock(return_value=["READ COMMITTED", "REPEATABLE READ"]),
+            ),
         ):
             conn.execution_options(isolation_level="repeatable_read")
             dbapi_conn = conn.connection.dbapi_connection
@@ -1645,9 +1647,12 @@ class ResetFixture:
         event.listen(engine, "rollback_twophase", harness.rollback_twophase)
         event.listen(engine, "commit_twophase", harness.commit_twophase)
 
-        with mock.patch.object(
-            engine.dialect, "do_rollback", harness.do_rollback
-        ), mock.patch.object(engine.dialect, "do_commit", harness.do_commit):
+        with (
+            mock.patch.object(
+                engine.dialect, "do_rollback", harness.do_rollback
+            ),
+            mock.patch.object(engine.dialect, "do_commit", harness.do_commit),
+        ):
             yield harness
 
         event.remove(engine, "rollback", harness.rollback)
index 60edbf608d924382acb69a0646bb814159f802fd..a37b088c7df536a4a4ad215f55e9a48ebea0a5eb 100644 (file)
@@ -372,11 +372,14 @@ class AsyncEngineTest(EngineFixture):
             # the thing here that emits the warning is the correct path
             from sqlalchemy.pool.base import _finalize_fairy
 
-            with mock.patch.object(
-                pool._dialect,
-                "do_rollback",
-                mock.Mock(side_effect=Exception("can't run rollback")),
-            ), mock.patch("sqlalchemy.util.warn") as m:
+            with (
+                mock.patch.object(
+                    pool._dialect,
+                    "do_rollback",
+                    mock.Mock(side_effect=Exception("can't run rollback")),
+                ),
+                mock.patch("sqlalchemy.util.warn") as m,
+            ):
                 _finalize_fairy(
                     None, rec, pool, ref, echo, transaction_was_reset=False
                 )
index 9a4865eb6d351ac7efaba477f0cb28d4a0532554..e9aa336c8dae42426e84350fab69cd6da2911006 100644 (file)
@@ -33,9 +33,9 @@ class Bar(HasUpdatedAt, Base):
 
 Bar.__mapper__
 
-# EXPECTED_MYPY: "Type[HasUpdatedAt]" has no attribute "__mapper__"
+# EXPECTED_MYPY: "type[HasUpdatedAt]" has no attribute "__mapper__"
 HasUpdatedAt.__mapper__
 
 
-# EXPECTED_MYPY: "Type[SomeAbstract]" has no attribute "__mapper__"
+# EXPECTED_MYPY: "type[SomeAbstract]" has no attribute "__mapper__"
 SomeAbstract.__mapper__
index 661d55a7b6a8acdb29a601a3b7114c385e6ad7d2..fb05b767a5b01715633fc52e1f3131f05a3be681 100644 (file)
@@ -21,5 +21,5 @@ class A:
 
 a1 = A(id=5, ordering=10)
 
-# EXPECTED_MYPY: Argument "parents" to "A" has incompatible type "List[A]"; expected "Mapped[Any]"  # noqa
+# EXPECTED_MYPY: Argument "parents" to "A" has incompatible type "list[A]"; expected "Mapped[Any]"  # noqa
 a2 = A(parents=[a1])
index eb50c5391bef9952dfd32975be3bcde425439619..d8b179e9a7478af31a5143f3553622faf1b9d3ce 100644 (file)
@@ -37,10 +37,10 @@ class A:
         B, collection_class=ordering_list("ordering")
     )
 
-    # EXPECTED: Left hand assignment 'cs: "List[B]"' not compatible with ORM mapped expression of type "Mapped[List[C]]"  # noqa
+    # EXPECTED: Left hand assignment 'cs: "list[B]"' not compatible with ORM mapped expression of type "Mapped[list[C]]"  # noqa
     cs: List[B] = relationship(C, uselist=True)
 
-    # EXPECTED: Left hand assignment 'cs_2: "B"' not compatible with ORM mapped expression of type "Mapped[List[C]]"  # noqa
+    # EXPECTED: Left hand assignment 'cs_2: "B"' not compatible with ORM mapped expression of type "Mapped[list[C]]"  # noqa
     cs_2: B = relationship(C, uselist=True)
 
 
index 4057baeb379de9d7862bba0cf604579834517522..04db946abfbea9bf313240d556672820dc24005f 100644 (file)
@@ -28,5 +28,5 @@ class A(Base):
 # EXPECTED_MYPY: List item 1 has incompatible type "A"; expected "B"
 a1 = A(bs=[B(data="b"), A()])
 
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "List[B]", variable has type "Set[B]") # noqa
+# EXPECTED_MYPY: Incompatible types in assignment (expression has type "list[B]", variable has type "set[B]") # noqa
 x: Set[B] = a1.bs
index 1c7cd9f303d48f3c01d5649891af0e2dce214732..95d77fde59bc2e968b16a9d124387dfb5eae52ec 100644 (file)
@@ -27,7 +27,7 @@ class A(Base):
 
     bs: Set[B] = relationship(B, uselist=True, back_populates="a")
 
-    # EXPECTED: Left hand assignment 'another_bs: "Set[B]"' not compatible with ORM mapped expression of type "Mapped[B]" # noqa
+    # EXPECTED: Left hand assignment 'another_bs: "set[B]"' not compatible with ORM mapped expression of type "Mapped[B]" # noqa
     another_bs: Set[B] = relationship(B, viewonly=True)
 
 
index 192c46aff2f35b974d9dad432af41fb785013c76..c80e8cd26315a6a571f9325f8ec8824dbe4d394c 100644 (file)
@@ -1577,11 +1577,14 @@ class DeclarativeMultiBaseTest(
             attr_type.fail()
 
     def test_column_named_twice(self):
-        with expect_warnings(
-            "On class 'Foo', Column object 'x' named directly multiple "
-            "times, only one will be used: x, y. Consider using "
-            "orm.synonym instead"
-        ), expect_raises(exc.DuplicateColumnError):
+        with (
+            expect_warnings(
+                "On class 'Foo', Column object 'x' named directly multiple "
+                "times, only one will be used: x, y. Consider using "
+                "orm.synonym instead"
+            ),
+            expect_raises(exc.DuplicateColumnError),
+        ):
 
             class Foo(Base):
                 __tablename__ = "foo"
@@ -1592,11 +1595,14 @@ class DeclarativeMultiBaseTest(
 
     @testing.variation("style", ["old", "new"])
     def test_column_repeated_under_prop(self, style):
-        with expect_warnings(
-            "On class 'Foo', Column object 'x' named directly multiple "
-            "times, only one will be used: x, y, z. Consider using "
-            "orm.synonym instead"
-        ), expect_raises(exc.DuplicateColumnError):
+        with (
+            expect_warnings(
+                "On class 'Foo', Column object 'x' named directly multiple "
+                "times, only one will be used: x, y, z. Consider using "
+                "orm.synonym instead"
+            ),
+            expect_raises(exc.DuplicateColumnError),
+        ):
             if style.old:
 
                 class Foo(Base):
index 52c4dae51a572bc003aaf2c0992143dd7c0bae03..51a74d5afc5284cb456ad3c1b9ee70dee33ddc07 100644 (file)
@@ -226,9 +226,12 @@ class DCTransformsTest(AssertsCompiledSQL, fixtures.TestBase):
             foo: Mapped[str]
             bar: Mapped[str] = mapped_column()
 
-        with _dataclass_mixin_warning(
-            "_BaseMixin", "'create_user', 'update_user'"
-        ), _dataclass_mixin_warning("SubMixin", "'foo', 'bar'"):
+        with (
+            _dataclass_mixin_warning(
+                "_BaseMixin", "'create_user', 'update_user'"
+            ),
+            _dataclass_mixin_warning("SubMixin", "'foo', 'bar'"),
+        ):
 
             class User(SubMixin, Base):
                 __tablename__ = "sys_user"
index be42dc60904a7fcca0f2cc394e67042f90634c91..e2016f8b5d9b07becd363fba66e139cc33a49478 100644 (file)
@@ -2712,8 +2712,9 @@ class MultipleAdaptUsesEntityOverTableTest(
     def test_two_joins_adaption(self):
         a, c, d = self.tables.a, self.tables.c, self.tables.d
 
-        with _aliased_join_warning(r"C\(c\)"), _aliased_join_warning(
-            r"D\(d\)"
+        with (
+            _aliased_join_warning(r"C\(c\)"),
+            _aliased_join_warning(r"D\(d\)"),
         ):
             q = self._two_join_fixture()._compile_state()
 
@@ -2745,8 +2746,9 @@ class MultipleAdaptUsesEntityOverTableTest(
     def test_two_joins_sql(self):
         q = self._two_join_fixture()
 
-        with _aliased_join_warning(r"C\(c\)"), _aliased_join_warning(
-            r"D\(d\)"
+        with (
+            _aliased_join_warning(r"C\(c\)"),
+            _aliased_join_warning(r"D\(d\)"),
         ):
             self.assert_compile(
                 q,
index abd008cadf08859c3582c0321264f6154945c465..317ebdc468d4225d7f29ecd6d1bffe1cdb11d78c 100644 (file)
@@ -463,16 +463,22 @@ class BindIntegrationTest(_fixtures.FixtureTest):
 
         engine = {"e1": e1, "e2": e2, "e3": e3}[expected_engine_name]
 
-        with mock.patch(
-            "sqlalchemy.orm.context.ORMCompileState.orm_setup_cursor_result"
-        ), mock.patch(
-            "sqlalchemy.orm.context.ORMCompileState.orm_execute_statement"
-        ), mock.patch(
-            "sqlalchemy.orm.bulk_persistence."
-            "BulkORMInsert.orm_execute_statement"
-        ), mock.patch(
-            "sqlalchemy.orm.bulk_persistence."
-            "BulkUDCompileState.orm_setup_cursor_result"
+        with (
+            mock.patch(
+                "sqlalchemy.orm.context.ORMCompileState."
+                "orm_setup_cursor_result"
+            ),
+            mock.patch(
+                "sqlalchemy.orm.context.ORMCompileState.orm_execute_statement"
+            ),
+            mock.patch(
+                "sqlalchemy.orm.bulk_persistence."
+                "BulkORMInsert.orm_execute_statement"
+            ),
+            mock.patch(
+                "sqlalchemy.orm.bulk_persistence."
+                "BulkUDCompileState.orm_setup_cursor_result"
+            ),
         ):
             sess.execute(statement)
 
index 5e1672b526b62cabec4c7856a51968bd95e2de74..287f43646469748a6809bbf3e9474e16d45b3512 100644 (file)
@@ -2580,8 +2580,9 @@ class SessionEventsTest(RemoveORMEventsGlobally, _fixtures.FixtureTest):
         u2 = User(name="u1", id=1)
         sess.add(u2)
 
-        with expect_raises(sa.exc.IntegrityError), expect_warnings(
-            "New instance"
+        with (
+            expect_raises(sa.exc.IntegrityError),
+            expect_warnings("New instance"),
         ):
             sess.commit()
 
@@ -2636,8 +2637,9 @@ class SessionEventsTest(RemoveORMEventsGlobally, _fixtures.FixtureTest):
 
         u2 = User(name="u1", id=1)
         sess.add(u2)
-        with expect_raises(sa.exc.IntegrityError), expect_warnings(
-            "New instance"
+        with (
+            expect_raises(sa.exc.IntegrityError),
+            expect_warnings("New instance"),
         ):
             sess.commit()
 
index 67b6042361d765e1e70a8b2e7dc9dd827a6b8f10..2f7a2f1980ab86ba2c42a273ac034feb31064682 100644 (file)
@@ -671,13 +671,16 @@ class SessionTransactionTest(fixtures.RemovesEvents, FixtureTest):
         def fail(*arg, **kw):
             raise BaseException("some base exception")
 
-        with mock.patch.object(
-            testing.db.dialect, "do_rollback", side_effect=fail
-        ) as fail_mock, mock.patch.object(
-            testing.db.dialect,
-            "do_commit",
-            side_effect=testing.db.dialect.do_commit,
-        ) as succeed_mock:
+        with (
+            mock.patch.object(
+                testing.db.dialect, "do_rollback", side_effect=fail
+            ) as fail_mock,
+            mock.patch.object(
+                testing.db.dialect,
+                "do_commit",
+                side_effect=testing.db.dialect.do_commit,
+            ) as succeed_mock,
+        ):
             # sess.begin() -> commit().  why would do_rollback() be called?
             # because of connection pool finalize_fairy *after* the commit.
             # this will cause the conn.close() in session.commit() to fail,
index 1cf3140a56c779bdc2342ef87cab7a79158be428..46821fe0558f2fc2f4f97b9eca49982baa685e28 100644 (file)
@@ -429,9 +429,12 @@ class VersioningTest(fixtures.MappedTest):
             else:
                 return self.context.rowcount
 
-        with patch.object(
-            config.db.dialect, "supports_sane_multi_rowcount", False
-        ), patch("sqlalchemy.engine.cursor.CursorResult.rowcount", rowcount):
+        with (
+            patch.object(
+                config.db.dialect, "supports_sane_multi_rowcount", False
+            ),
+            patch("sqlalchemy.engine.cursor.CursorResult.rowcount", rowcount),
+        ):
             Foo = self.classes.Foo
             s1 = self._fixture()
             f1s1 = Foo(value="f1 value")
@@ -444,10 +447,11 @@ class VersioningTest(fixtures.MappedTest):
             eq_(f1s1.version_id, 2)
 
     def test_update_delete_no_plain_rowcount(self):
-        with patch.object(
-            config.db.dialect, "supports_sane_rowcount", False
-        ), patch.object(
-            config.db.dialect, "supports_sane_multi_rowcount", False
+        with (
+            patch.object(config.db.dialect, "supports_sane_rowcount", False),
+            patch.object(
+                config.db.dialect, "supports_sane_multi_rowcount", False
+            ),
         ):
             Foo = self.classes.Foo
             s1 = self._fixture()
@@ -714,10 +718,11 @@ class VersionOnPostUpdateTest(fixtures.MappedTest):
 
         n1.related.append(n2)
 
-        with patch.object(
-            config.db.dialect, "supports_sane_rowcount", False
-        ), patch.object(
-            config.db.dialect, "supports_sane_multi_rowcount", False
+        with (
+            patch.object(config.db.dialect, "supports_sane_rowcount", False),
+            patch.object(
+                config.db.dialect, "supports_sane_multi_rowcount", False
+            ),
         ):
             s2 = Session(bind=s.connection(bind_arguments=dict(mapper=Node)))
             s2.query(Node).filter(Node.id == n2.id).update({"version_id": 3})
index 26de957e1ef9e7b0043521c6911e5d8d191d2dcb..f87c6520d90ff52c7ffb6fb84f8a6c956311180c 100644 (file)
@@ -3582,9 +3582,10 @@ class AlternateCursorResultTest(fixtures.TablesTest):
                 r = conn.execute(select(self.table).limit(1))
 
                 r.fetchone()
-                with mock.patch.object(
-                    r, "_soft_close", raise_
-                ), testing.expect_raises_message(IOError, "random non-DBAPI"):
+                with (
+                    mock.patch.object(r, "_soft_close", raise_),
+                    testing.expect_raises_message(IOError, "random non-DBAPI"),
+                ):
                     r.first()
                 r.close()
 
index a25a0b8cce538ec3e9a10e137934451a33cbe8af..8d74ba03e8ee357c76d3864c4583dad95cb51b02 100644 (file)
@@ -68,7 +68,7 @@ print(stmt)
 
 t1 = Test()
 
-# EXPECTED_RE_TYPE: .*[dD]ict\[.*str, Any\]
+# EXPECTED_RE_TYPE: .*dict\[.*str, Any\]
 reveal_type(t1.data)
 
 # EXPECTED_TYPE: UUID
index 39b41dfbb7744bb8ed76c3d82a9d93b9f883cb4b..1cc5b1c014a52cf6e69d224f67b053f1bed730f3 100644 (file)
@@ -55,13 +55,13 @@ with Session(e) as sess:
 
     rows1 = q.all()
 
-    # EXPECTED_RE_TYPE: builtins.[Ll]ist\[.*User\*?\]
+    # EXPECTED_RE_TYPE: builtins.list\[.*User\*?\]
     reveal_type(rows1)
 
     q2 = sess.query(User.id).filter_by(id=7)
     rows2 = q2.all()
 
-    # EXPECTED_TYPE: List[.*Row[.*int].*]
+    # EXPECTED_TYPE: list[.*Row[.*int].*]
     reveal_type(rows2)
 
     # test #8280
index 252be918d8cd6225e8d37a9510276d4acf7a2873..424a03c8aec16b1ff4a4727074196c6eb21ed0a0 100644 (file)
@@ -1,7 +1,5 @@
 from __future__ import annotations
 
-from typing import Tuple
-
 from sqlalchemy import Column
 from sqlalchemy import column
 from sqlalchemy import create_engine
@@ -133,14 +131,14 @@ def t_legacy_query_single_entity() -> None:
     # EXPECTED_TYPE: User
     reveal_type(q1.one())
 
-    # EXPECTED_TYPE: List[User]
+    # EXPECTED_TYPE: list[User]
     reveal_type(q1.all())
 
     # mypy switches to builtins.list for some reason here
-    # EXPECTED_RE_TYPE: .*\.[Ll]ist\[.*Row\*?\[.*User\].*\]
+    # EXPECTED_RE_TYPE: .*\.list\[.*Row\*?\[.*User\].*\]
     reveal_type(q1.only_return_tuples(True).all())
 
-    # EXPECTED_TYPE: List[Tuple[User]]
+    # EXPECTED_TYPE: list[tuple[User]]
     reveal_type(q1.tuples().all())
 
 
@@ -172,7 +170,7 @@ def t_legacy_query_cols_tupleq_1() -> None:
 
     q2 = q1.tuples()
 
-    # EXPECTED_TYPE: Tuple[int, str]
+    # EXPECTED_TYPE: tuple[int, str]
     reveal_type(q2.one())
 
     r1 = q2.one()
@@ -383,7 +381,7 @@ def t_select_w_core_selectables() -> None:
 
     # this one unfortunately is not working in mypy.
     # pylance gets the correct type
-    #   EXPECTED_TYPE: Select[Tuple[int, Any]]
+    #   EXPECTED_TYPE: Select[tuple[int, Any]]
     # when experimenting with having a separate TypedSelect class for typing,
     # mypy would downgrade to Any rather than picking the basemost type.
     # with typing integrated into Select etc. we can at least get a Select
@@ -392,9 +390,9 @@ def t_select_w_core_selectables() -> None:
     reveal_type(s2)
 
     # so a fully explicit type may be given
-    s2_typed: Select[Tuple[int, str]] = select(User.id, s1.c.name)
+    s2_typed: Select[tuple[int, str]] = select(User.id, s1.c.name)
 
-    # EXPECTED_TYPE: Select[Tuple[int, str]]
+    # EXPECTED_TYPE: Select[tuple[int, str]]
     reveal_type(s2_typed)
 
     # plain FromClause etc we at least get Select
index 3c8b7f91348e60a007202ddd798de9717b3b184f..498d2d276a430b4175c4673e14f0740da02ac742 100644 (file)
@@ -359,11 +359,11 @@ def t_connection_execute_multi_row_t() -> None:
 def t_connection_execute_multi() -> None:
     result = connection.execute(multi_stmt).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -378,11 +378,11 @@ def t_connection_execute_multi() -> None:
 def t_connection_execute_single() -> None:
     result = connection.execute(single_stmt).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
@@ -394,7 +394,7 @@ def t_connection_execute_single() -> None:
 def t_connection_execute_single_row_scalar() -> None:
     result = connection.execute(single_stmt).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
 
     x = result.scalar()
@@ -424,11 +424,11 @@ def t_connection_scalars() -> None:
 def t_session_execute_multi() -> None:
     result = session.execute(multi_stmt).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -443,11 +443,11 @@ def t_session_execute_multi() -> None:
 def t_session_execute_single() -> None:
     result = session.execute(single_stmt).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
@@ -477,11 +477,11 @@ def t_session_scalars() -> None:
 async def t_async_connection_execute_multi() -> None:
     result = (await async_connection.execute(multi_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -496,12 +496,12 @@ async def t_async_connection_execute_multi() -> None:
 async def t_async_connection_execute_single() -> None:
     result = (await async_connection.execute(single_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
 
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
@@ -531,11 +531,11 @@ async def t_async_connection_scalars() -> None:
 async def t_async_session_execute_multi() -> None:
     result = (await async_session.execute(multi_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -550,11 +550,11 @@ async def t_async_session_execute_multi() -> None:
 async def t_async_session_execute_single() -> None:
     result = (await async_session.execute(single_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
@@ -584,11 +584,11 @@ async def t_async_session_scalars() -> None:
 async def t_async_connection_stream_multi() -> None:
     result = (await async_connection.stream(multi_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = await result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -603,11 +603,11 @@ async def t_async_connection_stream_multi() -> None:
 async def t_async_connection_stream_single() -> None:
     result = (await async_connection.stream(single_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
     row = await result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
@@ -630,11 +630,11 @@ async def t_async_connection_stream_scalars() -> None:
 async def t_async_session_stream_multi() -> None:
     result = (await async_session.stream(multi_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*TupleResult\[tuple\[builtins.int\*?, builtins.str\*?\]\]
     reveal_type(result)
     row = await result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.int\*?, builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.int\*?, builtins.str\*?\]
     reveal_type(row)
 
     x, y = row
@@ -649,11 +649,11 @@ async def t_async_session_stream_multi() -> None:
 async def t_async_session_stream_single() -> None:
     result = (await async_session.stream(single_stmt)).t
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[Tuple\[builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*AsyncTupleResult\[tuple\[builtins.str\*?\]\]
     reveal_type(result)
     row = await result.one()
 
-    # EXPECTED_RE_TYPE: Tuple\[builtins.str\*?\]
+    # EXPECTED_RE_TYPE: tuple\[builtins.str\*?\]
     reveal_type(row)
 
     (x,) = row
diff --git a/tox.ini b/tox.ini
index 0b4808e6b05b94204998dffdc5fc3cd1b2a4870e..4ff125d62cd61a25d0ade9924ab466decf8a2697 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -28,9 +28,9 @@ usedevelop=
      cov: True
 
 extras=
-     py{3,38,39,310,311,312,313}: {[greenletextras]extras}
+     py{3,39,310,311,312,313}: {[greenletextras]extras}
 
-     py{38,39,310}-sqlite_file: sqlcipher
+     py{39,310}-sqlite_file: sqlcipher
      postgresql: postgresql
      postgresql: postgresql_pg8000
      postgresql: postgresql_psycopg
@@ -125,7 +125,7 @@ setenv=
 
     sqlite-nogreenlet: EXTRA_SQLITE_DRIVERS={env:EXTRA_SQLITE_DRIVERS:--dbdriver sqlite --dbdriver pysqlite_numeric}
 
-    py{37,38,39}-sqlite_file: EXTRA_SQLITE_DRIVERS={env:EXTRA_SQLITE_DRIVERS:--dbdriver sqlite --dbdriver aiosqlite --dbdriver pysqlcipher}
+    py{39}-sqlite_file: EXTRA_SQLITE_DRIVERS={env:EXTRA_SQLITE_DRIVERS:--dbdriver sqlite --dbdriver aiosqlite --dbdriver pysqlcipher}
 
     # omit pysqlcipher for Python 3.10
     py{3,310,311,312}-sqlite_file: EXTRA_SQLITE_DRIVERS={env:EXTRA_SQLITE_DRIVERS:--dbdriver sqlite --dbdriver aiosqlite}