From cb2ce95885a8cbb9d24ca73b1953e5c4c5c258bd Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Wed, 4 Feb 2026 22:07:33 -0800 Subject: [PATCH] chore: drop Python 3.9 support (EOL) Remove Python 3.9 from CI matrix, setup.cfg classifiers, tox envlist, mergify checks, and bump python_requires to >=3.10. Inline code that was gated behind sys.version_info checks: - Use slots=True directly in dataclass decorator (3.10+) - Use futures.Future[t.Any] directly (3.9+) - Remove redundant version checks and imports in tests --- .github/workflows/ci.yaml | 2 -- .mergify.yml | 1 - pyproject.toml | 2 +- .../notes/drop-python-3.9-ecfa2d7db9773e96.yaml | 5 +++++ setup.cfg | 3 +-- tenacity/__init__.py | 15 ++------------- tests/test_tenacity.py | 11 +---------- tox.ini | 8 ++++---- 8 files changed, 14 insertions(+), 33 deletions(-) create mode 100644 releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10250c5b..70d2f465 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,8 +18,6 @@ jobs: strategy: matrix: include: - - python: "3.9" - tox: py39 - python: "3.10" tox: py310 - python: "3.11" diff --git a/.mergify.yml b/.mergify.yml index 48d990dd..a7d9ec5d 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -7,7 +7,6 @@ queue_rules: - author = jd - "#approved-reviews-by >= 1" - author = dependabot[bot] - - "check-success=test (3.9, py39)" - "check-success=test (3.10, py310)" - "check-success=test (3.11, py311)" - "check-success=test (3.12, py312)" diff --git a/pyproject.toml b/pyproject.toml index 2ff52b9b..57c4c762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ build-backend="setuptools.build_meta" [tool.ruff] line-length = 88 indent-width = 4 -target-version = "py39" +target-version = "py310" [tool.mypy] strict = true diff --git a/releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml b/releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml new file mode 100644 index 00000000..ac2a8cfd --- /dev/null +++ b/releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Python 3.9 has reached end-of-life and is no longer supported. + The minimum supported version is now Python 3.10. diff --git a/setup.cfg b/setup.cfg index b4aafb33..42b794e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,6 @@ classifier = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 @@ -23,7 +22,7 @@ classifier = [options] install_requires = -python_requires = >=3.9 +python_requires = >=3.10 packages = find: [options.packages.find] diff --git a/tenacity/__init__.py b/tenacity/__init__.py index b0b47946..7a6ee5d5 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -101,12 +101,7 @@ WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any]) -dataclass_kwargs = {} -if sys.version_info >= (3, 10): - dataclass_kwargs.update({"slots": True}) - - -@dataclasses.dataclass(**dataclass_kwargs) +@dataclasses.dataclass(slots=True) class IterState: actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field( default_factory=list @@ -486,13 +481,7 @@ def __call__( return do # type: ignore[no-any-return] -if sys.version_info >= (3, 9): - FutureGenericT = futures.Future[t.Any] -else: - FutureGenericT = futures.Future - - -class Future(FutureGenericT): +class Future(futures.Future[t.Any]): """Encapsulates a (future or past) attempted call to a target function.""" def __init__(self, attempt_number: int) -> None: diff --git a/tests/test_tenacity.py b/tests/test_tenacity.py index 5e5dddfc..ff1f4b38 100644 --- a/tests/test_tenacity.py +++ b/tests/test_tenacity.py @@ -17,7 +17,6 @@ import datetime import logging import re -import sys import time import typing import unittest @@ -28,6 +27,7 @@ from unittest import mock import pytest +from typeguard import check_type import tenacity from tenacity import RetryCallState, RetryError, Retrying, retry @@ -1729,17 +1729,8 @@ def test_retry_error_is_pickleable(self): class TestRetryTyping(unittest.TestCase): - @pytest.mark.skipif( - sys.version_info < (3, 0), reason="typeguard not supported for python 2" - ) def test_retry_type_annotations(self): """The decorator should maintain types of decorated functions.""" - # Just in case this is run with unit-test, return early for py2 - if sys.version_info < (3, 0): - return - - # Function-level import because we can't install this for python 2. - from typeguard import check_type def num_to_str(number): # type: (int) -> str diff --git a/tox.ini b/tox.ini index e3641348..d3dfdb10 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # we only test trio on latest python version -envlist = py3{9,10,11,12,13,14,14-trio}, pep8, pypy3 +envlist = py3{10,11,12,13,14,14-trio}, pep8, pypy3 skip_missing_interpreters = True [testenv] @@ -11,9 +11,9 @@ deps = .[doc] trio: trio commands = - py3{8,9,10,11,12,13,14},pypy3: pytest {posargs} - py3{8,9,10,11,12,13,14},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build - py3{8,9,10,11,12,13,14},pypy3: sphinx-build -a -E -W -b html doc/source doc/build + py3{10,11,12,13,14},pypy3: pytest {posargs} + py3{10,11,12,13,14},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build + py3{10,11,12,13,14},pypy3: sphinx-build -a -E -W -b html doc/source doc/build [testenv:pep8] basepython = python3