From c8fad698df2416fc3ee7b1abb67eb20ae5b2ba82 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 2 Feb 2026 14:25:53 +0100 Subject: [PATCH] Remove Python3.9 support from CI and tests Part of #13782 --- .github/workflows/daily.yml | 2 +- .github/workflows/meta_tests.yml | 2 +- .github/workflows/stubtest_stdlib.yml | 2 +- .github/workflows/tests.yml | 4 +-- tests/README.md | 44 ++++++++++++++++----------- tests/REGRESSION.md | 12 ++++---- tests/mypy_test.py | 2 +- tests/regr_test.py | 6 ++-- tests/runtests.py | 2 +- tests/typecheck_typeshed.py | 2 +- 10 files changed, 44 insertions(+), 34 deletions(-) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 58d85ed3fefb..516e6a2633b9 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -35,7 +35,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false steps: diff --git a/.github/workflows/meta_tests.yml b/.github/workflows/meta_tests.yml index 6acd88a39d42..af2711512728 100644 --- a/.github/workflows/meta_tests.yml +++ b/.github/workflows/meta_tests.yml @@ -65,7 +65,7 @@ jobs: with: version: PATH python-platform: ${{ matrix.python-platform }} - python-version: "3.9" # Oldest version supported for running scripts and tests + python-version: "3.10" # Oldest version supported for running scripts and tests project: ./pyrightconfig.scripts_and_tests.json stubsabot-dry-run: name: Stubsabot dry run diff --git a/.github/workflows/stubtest_stdlib.yml b/.github/workflows/stubtest_stdlib.yml index fd7c6cdebcb1..db679c9971cd 100644 --- a/.github/workflows/stubtest_stdlib.yml +++ b/.github/workflows/stubtest_stdlib.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c93c9a84ee2..cdd9b0707dde 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,7 +42,7 @@ jobs: strategy: matrix: platform: ["linux", "win32", "darwin"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false steps: - uses: actions/checkout@v6 @@ -84,7 +84,7 @@ jobs: strategy: matrix: python-platform: ["Linux", "Windows", "Darwin"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false steps: - uses: actions/checkout@v6 diff --git a/tests/README.md b/tests/README.md index 1d38db0b3ec4..c5b970751afe 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,26 +1,28 @@ This directory contains several tests: + - `tests/mypy_test.py` -tests the stubs with [mypy](https://github.com/python/mypy/) + tests the stubs with [mypy](https://github.com/python/mypy/) - `tests/pyright_test.py` tests the stubs with -[pyright](https://github.com/microsoft/pyright). + [pyright](https://github.com/microsoft/pyright). - `tests/regr_test.py` runs mypy against the test cases for typeshed's -stubs, guarding against accidental regressions. + stubs, guarding against accidental regressions. - `tests/check_typeshed_structure.py` checks that typeshed's directory -structure and metadata files are correct. + structure and metadata files are correct. - `tests/stubtest_stdlib.py` checks standard library stubs against the -objects at runtime. + objects at runtime. - `tests/stubtest_third_party.py` checks third-party stubs against the -objects at runtime. + objects at runtime. - `tests/typecheck_typeshed.py` runs mypy against typeshed's own code -in the `tests` and `scripts` directories. + in the `tests` and `scripts` directories. To run the tests, follow the [setup instructions](../CONTRIBUTING.md#preparing-the-environment) -in the `CONTRIBUTING.md` document. In particular, you have to run with Python 3.9+. +in the `CONTRIBUTING.md` document. In order for `pyright_test` to work correctly, some third-party stubs may require extra dependencies external to typeshed to be installed in your virtual environment prior to running the test. You can list or install all of a stubs package's external dependencies using the following script: + ```bash (.venv)$ python tests/get_external_stub_requirements.py # List external dependencies for (.venv)$ python tests/get_external_stub_requirements.py # List external dependencies for and @@ -31,6 +33,7 @@ You can list or install all of a stubs package's external dependencies using the ## Run all tests for a specific stub Run using: + ```bash (.venv)$ python3 tests/runtests.py / ``` @@ -48,9 +51,10 @@ whether or not the test passes will depend on the exact version of Python you're using, as well as various other details regarding your local environment. For more information, see the docs on [`stubtest_stdlib.py`](#stubtest_stdlibpy) below. -## mypy\_test.py +## mypy_test.py Run using: + ```bash (.venv)$ python3 tests/mypy_test.py ``` @@ -65,11 +69,12 @@ imported but doesn't check whether stubs match their implementation Run `python tests/mypy_test.py --help` for information on the various configuration options for this script. -## pyright\_test.py +## pyright_test.py This test requires [Node.js](https://nodejs.org) to be installed. Although typeshed runs pyright in CI, it does not currently use this script. However, this script uses the same pyright version and configuration as the CI. + ```bash (.venv)$ python3 tests/pyright_test.py # Check all files (.venv)$ python3 tests/pyright_test.py stdlib/sys.pyi # Check one file @@ -81,7 +86,7 @@ checks that would typically fail on incomplete stubs (such as `Unknown` checks). In typeshed's CI, pyright is run with these configuration settings on a subset of the stubs in typeshed (including the standard library). -## regr\_test.py +## regr_test.py This test runs mypy against the test cases for typeshed's stdlib and third-party stubs. See [the REGRESSION.md document](./REGRESSION.md) @@ -90,18 +95,20 @@ for more information about what these test cases are for and how they work. Run `python tests/regr_test.py --help` for information on the various configuration options. -## check\_typeshed\_structure.py +## check_typeshed_structure.py This checks that typeshed's directory structure and metadata files are correct. Run using: + ```bash $ python3 tests/check_typeshed_structure.py ``` -## stubtest\_stdlib.py +## stubtest_stdlib.py Run using + ```bash (.venv)$ python3 tests/stubtest_stdlib.py ``` @@ -116,7 +123,7 @@ test it automatically (or [running the test via Github Actions](https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow#running-a-workflow) on your typeshed fork). -As a convenience, stubtest\_stdlib.py will look for local-only allowlist files +As a convenience, stubtest_stdlib.py will look for local-only allowlist files and use those if they are present. Only version-specific local allowlists are supported. An example local allowlist file is `stdlib/@tests/stubtest_allowlists/py312.txt.local`. Use caution when taking advantage of this feature; @@ -130,13 +137,14 @@ can add to the allowlists for each affected Python version in `stdlib/@tests/stubtest_allowlists`. Please file issues for stubtest false positives at [mypy](https://github.com/python/mypy/issues). -## stubtest\_third\_party.py +## stubtest_third_party.py :warning: This script downloads and executes arbitrary code from PyPI. Only run this script locally if you know you can trust the packages you're running stubtest on. Run using + ```bash (.venv)$ python3 tests/stubtest_third_party.py ``` @@ -151,6 +159,7 @@ check on the command line: If you have the runtime package installed in your local virtual environment, you can also run stubtest directly, with + ```bash (.venv)$ MYPYPATH= python3 -m mypy.stubtest \ --custom-typeshed-dir \ @@ -177,7 +186,7 @@ considered "incomplete". You can help make typeshed's stubs more complete by removing `ignore_missing_stub = true` from the `tests/METADATA.toml` file for a third-party stubs distribution, running stubtest, and then adding things that -stubtest reports to be missing to the stub. However, note that not *everything* +stubtest reports to be missing to the stub. However, note that not _everything_ that stubtest reports to be missing should necessarily be added to the stub. For some implementation details, it is often better to add allowlist entries for missing objects rather than trying to match the runtime in every detail. @@ -199,9 +208,10 @@ For Django stubs specifically, you'll need to create a `django_settings.py` file that contains the Django settings required by the plugin. This file will be referenced by the plugin configuration to properly validate Django-specific types during stubtest execution. -## typecheck\_typeshed.py +## typecheck_typeshed.py Run using + ```bash (.venv)$ python3 tests/typecheck_typeshed.py ``` diff --git a/tests/REGRESSION.md b/tests/REGRESSION.md index 32a8eb2bd09c..cae64cc8b646 100644 --- a/tests/REGRESSION.md +++ b/tests/REGRESSION.md @@ -25,7 +25,7 @@ the annotations correctly. Examples of tests like these are Other test cases, such as the samples for `ExitStack` in `stdlib/@tests/test_cases/check_contextlib.py` and the samples for `LogRecord` in `stdlib/@tests/test_cases/check_logging.py`, do not relate to -stubs where the annotations are particularly complex, but they *do* relate to +stubs where the annotations are particularly complex, but they _do_ relate to stubs where decisions have been taken that might be slightly unusual. These test cases serve a different purpose: to check that type checkers do not emit false-positive errors for idiomatic usage of these classes. @@ -65,7 +65,7 @@ mypy's [`--warn-unused-ignores`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-warn-unused-ignores) setting and pyright's [`reportUnnecessaryTypeIgnoreComment`](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#type-check-diagnostics-settings) -setting) to test instances where a type checker *should* emit some kind of +setting) to test instances where a type checker _should_ emit some kind of error, if the stubs are correct. Both settings are enabled by default for all `@tests/test_cases/` subdirectories in typeshed. @@ -121,13 +121,13 @@ Some tests will only pass on mypy with a specific Python version passed on the command line to the `tests/regr_test.py` script. To mark a test-case file as being skippable on lower versions of Python, append `-py3*` to the filename. -For example, if `foo` is a stdlib feature that's new in Python 3.11, +For example, if `foo` is a stdlib feature that's new in Python 3.14, test cases for `foo` should be put in a file named -`stdlib/@tests/test_cases/check_foo-py311.py`. +`stdlib/@tests/test_cases/check_foo-py314.py`. This means that mypy will only run the test case -if `--python-version 3.11`, `--python-version 3.12`, etc. +if `--python-version 3.14`, `--python-version 3.15`, etc. is passed on the command line to `tests/regr_test.py`, -but it _won't_ run the test case if e.g. `--python-version 3.9` +but it _won't_ run the test case if e.g. `--python-version 3.13` is passed on the command line. However, `if sys.version_info >= (3, target):` is still required for `pyright` diff --git a/tests/mypy_test.py b/tests/mypy_test.py index 5e263a9cbb15..dfc7adba8062 100755 --- a/tests/mypy_test.py +++ b/tests/mypy_test.py @@ -44,7 +44,7 @@ print_error("Cannot import mypy. Did you install it?") sys.exit(1) -SUPPORTED_VERSIONS = ["3.14", "3.13", "3.12", "3.11", "3.10", "3.9"] +SUPPORTED_VERSIONS = ["3.14", "3.13", "3.12", "3.11", "3.10"] SUPPORTED_PLATFORMS = ("linux", "win32", "darwin") DIRECTORIES_TO_TEST = [STDLIB_PATH, STUBS_PATH] diff --git a/tests/regr_test.py b/tests/regr_test.py index 0693974a901f..99cb3a57e581 100755 --- a/tests/regr_test.py +++ b/tests/regr_test.py @@ -41,7 +41,7 @@ TYPESHED = "typeshed" SUPPORTED_PLATFORMS = ["linux", "darwin", "win32"] -SUPPORTED_VERSIONS = ["3.14", "3.13", "3.12", "3.11", "3.10", "3.9"] +SUPPORTED_VERSIONS = ["3.14", "3.13", "3.12", "3.11", "3.10"] def distribution_with_test_cases(distribution_name: str) -> DistributionTests: @@ -213,8 +213,8 @@ def run_testcases( flags.extend(["--custom-typeshed-dir", str(custom_typeshed)]) - # If the test-case filename ends with -py39, - # only run the test if --python-version was set to 3.9 or higher (for example) + # If the test-case filename ends with -py314, + # only run the test if --python-version was set to 3.14 or higher (for example) for path in new_test_case_dir.rglob("*.py"): if match := re.fullmatch(r".*-py3(\d{1,2})", path.stem): minor_version_required = int(match[1]) diff --git a/tests/runtests.py b/tests/runtests.py index 296cb8ccd068..832ade5216ba 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -50,7 +50,7 @@ def main() -> None: parser.add_argument( "--python-version", default=None, - choices=("3.9", "3.10", "3.11", "3.12", "3.13", "3.14"), + choices=("3.10", "3.11", "3.12", "3.13", "3.14"), # We're using the oldest fully supported version because it's the most likely to produce errors # due to unsupported syntax, feature, or bug in a tool. help="Target Python version for the test (defaults to oldest supported Python version).", diff --git a/tests/typecheck_typeshed.py b/tests/typecheck_typeshed.py index 90d7afcaf706..63449e4d4ee3 100755 --- a/tests/typecheck_typeshed.py +++ b/tests/typecheck_typeshed.py @@ -14,7 +14,7 @@ ReturnCode: TypeAlias = int SUPPORTED_PLATFORMS = ("linux", "darwin", "win32") -SUPPORTED_VERSIONS = ("3.14", "3.13", "3.12", "3.11", "3.10", "3.9") +SUPPORTED_VERSIONS = ("3.14", "3.13", "3.12", "3.11", "3.10") LOWEST_SUPPORTED_VERSION = min(SUPPORTED_VERSIONS, key=lambda x: int(x.split(".")[1])) DIRECTORIES_TO_TEST = ("scripts", "tests") EMPTY: list[str] = []