uv /
scripts /
sync-python-version-constants.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114"""Update the Python version constants in the test common module.
This script reads the download-metadata.json file and extracts the latest
patch version for each minor version (3.15, 3.14, 3.13, 3.12, 3.11, 3.10).
It then updates the LATEST_PYTHON_X_Y constants in crates/uv/tests/it/common/mod.rs.
For minor versions with stable releases, it uses the latest stable version.
For minor versions with only prereleases, it uses the latest prerelease.
This is called by the sync-python-releases workflow to keep the test constants
in sync with the latest available Python versions.
"""
# /// script
# requires-python = ">=3.12"
# dependencies = ["packaging"]
# ///
from __future__ import annotations
import json
import re
from pathlib import Path
from packaging.version import Version
SELF_DIR = Path(__file__).parent
ROOT = SELF_DIR.parent
def main() -> None:
# Read the download metadata
metadata_path = ROOT / "crates" / "uv-python" / "download-metadata.json"
with open(metadata_path) as f:
metadata = json.load(f)
# Collect all versions per minor, separating stable and prerelease
stable_versions: dict[str, str] = {}
prerelease_versions: dict[str, str] = {}
for info in metadata.values():
if info["name"] != "cpython":
continue
if info.get("variant"):
continue
minor = f"3.{info['minor']}"
prerelease = info.get("prerelease", "")
version = f"{info['major']}.{info['minor']}.{info['patch']}"
if prerelease:
version += prerelease
if prerelease:
if minor not in prerelease_versions or Version(version) > Version(
prerelease_versions[minor]
):
prerelease_versions[minor] = version
else:
if minor not in stable_versions or Version(version) > Version(
stable_versions[minor]
):
stable_versions[minor] = version
# Use stable if available, otherwise prerelease
latest_versions: dict[str, str] = {}
for minor in stable_versions:
latest_versions[minor] = stable_versions[minor]
for minor in prerelease_versions:
if minor not in latest_versions:
latest_versions[minor] = prerelease_versions[minor]
# Update the constants in common/mod.rs
mod_path = ROOT / "crates" / "uv" / "tests" / "it" / "common" / "mod.rs"
content = mod_path.read_text()
# Extract old values first
old_versions: dict[str, str] = {}
for minor in ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10"]:
const_name = f"LATEST_PYTHON_{minor.replace('.', '_')}"
match = re.search(rf'pub const {const_name}: &str = "([^"]+)";', content)
if match:
old_versions[minor] = match.group(1)
for minor in ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10"]:
if minor not in latest_versions:
continue
const_name = f"LATEST_PYTHON_{minor.replace('.', '_')}"
old_pattern = rf'pub const {const_name}: &str = "[^"]+";'
new_value = f'pub const {const_name}: &str = "{latest_versions[minor]}";'
content = re.sub(old_pattern, new_value, content)
mod_path.write_text(content)
updates = []
for minor in ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10"]:
if minor not in latest_versions:
continue
new_version = latest_versions[minor]
old_version = old_versions.get(minor)
if old_version != new_version:
updates.append(f" {old_version} -> {new_version}")
if updates:
print("Updated Python version constants:")
for update in updates:
print(update)
else:
print("Python version constants are up to date")
if __name__ == "__main__":
main()