Add prerelease compatibility check (#8020)

## Summary

Closes #7977. Makes `PythonDownloadRequest` account for the prerelease
part if allowed. Also stores the prerelease in `PythonInstallationKey`
directly as a `Prerelease` rather than a string.

## Test Plan

Correctly picks the relevant prerelease (rather than picking the most
recent one):
```
λ cargo run python install 3.13.0rc2
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/uv python install 3.13.0rc2`
Searching for Python versions matching: Python 3.13rc2
cpython-3.13.0rc2-macos-aarch64-none ------------------------------ 457.81 KiB/14.73 MiB                                                                                                                    ^C

λ cargo run python install 3.13.0rc3                 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/uv python install 3.13.0rc3`
Searching for Python versions matching: Python 3.13rc3
Found existing installation for Python 3.13rc3: cpython-3.13.0rc3-macos-aarch64-none
```
This commit is contained in:
trag1c 2024-10-08 23:20:58 +02:00 committed by GitHub
parent cfaa834dee
commit 37273cb4bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 794 additions and 768 deletions

View file

@ -17,6 +17,7 @@ Usage:
import argparse
import json
import logging
import re
import subprocess
import sys
from pathlib import Path
@ -29,6 +30,7 @@ WORKSPACE_ROOT = CRATE_ROOT.parent.parent
VERSION_METADATA = CRATE_ROOT / "download-metadata.json"
TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache"
TARGET = TEMPLATE.with_suffix("")
PRERELEASE_PATTERN = re.compile(r"(a|b|rc)(\d+)")
def prepare_name(name: str) -> str:
@ -61,11 +63,21 @@ def prepare_arch(arch: str) -> str:
return arch.capitalize()
def prepare_prerelease(prerelease: str) -> str:
if not prerelease:
return "None"
if not (match := PRERELEASE_PATTERN.match(prerelease)):
raise ValueError(f"Invalid prerelease: {prerelease!r}")
kind, number = match.groups()
return f"Some(Prerelease {{ kind: PrereleaseKind::{kind.capitalize()}, number: {number} }})"
def prepare_value(value: dict) -> dict:
value["os"] = value["os"].title()
value["arch"] = prepare_arch(value["arch"])
value["name"] = prepare_name(value["name"])
value["libc"] = prepare_libc(value["libc"])
value["prerelease"] = prepare_prerelease(value["prerelease"])
return value