Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/check-python-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,15 @@ jobs:
gh issue create \
--title "$title" \
--label debt \
--body "Python $VERSION has a published release candidate according to the [Python.org downloads API](https://www.python.org/api/v2/downloads/release/?is_published=true&pre_release=true&version=3). Update the supported Python versions in this repository."
--label python-version \
--body "Python $VERSION has a published release candidate according to the [Python.org downloads API](https://www.python.org/api/v2/downloads/release/?is_published=true&pre_release=true&version=3), but is missing from at least one CI test matrix.
### Requested changes
- [ ] Ensure the \`python\` matrix in \`.github/workflows/pr-check.yml\` includes Python $VERSION.
- [ ] Ensure the \`python\` matrix in \`.github/workflows/push-check.yml\` includes Python $VERSION.
- [ ] Run the tests on Python $VERSION and resolve any compatibility failures.
Compare matrix entries by major.minor; patch and prerelease/dev variants of $VERSION already count as coverage. Add the version only where missing and preserve existing entries.
Keep minimum-supported-version and build-runtime Python pins unchanged; adding CI coverage does not require raising the minimum supported Python version."
fi
58 changes: 47 additions & 11 deletions build/test_update_python_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ def test_latest_release_candidate_rejects_malformed_release_date(release_date):
update_python_versions.latest_release_candidate(releases, NOW)


def test_configured_versions(tmp_path):
@pytest.mark.parametrize(
"versions",
["['3.10', '3.11', '3.12']", "['3.10.9', '3.11-dev', '3.12.0rc1']"],
)
def test_configured_versions(tmp_path, versions):
workflow = tmp_path / "pr-check.yml"
workflow.write_text(
"matrix:\n python: ['3.10', '3.11', '3.12']\n", encoding="utf-8"
)
workflow.write_text(f"matrix:\n python: {versions}\n", encoding="utf-8")

assert update_python_versions.configured_versions(workflow) == {
"3.10",
Expand All @@ -63,17 +65,51 @@ def test_configured_versions_requires_one_matrix(tmp_path):


@pytest.mark.parametrize(
"matrix, expected",
"pr_version, push_version, expected",
[
("python: ['3.14']\n", "3.15"),
("python: ['3.14', '3.15']\n", None),
("3.14", "3.14", "3.15"),
("3.15", "3.14", "3.15"),
("3.14", "3.15", "3.15"),
("3.15", "3.15", None),
("3.15.1", "3.15.0-rc.2", None),
("3.150", "3.15", "3.15"),
],
)
def test_unconfigured_version(tmp_path, matrix, expected):
workflow = tmp_path / "pr-check.yml"
workflow.write_text(matrix, encoding="utf-8")
def test_unconfigured_version(tmp_path, pr_version, push_version, expected):
workflows = [tmp_path / name for name in ("pr-check.yml", "push-check.yml")]
for workflow, version in zip(workflows, (pr_version, push_version)):
workflow.write_text(f"python: ['{version}']\n", encoding="utf-8")
releases = [release("Python 3.15.0rc1", "2026-08-01T00:00:00Z")]

assert (
update_python_versions.unconfigured_version(releases, workflow, NOW) == expected
update_python_versions.unconfigured_version(releases, workflows, NOW)
== expected
)


@pytest.mark.parametrize(
"push_version, expected", [("3.14", "3.15\n"), ("3.15-dev", "")]
)
def test_main_checks_both_matrices(
tmp_path, monkeypatch, capsys, push_version, expected
):
workflow_dir = tmp_path / ".github" / "workflows"
workflow_dir.mkdir(parents=True)
(workflow_dir / "pr-check.yml").write_text("python: ['3.15.1']\n", encoding="utf-8")
(workflow_dir / "push-check.yml").write_text(
f"python: ['{push_version}']\n", encoding="utf-8"
)
monkeypatch.setattr(
update_python_versions,
"__file__",
str(tmp_path / "build" / "update_python_versions.py"),
)
monkeypatch.setattr(
update_python_versions,
"fetch_releases",
lambda: [release("Python 3.15.0rc1", "2020-08-01T00:00:00Z")],
)

update_python_versions.main()

assert capsys.readouterr().out == expected
16 changes: 10 additions & 6 deletions build/update_python_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,25 @@ def configured_versions(workflow):
matches = VERSION_MATRIX.findall(workflow.read_text(encoding="utf-8"))
if len(matches) != 1:
raise RuntimeError(f"Expected one Python version matrix in {workflow}")
versions = set(re.findall(r"['\"](3\.\d+)['\"]", matches[0]))
versions = set(re.findall(r"['\"](\d+\.\d+)[^'\"]*['\"]", matches[0]))
if not versions:
raise RuntimeError(f"No Python versions found in {workflow}")
return versions


def unconfigured_version(releases, workflow, now=None):
"""Return the newest RC series when it is missing from the test matrix."""
def unconfigured_version(releases, workflows, now=None):
"""Return the newest RC series when it is missing from either test matrix."""
latest = latest_release_candidate(releases, now)
return latest if latest not in configured_versions(workflow) else None
for workflow in workflows:
if latest not in configured_versions(workflow):
return latest
return None


def main():
workflow = pathlib.Path(__file__).parent.parent / ".github/workflows/pr-check.yml"
version = unconfigured_version(fetch_releases(), workflow)
workflow_dir = pathlib.Path(__file__).parent.parent / ".github/workflows"
workflows = [workflow_dir / name for name in ("pr-check.yml", "push-check.yml")]
version = unconfigured_version(fetch_releases(), workflows)
if version:
print(version)

Expand Down
Loading