Skip to content

Commit e04e132

Browse files
committed
Improve create-python-mirror to output matching json and optionally modify URLs
1 parent 399094a commit e04e132

File tree

1 file changed

+57
-15
lines changed

1 file changed

+57
-15
lines changed

scripts/create-python-mirror.py

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929

3030
SELF_DIR = Path(__file__).parent
3131
REPO_ROOT = SELF_DIR.parent
32-
VERSIONS_FILE = REPO_ROOT / "crates" / "uv-python" / "download-metadata.json"
32+
DOWNLOAD_METADATA_FILENAME = "download-metadata.json"
33+
VERSIONS_FILE = REPO_ROOT / "crates" / "uv-python" / DOWNLOAD_METADATA_FILENAME
3334
PREFIXES = [
3435
"https://github.com/astral-sh/python-build-standalone/releases/download/",
3536
"https://downloads.python.org/pypy/",
3637
]
38+
NONE_SPECIFIED = "NONE_SPECIFIED"
3739

3840

3941
logging.basicConfig(
@@ -62,7 +64,7 @@ def sha256_checksum(file_path: Path) -> str:
6264
return hasher.hexdigest()
6365

6466

65-
def collect_metadata_from_git_history() -> List[Dict]:
67+
def collect_metadata_from_git_history() -> List[Tuple[str, Dict]]:
6668
"""Collect all metadata entries from the history of the VERSIONS_FILE."""
6769
metadata = []
6870
try:
@@ -74,7 +76,7 @@ def collect_metadata_from_git_history() -> List[Dict]:
7476
blob = commit.tree / str(VERSIONS_FILE.relative_to(REPO_ROOT))
7577
content = blob.data_stream.read().decode()
7678
data = json.loads(content)
77-
metadata.extend(data.values())
79+
metadata.extend(data.items())
7880
except KeyError:
7981
logger.warning(
8082
f"File {VERSIONS_FILE} not found in commit {commit.hexsha}. Skipping."
@@ -102,33 +104,33 @@ def check_arch(entry, arch):
102104
def match_version(entry, pattern):
103105
"""Checks whether pattern matches against the entries version."""
104106
vers = f"{entry['major']}.{entry['minor']}.{entry['patch']}"
105-
if entry["prerelease"] != "":
107+
if "prerelease" in entry and entry["prerelease"] != "":
106108
vers += f"-{entry['prerelease']}"
107109
return pattern.match(vers) is not None
108110

109111

110112
def filter_metadata(
111-
metadata: List[Dict],
113+
metadata: List[Tuple[str, Dict]],
112114
name: Optional[str],
113115
arch: Optional[str],
114116
os: Optional[str],
115117
version: Optional[re.Pattern],
116-
) -> List[Dict]:
118+
) -> List[Tuple[str, Dict]]:
117119
"""Filter the metadata based on name, architecture, and OS, ensuring unique URLs."""
118120
filtered = [
119121
entry
120122
for entry in metadata
121-
if (not name or entry["name"] == name)
122-
and (not arch or check_arch(entry["arch"], arch))
123-
and (not os or entry["os"] == os)
124-
and (not version or match_version(entry, version))
123+
if (not name or entry[1]["name"] == name)
124+
and (not arch or check_arch(entry[1]["arch"], arch))
125+
and (not os or entry[1]["os"] == os)
126+
and (not version or match_version(entry[1], version))
125127
]
126128
# Use a set to ensure unique URLs
127129
unique_urls = set()
128130
unique_filtered = []
129131
for entry in filtered:
130-
if entry["url"] not in unique_urls:
131-
unique_urls.add(entry["url"])
132+
if entry[1]["url"] not in unique_urls:
133+
unique_urls.add(entry[1]["url"])
132134
unique_filtered.append(entry)
133135
return unique_filtered
134136

@@ -222,6 +224,28 @@ async def sem_download(url, sha256):
222224
return success_count, errors
223225

224226

227+
def update_mirror_url(mirror_url: str, filtered_metadata: List[Tuple[str, Dict]]):
228+
"""Updates the given filtered_metadata to point at the given mirror_url"""
229+
if not mirror_url.endswith("/"):
230+
mirror_url += "/"
231+
for entry in filtered_metadata:
232+
for prefix in PREFIXES:
233+
entry[1]["url"] = entry[1]["url"].replace(prefix, mirror_url)
234+
235+
236+
def write_metadata(
237+
save_metadata: str, target: str, filtered_metadata: List[Tuple[str, Dict]]
238+
):
239+
"""Save the filtered_metadata to the given save_metadata path or to target if NONE_SPECIFIED"""
240+
241+
metadata_output = Path(target) / DOWNLOAD_METADATA_FILENAME
242+
if save_metadata != NONE_SPECIFIED:
243+
metadata_output = Path(save_metadata)
244+
Path(metadata_output).parent.mkdir(parents=True, exist_ok=True)
245+
with open(metadata_output, "w") as f:
246+
json.dump(dict(filtered_metadata), f, indent=2)
247+
248+
225249
def parse_arguments():
226250
"""Parse command-line arguments using argparse."""
227251
parser = argparse.ArgumentParser(description="Download and mirror Python builds.")
@@ -247,6 +271,15 @@ def parse_arguments():
247271
default=SELF_DIR / "mirror",
248272
help="Directory to store the downloaded files.",
249273
)
274+
parser.add_argument(
275+
"--save-metadata",
276+
nargs="?",
277+
const=NONE_SPECIFIED,
278+
help="File to save metadata. Defaults to 'target' if no value provided.",
279+
)
280+
parser.add_argument(
281+
"--mirror-url", help="Saved metadata has URLs updated to this root path."
282+
)
250283
return parser.parse_args()
251284

252285

@@ -258,13 +291,13 @@ def main():
258291
metadata = collect_metadata_from_git_history()
259292
else:
260293
with open(VERSIONS_FILE) as f:
261-
metadata = list(json.load(f).values())
294+
metadata: List[Tuple[str, Dict]] = list(json.load(f).items())
262295

263296
version = re.compile(args.version) if args.version else None
264297
filtered_metadata = filter_metadata(
265298
metadata, args.name, args.arch, args.os, version
266299
)
267-
urls = {(entry["url"], entry["sha256"]) for entry in filtered_metadata}
300+
urls = {(entry[1]["url"], entry[1]["sha256"]) for entry in filtered_metadata}
268301

269302
if not urls:
270303
logger.error("No URLs found.")
@@ -281,8 +314,17 @@ def main():
281314
print("Failed downloads:")
282315
for url, error in errors:
283316
print(f"- {url}: {error}")
317+
318+
example_mirror = f"file://{target.absolute()}"
319+
if args.mirror_url:
320+
update_mirror_url(args.mirror_url, filtered_metadata)
321+
example_mirror = args.mirror_url
322+
323+
if args.save_metadata is not None:
324+
write_metadata(args.save_metadata, args.target, filtered_metadata)
325+
284326
print(
285-
f"Example usage: `UV_PYTHON_INSTALL_MIRROR='file://{target.absolute()}' uv python install 3.13`"
327+
f"Example usage: `UV_PYTHON_INSTALL_MIRROR='{example_mirror}' uv python install 3.13`"
286328
)
287329
except Exception as e:
288330
logger.error(f"Error during download: {e}")

0 commit comments

Comments
 (0)