From 99828c02c68cd743f1ce5f89c74e07eee0197637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 21:57:25 -0700 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=91=B7=20ci:=20add=20Python=203.15=20?= =?UTF-8?q?to=20test=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yaml | 1 + tox.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 20ad76a..e8bdaa3 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -19,6 +19,7 @@ jobs: fail-fast: false matrix: env: + - "3.15" - "3.14" - "3.13" - "3.12" diff --git a/tox.toml b/tox.toml index 20f6da5..5a8ab0b 100644 --- a/tox.toml +++ b/tox.toml @@ -1,5 +1,5 @@ requires = ["tox>=4.38", "tox-uv>=1.29"] -env_list = ["fix", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "type-3.8", "type-3.14", "docs", "pkg_meta"] +env_list = ["fix", "3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "type-3.8", "type-3.14", "docs", "pkg_meta"] skip_missing_interpreters = true [env_run_base] From 2d5089b39d81a2e9874e5baaf000f0693da06706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 22:30:04 -0700 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=A6=20pkg:=20declare=20Python=203.?= =?UTF-8?q?15=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bdfede7..f4a0b14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries", "Topic :: Utilities", @@ -145,7 +146,7 @@ write-changes = true count = true [tool.pyproject-fmt] -max_supported_python = "3.14" +max_supported_python = "3.15" [tool.ty] src.exclude = [ "tests/windows/winreg_mock_values.py" ] From 76c413897ff2588f81e8c46dc7f517c6fcaf4d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 22:33:44 -0700 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9D=20docs(changelog):=20note=20Py?= =?UTF-8?q?thon=203.15=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog/104.packaging.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changelog/104.packaging.rst diff --git a/docs/changelog/104.packaging.rst b/docs/changelog/104.packaging.rst new file mode 100644 index 0000000..3206810 --- /dev/null +++ b/docs/changelog/104.packaging.rst @@ -0,0 +1 @@ +Declare support for Python 3.15 and run the test suite against it, currently in beta. From 3b74bfdb9f4efcf6fa77a7aa4d9dccec8f876486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 22:35:37 -0700 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=91=B7=20ci:=20test=20the=20free-thre?= =?UTF-8?q?aded=203.15=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yaml | 1 + tox.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index e8bdaa3..92f2550 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -20,6 +20,7 @@ jobs: matrix: env: - "3.15" + - "3.15t" - "3.14" - "3.13" - "3.12" diff --git a/tox.toml b/tox.toml index 5a8ab0b..c90e125 100644 --- a/tox.toml +++ b/tox.toml @@ -1,5 +1,5 @@ requires = ["tox>=4.38", "tox-uv>=1.29"] -env_list = ["fix", "3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "type-3.8", "type-3.14", "docs", "pkg_meta"] +env_list = ["fix", "3.15", "3.15t", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "type-3.8", "type-3.14", "docs", "pkg_meta"] skip_missing_interpreters = true [env_run_base] From 449a3a5d4c161c8c368522efbec648afb9e15f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 23:02:27 -0700 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=85=20test:=20name=20the=20interprete?= =?UTF-8?q?r=20a=20spec=20has=20to=20match?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tests built a spec that cannot describe the interpreter running them, so both failed on a free-threaded build. One took the executable's stem, which drops everything from the first dot: python3.15t arrived as python3, naming a build with the GIL. It now takes the full name and drops only the Windows suffix. The other asked for a bare version, which the parser reads as a build with the GIL, so discovery had nothing to return. It now carries the free-threaded marker the way the other tests in that file do. --- tests/test_discovery.py | 3 ++- tests/test_py_info_extra.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 9ea5ab4..138650d 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -437,7 +437,8 @@ def reject_first(info: PythonInfo) -> bool: proposed.append(str(info.executable)) return len(proposed) > 1 - result = get_interpreter([f"{major}.{minor}", sys.executable], [], session_cache, predicate=reject_first) + threaded = "t" if current.free_threaded else "" + result = get_interpreter([f"{major}.{minor}{threaded}", sys.executable], [], session_cache, predicate=reject_first) # rejecting the first proposal must not abort discovery; a later proposal (same or fallback spec) is accepted - # on hosts where every alias resolves to one interpreter the fallback re-proposes it, so executables may match assert result is not None diff --git a/tests/test_py_info_extra.py b/tests/test_py_info_extra.py index 19b5685..2a3b7bc 100644 --- a/tests/test_py_info_extra.py +++ b/tests/test_py_info_extra.py @@ -437,7 +437,9 @@ def test_satisfies_version_specifier_prerelease(version_info: VersionInfo, spec_ def test_satisfies_path_not_abs_basename_match() -> None: info = copy.deepcopy(CURRENT) - basename = Path(info.original_executable).stem + # stem drops everything from the first dot, so python3.15t arrives as python3 and stops naming a free-threaded + # build; only the Windows .exe belongs off the end. + basename = Path(info.original_executable).name.removesuffix(".exe") spec = PythonSpec.from_string_spec(basename) assert info.satisfies(spec, impl_must_match=False) is True From 16fcf5900e3d6eaa96e2600730f2e8b0d197f841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 20 Jul 2026 23:09:42 -0700 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=85=20test:=20keep=20the=20basename?= =?UTF-8?q?=20check=20on=203.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_py_info_extra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_py_info_extra.py b/tests/test_py_info_extra.py index 2a3b7bc..6cdebf1 100644 --- a/tests/test_py_info_extra.py +++ b/tests/test_py_info_extra.py @@ -439,7 +439,8 @@ def test_satisfies_path_not_abs_basename_match() -> None: info = copy.deepcopy(CURRENT) # stem drops everything from the first dot, so python3.15t arrives as python3 and stops naming a free-threaded # build; only the Windows .exe belongs off the end. - basename = Path(info.original_executable).name.removesuffix(".exe") + name = Path(info.original_executable).name + basename = name[: -len(".exe")] if name.endswith(".exe") else name spec = PythonSpec.from_string_spec(basename) assert info.satisfies(spec, impl_must_match=False) is True