Repository navigation
Expand file tree
/
Copy pathsync-python-version-constants.py
More file actions
129 lines (104 loc) · 4.16 KB
/
Copy pathsync-python-version-constants.py
File metadata and controls
129 lines (104 loc) · 4.16 KB
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""Update the Python version constants and the Python versions used in CI.
This script reads the download-metadata.json file and extracts the latest
patch version for each minor version (3.16, 3.15, 3.14, 3.13, 3.12, 3.11, 3.10).
It then updates the LATEST_PYTHON_X_Y constants in crates/uv-test/src/lib.rs
and the matching pins in .python-versions.
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
and CI Python versions in sync with the latest available Python versions.
"""
# /// script
# requires-python = ">=3.12"
# dependencies = ["packaging"]
# [tool.uv]
# no-build = true
# exclude-newer = "P7D"
# ///
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
PYTHON_MINOR_VERSIONS = ("3.16", "3.15", "3.14", "3.13", "3.12", "3.11", "3.10")
def main() -> None:
# Read the download metadata
metadata_path = ROOT / "crates" / "uv-python-managed" / "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, version in stable_versions.items():
latest_versions[minor] = version
for minor, version in prerelease_versions.items():
if minor not in latest_versions:
latest_versions[minor] = version
# Update the constants in uv-test/src/lib.rs
lib_path = ROOT / "crates" / "uv-test" / "src" / "lib.rs"
content = lib_path.read_text()
old_versions: dict[str, str] = {}
for minor in PYTHON_MINOR_VERSIONS:
const_name = f"LATEST_PYTHON_{minor.replace('.', '_')}"
pattern = rf'const {const_name}: &str = "([^"]+)";'
match = re.search(pattern, content)
if match:
old_versions[minor] = match.group(1)
if minor not in latest_versions:
continue
new_value = f'const {const_name}: &str = "{latest_versions[minor]}";'
content = re.sub(pattern, new_value, content)
lib_path.write_text(content)
# Update pins matching the previous latest versions, including free-threaded builds.
versions_path = ROOT / ".python-versions"
content = versions_path.read_text()
for minor, old_version in old_versions.items():
if minor not in latest_versions:
continue
content = re.sub(
rf"^{re.escape(old_version)}(t?)$",
rf"{latest_versions[minor]}\g<1>",
content,
flags=re.MULTILINE,
)
versions_path.write_text(content)
updates = []
for minor in PYTHON_MINOR_VERSIONS:
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()