From e0838c25674d1c46a514ebcb8f14fd058a595b2a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 29 Oct 2025 13:15:00 -0400 Subject: [PATCH] Drop Python 3.9, bump tests/builds to Python 3.10 (#19099) Python 3.9 EOL is on 2025-10-31 --- .ci/scripts/calculate_jobs.py | 10 +++---- .github/workflows/release-artifacts.yml | 2 +- .github/workflows/tests.yml | 6 ++--- build_rust.py | 6 ++--- changelog.d/19099.removal | 1 + docker/editable.Dockerfile | 2 +- docs/development/dependencies.md | 8 +++--- docs/setup/installation.md | 11 +++++--- docs/upgrade.md | 12 +++++++++ mypy.ini | 2 +- poetry.lock | 35 +++---------------------- pyproject.toml | 16 ++++++----- rust/Cargo.toml | 2 +- scripts-dev/build_debian_packages.py | 5 ++-- synapse/__init__.py | 4 +-- tox.ini | 2 +- 16 files changed, 59 insertions(+), 65 deletions(-) create mode 100644 changelog.d/19099.removal diff --git a/.ci/scripts/calculate_jobs.py b/.ci/scripts/calculate_jobs.py index f3b1bb150..2971b3c5c 100755 --- a/.ci/scripts/calculate_jobs.py +++ b/.ci/scripts/calculate_jobs.py @@ -36,11 +36,11 @@ IS_PR = os.environ["GITHUB_REF"].startswith("refs/pull/") # First calculate the various trial jobs. # # For PRs, we only run each type of test with the oldest Python version supported (which -# is Python 3.9 right now) +# is Python 3.10 right now) trial_sqlite_tests = [ { - "python-version": "3.9", + "python-version": "3.10", "database": "sqlite", "extras": "all", } @@ -53,12 +53,12 @@ if not IS_PR: "database": "sqlite", "extras": "all", } - for version in ("3.10", "3.11", "3.12", "3.13") + for version in ("3.11", "3.12", "3.13") ) trial_postgres_tests = [ { - "python-version": "3.9", + "python-version": "3.10", "database": "postgres", "postgres-version": "13", "extras": "all", @@ -77,7 +77,7 @@ if not IS_PR: trial_no_extra_tests = [ { - "python-version": "3.9", + "python-version": "3.10", "database": "sqlite", "extras": "", } diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index a19bde0a6..f3e0da5aa 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -145,7 +145,7 @@ jobs: - name: Only build a single wheel on PR if: startsWith(github.ref, 'refs/pull/') - run: echo "CIBW_BUILD="cp39-manylinux_*"" >> $GITHUB_ENV + run: echo "CIBW_BUILD="cp310-manylinux_*"" >> $GITHUB_ENV - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f75435bed..93c0e9415 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -470,7 +470,7 @@ jobs: - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.9' + python-version: '3.10' - name: Prepare old deps if: steps.cache-poetry-old-deps.outputs.cache-hit != 'true' @@ -514,7 +514,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["pypy-3.9"] + python-version: ["pypy-3.10"] extras: ["all"] steps: @@ -638,7 +638,7 @@ jobs: strategy: matrix: include: - - python-version: "3.9" + - python-version: "3.10" postgres-version: "13" - python-version: "3.13" diff --git a/build_rust.py b/build_rust.py index af7bd2fdc..a9e9265da 100644 --- a/build_rust.py +++ b/build_rust.py @@ -27,12 +27,12 @@ def build(setup_kwargs: dict[str, Any]) -> None: setup_kwargs["zip_safe"] = False # We look up the minimum supported Python version with - # `python_requires` (e.g. ">=3.9.0,<4.0.0") and finding the first Python + # `python_requires` (e.g. ">=3.10.0,<4.0.0") and finding the first Python # version that matches. We then convert that into the `py_limited_api` form, - # e.g. cp39 for Python 3.9. + # e.g. cp310 for Python 3.10. py_limited_api: str python_bounds = SpecifierSet(setup_kwargs["python_requires"]) - for minor_version in itertools.count(start=8): + for minor_version in itertools.count(start=10): if f"3.{minor_version}.0" in python_bounds: py_limited_api = f"cp3{minor_version}" break diff --git a/changelog.d/19099.removal b/changelog.d/19099.removal new file mode 100644 index 000000000..8279a1c7f --- /dev/null +++ b/changelog.d/19099.removal @@ -0,0 +1 @@ +Drop support for Python 3.9. diff --git a/docker/editable.Dockerfile b/docker/editable.Dockerfile index 7e5da4e4f..b2aff9cb5 100644 --- a/docker/editable.Dockerfile +++ b/docker/editable.Dockerfile @@ -3,7 +3,7 @@ # # Used by `complement.sh`. Not suitable for production use. -ARG PYTHON_VERSION=3.9 +ARG PYTHON_VERSION=3.10 ### ### Stage 0: generate requirements.txt diff --git a/docs/development/dependencies.md b/docs/development/dependencies.md index e381b3d15..1b3348703 100644 --- a/docs/development/dependencies.md +++ b/docs/development/dependencies.md @@ -79,17 +79,17 @@ phonenumbers = [ We can see this pinned version inside the docker image for that release: ``` -$ docker pull vectorim/synapse:v1.97.0 +$ docker pull matrixdotorg/synapse:latest ... -$ docker run --entrypoint pip vectorim/synapse:v1.97.0 show phonenumbers +$ docker run --entrypoint pip matrixdotorg/synapse:latest show phonenumbers Name: phonenumbers -Version: 8.12.44 +Version: 9.0.15 Summary: Python version of Google's common library for parsing, formatting, storing and validating international phone numbers. Home-page: https://github.com/daviddrysdale/python-phonenumbers Author: David Drysdale Author-email: dmd@lurklurk.org License: Apache License 2.0 -Location: /usr/local/lib/python3.9/site-packages +Location: /usr/local/lib/python3.12/site-packages Requires: Required-by: matrix-synapse ``` diff --git a/docs/setup/installation.md b/docs/setup/installation.md index 68f224d33..786672c68 100644 --- a/docs/setup/installation.md +++ b/docs/setup/installation.md @@ -204,7 +204,7 @@ When following this route please make sure that the [Platform-specific prerequis System requirements: - POSIX-compliant system (tested on Linux & OS X) -- Python 3.9 or later, up to Python 3.13. +- Python 3.10 or later, up to Python 3.13. - At least 1GB of free RAM if you want to join large public rooms like #matrix:matrix.org If building on an uncommon architecture for which pre-built wheels are @@ -307,11 +307,16 @@ sudo dnf group install "Development Tools" ##### Red Hat Enterprise Linux / Rocky Linux / Oracle Linux -*Note: The term "RHEL" below refers to Red Hat Enterprise Linux, Oracle Linux and Rocky Linux. The distributions are 1:1 binary compatible.* +*Note: The term "RHEL" below refers to Red Hat Enterprise Linux, Oracle Linux and Rocky Linux. +The distributions are 1:1 binary compatible.* It's recommended to use the latest Python versions. -RHEL 8 in particular ships with Python 3.6 by default which is EOL and therefore no longer supported by Synapse. RHEL 9 ships with Python 3.9 which is still supported by the Python core team as of this writing. However, newer Python versions provide significant performance improvements and they're available in official distributions' repositories. Therefore it's recommended to use them. +RHEL 8 & 9 in particular ship with Python 3.6 & 3.9 respectively by default +which are EOL and therefore no longer supported by Synapse. +However, newer Python versions provide significant performance improvements +and they're available in official distributions' repositories. +Therefore it's recommended to use them. Python 3.11 and 3.12 are available for both RHEL 8 and 9. diff --git a/docs/upgrade.md b/docs/upgrade.md index 63d567505..faf6cbf8d 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -117,6 +117,18 @@ each upgrade are complete before moving on to the next upgrade, to avoid stacking them up. You can monitor the currently running background updates with [the Admin API](usage/administration/admin_api/background_updates.html#status). +# Upgrading to v1.142.0 + +## Minimum supported Python version + +The minimum supported Python version has been increased from v3.9 to v3.10. +You will need Python 3.10+ to run Synapse v1.142.0. + +If you use current versions of the +[matrixorg/synapse](setup/installation.html#docker-images-and-ansible-playbooks) +Docker images, no action is required. + + # Upgrading to v1.141.0 ## Docker images now based on Debian `trixie` with Python 3.13 diff --git a/mypy.ini b/mypy.ini index eefe405fe..d6a343429 100644 --- a/mypy.ini +++ b/mypy.ini @@ -37,7 +37,7 @@ strict_equality = True # Run mypy type checking with the minimum supported Python version to catch new usage # that isn't backwards-compatible (types, overloads, etc). -python_version = 3.9 +python_version = 3.10 files = docker/, diff --git a/poetry.lock b/poetry.lock index dd86fe815..5a16dd586 100644 --- a/poetry.lock +++ b/poetry.lock @@ -60,9 +60,6 @@ files = [ {file = "automat-25.4.16.tar.gz", hash = "sha256:0017591a5477066e90d26b0e696ddc143baafd87b588cfac8100bc6be9634de0"}, ] -[package.dependencies] -typing_extensions = {version = "*", markers = "python_version < \"3.10\""} - [package.extras] visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] @@ -510,7 +507,6 @@ files = [ [package.dependencies] gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.10.0.2", markers = "python_version < \"3.10\""} [package.extras] doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] @@ -806,7 +802,7 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version < \"3.10\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\"" files = [ {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, @@ -820,26 +816,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] -[[package]] -name = "importlib-resources" -version = "5.12.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version < \"3.10\"" -files = [ - {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, - {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8 ; python_version < \"3.12\"", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] - [[package]] name = "incremental" version = "24.7.2" @@ -2846,8 +2822,6 @@ files = [ [package.dependencies] click = "*" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} -importlib-resources = {version = ">=5", markers = "python_version < \"3.10\""} jinja2 = "*" tomli = {version = "*", markers = "python_version < \"3.11\""} @@ -2893,7 +2867,6 @@ files = [ [package.dependencies] id = "*" -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} keyring = {version = ">=21.2.0", markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\""} packaging = ">=24.0" readme-renderer = ">=35.0" @@ -3220,7 +3193,7 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version < \"3.10\"" +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\"" files = [ {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, @@ -3342,5 +3315,5 @@ url-preview = ["lxml"] [metadata] lock-version = "2.1" -python-versions = "^3.9.0" -content-hash = "5d71c862b924bc2af936cb6fef264a023213153543f738af31357deaf6de19b8" +python-versions = "^3.10.0" +content-hash = "0122c5aa55099678f2ba5094ec393ebd814def15213388b33e5f1d7760392ffc" diff --git a/pyproject.toml b/pyproject.toml index 9a57a2b8d..08b4b8af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ [tool.ruff] line-length = 88 -target-version = "py39" +target-version = "py310" [tool.ruff.lint] # See https://beta.ruff.rs/docs/rules/#error-e @@ -165,7 +165,7 @@ synapse_review_recent_signups = "synapse._scripts.review_recent_signups:main" update_synapse_database = "synapse._scripts.update_synapse_database:main" [tool.poetry.dependencies] -python = "^3.9.0" +python = "^3.10.0" # Mandatory Dependencies # ---------------------- @@ -201,7 +201,8 @@ bcrypt = ">=3.1.7" # Packagers that already took care of libwebp can lower that down to 5.4.0. Pillow = ">=10.0.1" # We use SortedDict.peekitem(), which was added in sortedcontainers 1.5.2. -sortedcontainers = ">=1.5.2" +# 2.0.5 updates collections.abc imports to avoid Python 3.10 incompatibility. +sortedcontainers = ">=2.0.5" pymacaroons = ">=0.13.0" msgpack = ">=0.5.2" phonenumbers = ">=8.2.0" @@ -217,7 +218,8 @@ netaddr = ">=0.7.18" # end up with a broken installation, with recent MarkupSafe but old Jinja, we # add a lower bound to the Jinja2 dependency. Jinja2 = ">=3.0" -bleach = ">=1.4.3" +# 3.2.0 updates collections.abc imports to avoid Python 3.10 incompatibility. +bleach = ">=3.2.0" # We use `assert_never`, which were added in `typing-extensions` 4.1. typing-extensions = ">=4.1" # We enforce that we have a `cryptography` version that bundles an `openssl` @@ -258,10 +260,12 @@ authlib = { version = ">=0.15.1", optional = true } # `contrib/systemd/log_config.yaml`. # Note: systemd-python 231 appears to have been yanked from pypi systemd-python = { version = ">=231", optional = true } -lxml = { version = ">=4.5.2", optional = true } +# 4.6.3 removes usage of _PyGen_Send which is unavailable in CPython as of Python 3.10. +lxml = { version = ">=4.6.3", optional = true } sentry-sdk = { version = ">=0.7.2", optional = true } opentracing = { version = ">=2.2.0", optional = true } -jaeger-client = { version = ">=4.0.0", optional = true } +# 4.2.0 updates collections.abc imports to avoid Python 3.10 incompatibility. +jaeger-client = { version = ">=4.2.0", optional = true } txredisapi = { version = ">=1.4.7", optional = true } hiredis = { version = "*", optional = true } Pympler = { version = "*", optional = true } diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 070635729..4f0319a7f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -34,7 +34,7 @@ pyo3 = { version = "0.25.1", features = [ "macros", "anyhow", "abi3", - "abi3-py39", + "abi3-py310", ] } pyo3-log = "0.12.4" pythonize = "0.25.0" diff --git a/scripts-dev/build_debian_packages.py b/scripts-dev/build_debian_packages.py index f94c5a37f..60aa8a579 100755 --- a/scripts-dev/build_debian_packages.py +++ b/scripts-dev/build_debian_packages.py @@ -21,13 +21,12 @@ from types import FrameType from typing import Collection, Optional, Sequence # These are expanded inside the dockerfile to be a fully qualified image name. -# e.g. docker.io/library/debian:bullseye +# e.g. docker.io/library/debian:bookworm # # If an EOL is forced by a Python version and we're dropping support for it, make sure -# to remove references to the distibution across Synapse (search for "bullseye" for +# to remove references to the distibution across Synapse (search for "bookworm" for # example) DISTS = ( - "debian:bullseye", # (EOL ~2024-07) (our EOL forced by Python 3.9 is 2025-10-05) "debian:bookworm", # (EOL 2026-06) (our EOL forced by Python 3.11 is 2027-10-24) "debian:sid", # (rolling distro, no EOL) "ubuntu:jammy", # 22.04 LTS (EOL 2027-04) (our EOL forced by Python 3.10 is 2026-10-04) diff --git a/synapse/__init__.py b/synapse/__init__.py index d1c306b8f..2bed06087 100644 --- a/synapse/__init__.py +++ b/synapse/__init__.py @@ -39,8 +39,8 @@ ImageFile.LOAD_TRUNCATED_IMAGES = True # Note that we use an (unneeded) variable here so that pyupgrade doesn't nuke the # if-statement completely. py_version = sys.version_info -if py_version < (3, 9): - print("Synapse requires Python 3.9 or above.") +if py_version < (3, 10): + print("Synapse requires Python 3.10 or above.") sys.exit(1) # Allow using the asyncio reactor via env var. diff --git a/tox.ini b/tox.ini index a506b5034..a0e397bbb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39, py310, py311, py312, py313 +envlist = py310, py311, py312, py313 # we require tox>=2.3.2 for the fix to https://github.com/tox-dev/tox/issues/208 minversion = 2.3.2