Drop Python 3.9 support Python 3.9 reached its official End-of-Life (EOL) on October 31, 2025 https://devguide.python.org/versions/ PiperOrigin-RevId: 854341311
diff --git a/.github/workflows/test_python.yml b/.github/workflows/test_python.yml index f071bbc..83bc307 100644 --- a/.github/workflows/test_python.yml +++ b/.github/workflows/test_python.yml
@@ -28,7 +28,7 @@ fail-fast: false # Don't cancel all jobs if one fails. matrix: type: [ Pure, C++] - version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + version: ["3.10", "3.11", "3.12", "3.13", "3.14"] include: - type: Pure targets: //python/... //python:python_version_test @@ -37,10 +37,6 @@ targets: //python/... //python:python_version_test flags: --define=use_fast_cpp_protos=true # Test using WORKSPACE with our oldest support Python version. - - version: "3.9" - nobzlmod: true - # Our 3.9 image has non-hermetic issues and can't be rebuilt. Use the old version. - image: us-docker.pkg.dev/protobuf-build/containers/test/linux/python:7.6.1-3.9-12e21b8dda91028bc14212a3ab582c7c4d149fac - version: "3.10" - version: "3.11" continuous-only: true
diff --git a/.github/workflows/test_upb.yml b/.github/workflows/test_upb.yml index d5e6b4c..220bb0c 100644 --- a/.github/workflows/test_upb.yml +++ b/.github/workflows/test_upb.yml
@@ -142,25 +142,21 @@ # a single wheel. As a result we can just test the oldest and newest # supported Python versions and assume this gives us sufficient test # coverage. - - { os: ubuntu-latest, python-version: "3.9", architecture: x64, type: 'binary' } - - { os: macos-14, python-version: "3.9", architecture: arm64, type: 'binary' } - - { os: ubuntu-latest, python-version: "3.13", architecture: x64, type: 'binary' } - - { os: macos-14, python-version: "3.13", architecture: arm64, type: 'binary' } - - { os: ubuntu-latest, python-version: "3.9", architecture: x64, type: 'source'} - - { os: macos-14, python-version: "3.9", architecture: arm64, type: 'source', continuous-only: true } - - { os: ubuntu-latest, python-version: "3.13", architecture: x64, type: 'source'} - - { os: macos-14, python-version: "3.13", architecture: arm64, type: 'source', continuous-only: true } + - { os: ubuntu-latest, python-version: "3.10", architecture: x64, type: 'binary' } + - { os: macos-14, python-version: "3.10", architecture: arm64, type: 'binary' } + - { os: ubuntu-latest, python-version: "3.14", architecture: x64, type: 'binary' } + - { os: macos-14, python-version: "3.14", architecture: arm64, type: 'binary' } + - { os: ubuntu-latest, python-version: "3.10", architecture: x64, type: 'source'} + - { os: macos-14, python-version: "3.10", architecture: arm64, type: 'source', continuous-only: true } - { os: ubuntu-latest, python-version: "3.14", architecture: x64, type: 'source', continuous-only: true } - { os: macos-14, python-version: "3.14", architecture: arm64, type: 'source', continuous-only: true } # Windows uses the full API up until Python 3.10. - - { os: windows-2022, python-version: "3.9", architecture: x86, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.10", architecture: x86, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.11", architecture: x86, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.12", architecture: x86, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.13", architecture: x86, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.14", architecture: x86, type: 'binary', continuous-only: true } - - { os: windows-2022, python-version: "3.9", architecture: x64, type: 'binary' } - { os: windows-2022, python-version: "3.10", architecture: x64, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.11", architecture: x64, type: 'binary', continuous-only: true } - { os: windows-2022, python-version: "3.12", architecture: x64, type: 'binary', continuous-only: true } @@ -237,7 +233,7 @@ strategy: fail-fast: false # Don't cancel all jobs if one fails. matrix: - python-version: ["3.9", "3.13", "3.14"] + python-version: ["3.10", "3.14"] # oldest + newest versions runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request_target' }} steps:
diff --git a/MODULE.bazel b/MODULE.bazel index d60a293..59a9c3b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -139,11 +139,11 @@ register_toolchains("//bazel/private/toolchains:all") SUPPORTED_PYTHON_VERSIONS = [ - "3.9", "3.10", "3.11", "3.12", "3.13", + "3.14", ] # TODO: Replace system_python with hermetic_python. @@ -331,18 +331,8 @@ # Python headers for release python_headers = use_extension("//python/dist:python_downloads.bzl", "python_headers", dev_dependency = True) python_headers.source_archive( - sha256 = "df796b2dc8ef085edae2597a41c1c0a63625ebd92487adaef2fed22b567873e8", - version = "3.9.0", -) -python_headers.nuget_package( - cpu = "i686", - sha256 = "229abecbe49dc08fe5709e0b31e70edfb3b88f23335ebfc2904c44f940fd59b6", - version = "3.9.0", -) -python_headers.nuget_package( - cpu = "x86-64", - sha256 = "6af58a733e7dfbfcdd50d55788134393d6ffe7ab8270effbf724bdb786558832", - version = "3.9.0", + sha256 = "c4e0cbad57c90690cb813fb4663ef670b4d0f587d8171e2c42bd4c9245bd2758", + version = "3.10.0", ) python_headers.nuget_package( cpu = "i686", @@ -357,8 +347,6 @@ use_repo( python_headers, "nuget_python_i686_3.10.0", - "nuget_python_i686_3.9.0", "nuget_python_x86-64_3.10.0", - "nuget_python_x86-64_3.9.0", - "python-3.9.0", + "python-3.10.0", )
diff --git a/protobuf_deps.bzl b/protobuf_deps.bzl index d481463..7a29dd7 100644 --- a/protobuf_deps.bzl +++ b/protobuf_deps.bzl
@@ -150,7 +150,7 @@ if not native.existing_rule("system_python"): system_python( name = "system_python", - minimum_python_version = "3.9", + minimum_python_version = "3.10", ) if not native.existing_rule("rules_jvm_external"): @@ -204,18 +204,8 @@ # Python Downloads python_source_archive( - version = "3.9.0", - sha256 = "df796b2dc8ef085edae2597a41c1c0a63625ebd92487adaef2fed22b567873e8", - ) - python_nuget_package( - version = "3.9.0", - cpu = "i686", - sha256 = "229abecbe49dc08fe5709e0b31e70edfb3b88f23335ebfc2904c44f940fd59b6", - ) - python_nuget_package( - version = "3.9.0", - cpu = "x86-64", - sha256 = "6af58a733e7dfbfcdd50d55788134393d6ffe7ab8270effbf724bdb786558832", + version = "3.10.0", + sha256 = "c4e0cbad57c90690cb813fb4663ef670b4d0f587d8171e2c42bd4c9245bd2758", ) python_nuget_package( version = "3.10.0",
diff --git a/python/BUILD.bazel b/python/BUILD.bazel index e8c94ae..291a4cc 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel
@@ -5,7 +5,6 @@ # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd -load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") load("//python:build_targets.bzl", "build_targets") load("//python:py_extension.bzl", "py_extension") @@ -21,7 +20,6 @@ ) LIMITED_API_FLAG_SELECT = { - ":limited_api_3.9": ["-DPy_LIMITED_API=0x03090000"], ":limited_api_3.10": ["-DPy_LIMITED_API=0x030a0000"], "//conditions:default": [], } @@ -43,40 +41,14 @@ ) config_setting( - name = "limited_api_3.9", + name = "limited_api_3.10", flag_values = { ":limited_api": "True", - ":python_version": "39", + ":python_version": "310", }, ) config_setting( - name = "full_api_3.9_win32", - flag_values = { - ":limited_api": "False", - ":python_version": "39", - }, - values = {"cpu": "win32"}, -) - -config_setting( - name = "full_api_3.9_win64", - flag_values = { - ":limited_api": "False", - ":python_version": "39", - }, - values = {"cpu": "win64"}, -) - -selects.config_setting_group( - name = "full_api_3.9", - match_any = [ - "full_api_3.9_win32", - ":full_api_3.9_win64", - ], -) - -config_setting( name = "limited_api_3.10_win32", flag_values = { ":limited_api": "True", @@ -94,14 +66,6 @@ values = {"cpu": "win64"}, ) -selects.config_setting_group( - name = "limited_api_3.10", - match_any = [ - ":limited_api_3.10_win32", - ":limited_api_3.10_win64", - ], -) - _message_target_compatible_with = { "@platforms//os:windows": ["@platforms//:incompatible"], "@system_python//:none": ["@platforms//:incompatible"],
diff --git a/python/dist/BUILD.bazel b/python/dist/BUILD.bazel index e93790a..14d3f47 100644 --- a/python/dist/BUILD.bazel +++ b/python/dist/BUILD.bazel
@@ -328,7 +328,6 @@ py_wheel( name = "binary_wheel", abi = select({ - "//python:full_api_3.9": "cp39", "//conditions:default": "abi3", }), author = "protobuf@googlegroups.com", @@ -336,7 +335,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -362,15 +360,13 @@ ":windows_x86_64": "win_amd64", "//conditions:default": "any", }), - python_requires = ">=3.9", + python_requires = ">=3.10", # LINT.IfChange(python_tag) python_tag = selects.with_or({ - ("//python:limited_api_3.9", "//python:full_api_3.9"): "cp39", "//python:limited_api_3.10": "cp310", "//conditions:default": "cp" + SYSTEM_PYTHON_VERSION, }), # LINT.ThenChange( - # :full_api_version, # :limited_api_wheels, # ) strip_path_prefixes = [ @@ -399,7 +395,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -413,7 +408,7 @@ homepage = "https://developers.google.com/protocol-buffers/", license = "3-Clause BSD License", platform = "any", - python_requires = ">=3.9", + python_requires = ">=3.10", python_tag = "py3", strip_path_prefixes = [ "python/", @@ -466,12 +461,6 @@ "win32", "win64", ], - # Windows needs version-specific wheels until 3.10. - # LINT.IfChange(full_api_version) - full_api_versions = [ - "39", - ], - # LINT.ThenChange(:python_tag) # Limited API: these wheels will satisfy any Python version >= the # given version. # @@ -482,10 +471,10 @@ limited_api_wheels = { "win32": "310", "win64": "310", - "linux-x86_64": "39", - "linux-aarch_64": "39", - "linux-s390_64": "39", - "osx-universal2": "39", + "linux-x86_64": "310", + "linux-aarch_64": "310", + "linux-s390_64": "310", + "osx-universal2": "310", }, # LINT.ThenChange(:python_tag) pure_python_wheel = ":pure_python_wheel",
diff --git a/python/dist/setup.py b/python/dist/setup.py index e73e1aa..92aa8b6 100755 --- a/python/dist/setup.py +++ b/python/dist/setup.py
@@ -71,7 +71,6 @@ classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', @@ -89,5 +88,5 @@ extra_link_args=extra_link_args, ) ], - python_requires='>=3.9', + python_requires='>=3.10', )
diff --git a/python/dist/system_python.bzl b/python/dist/system_python.bzl index 2a1c564..16a1a95 100644 --- a/python/dist/system_python.bzl +++ b/python/dist/system_python.bzl
@@ -268,7 +268,7 @@ implementation = _system_python_impl, local = True, attrs = { - "minimum_python_version": attr.string(default = "3.9"), + "minimum_python_version": attr.string(default = "3.10"), }, )
diff --git a/python/protobuf_distutils/setup.py b/python/protobuf_distutils/setup.py index 61cf99f..00fa76e 100644 --- a/python/protobuf_distutils/setup.py +++ b/python/protobuf_distutils/setup.py
@@ -30,7 +30,6 @@ # These Python versions should match the protobuf package: 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12',
diff --git a/python/py_extension.bzl b/python/py_extension.bzl index 8603aba..82d8445 100644 --- a/python/py_extension.bzl +++ b/python/py_extension.bzl
@@ -28,9 +28,7 @@ linkshared = True, linkstatic = True, deps = deps + select({ - "//python:limited_api_3.9": ["@python-3.9.0//:python_headers"], - "//python:full_api_3.9_win32": ["@nuget_python_i686_3.9.0//:python_full_api"], - "//python:full_api_3.9_win64": ["@nuget_python_x86-64_3.9.0//:python_full_api"], + "//python:limited_api_3.10": ["@python-3.10.0//:python_headers"], "//python:limited_api_3.10_win32": ["@nuget_python_i686_3.10.0//:python_limited_api"], "//python:limited_api_3.10_win64": ["@nuget_python_x86-64_3.10.0//:python_limited_api"], "//conditions:default": ["@system_python//:python_headers"],