mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00

I'm waiting on a response to get our subscription back up. Then I can re-enable this. But for now, this would cause failing CI tests.
423 lines
14 KiB
Python
423 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test `uv add` against multiple Python package registries.
|
|
|
|
This script looks for environment variables that configure registries for testing.
|
|
To configure a registry, set the following environment variables:
|
|
|
|
`UV_TEST_<registry_name>_URL` URL for the registry
|
|
`UV_TEST_<registry_name>_TOKEN` authentication token
|
|
|
|
The username defaults to "__token__" but can be optionally set with:
|
|
`UV_TEST_<registry_name>_USERNAME`
|
|
|
|
The package to install defaults to "astral-registries-test-pkg" but can be optionally
|
|
set with:
|
|
`UV_TEST_<registry_name>_PKG`
|
|
|
|
Keep in mind that some registries can fall back to PyPI internally, so make sure
|
|
you choose a package that only exists in the registry you are testing.
|
|
|
|
You can also use the 1Password CLI to fetch registry credentials from a vault by passing
|
|
the `--use-op` flag. For each item in the vault named `UV_TEST_XXX`, the script will set
|
|
env vars for any of the following fields, if present:
|
|
`UV_TEST_<registry_name>_USERNAME` from the `username` field
|
|
`UV_TEST_<registry_name>_TOKEN` from the `password` field
|
|
`UV_TEST_<registry_name>_URL` from a field with the label `url`
|
|
`UV_TEST_<registry_name>_PKG` from a field with the label `pkg`
|
|
|
|
# /// script
|
|
# requires-python = ">=3.12"
|
|
# dependencies = ["colorama>=0.4.6"]
|
|
# ///
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict
|
|
|
|
import colorama
|
|
from colorama import Fore
|
|
|
|
|
|
def initialize_colorama(force_color=False):
|
|
colorama.init(strip=not force_color, autoreset=True)
|
|
|
|
|
|
cwd = Path(__file__).parent
|
|
|
|
DEFAULT_TIMEOUT = 30
|
|
DEFAULT_PKG_NAME = "astral-registries-test-pkg"
|
|
|
|
KNOWN_REGISTRIES = [
|
|
# TODO(john): Restore this when subscription starts up again
|
|
# "artifactory",
|
|
"azure",
|
|
"aws",
|
|
"cloudsmith",
|
|
"gcp",
|
|
"gemfury",
|
|
"gitlab",
|
|
]
|
|
|
|
|
|
def fetch_op_items(vault_name: str, env: Dict[str, str]) -> Dict[str, str]:
|
|
"""Fetch items from the specified 1Password vault and add them to the environment.
|
|
|
|
For each item named UV_TEST_XXX in the vault:
|
|
- Set `UV_TEST_XXX_USERNAME` to the `username` field
|
|
- Set `UV_TEST_XXX_TOKEN` to the `password` field
|
|
- Set `UV_TEST_XXX_URL` to the `url` field
|
|
|
|
Raises exceptions for any 1Password CLI errors so they can be handled by the caller.
|
|
"""
|
|
# Run 'op item list' to get all items in the vault
|
|
result = subprocess.run(
|
|
["op", "item", "list", "--vault", vault_name, "--format", "json"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
items = json.loads(result.stdout)
|
|
updated_env = env.copy()
|
|
|
|
for item in items:
|
|
item_id = item["id"]
|
|
item_title = item["title"]
|
|
|
|
# Only process items that match the registry naming pattern
|
|
if item_title.startswith("UV_TEST_"):
|
|
# Extract the registry name (e.g., "AWS" from "UV_TEST_AWS")
|
|
registry_name = item_title.removeprefix("UV_TEST_")
|
|
|
|
# Get the item details
|
|
item_details = subprocess.run(
|
|
["op", "item", "get", item_id, "--format", "json"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
item_data = json.loads(item_details.stdout)
|
|
|
|
username = None
|
|
password = None
|
|
url = None
|
|
pkg = None
|
|
|
|
if "fields" in item_data:
|
|
for field in item_data["fields"]:
|
|
if field.get("id") == "username":
|
|
username = field.get("value")
|
|
elif field.get("id") == "password":
|
|
password = field.get("value")
|
|
elif field.get("label") == "url":
|
|
url = field.get("value")
|
|
elif field.get("label") == "pkg":
|
|
pkg = field.get("value")
|
|
if username:
|
|
updated_env[f"UV_TEST_{registry_name}_USERNAME"] = username
|
|
if password:
|
|
updated_env[f"UV_TEST_{registry_name}_TOKEN"] = password
|
|
if url:
|
|
updated_env[f"UV_TEST_{registry_name}_URL"] = url
|
|
if pkg:
|
|
updated_env[f"UV_TEST_{registry_name}_PKG"] = pkg
|
|
|
|
print(f"Added 1Password credentials for {registry_name}")
|
|
|
|
return updated_env
|
|
|
|
|
|
def get_registries(env: Dict[str, str]) -> Dict[str, str]:
|
|
pattern = re.compile(r"^UV_TEST_(.+)_URL$")
|
|
registries: Dict[str, str] = {}
|
|
|
|
for env_var, value in env.items():
|
|
match = pattern.match(env_var)
|
|
if match:
|
|
registry_name = match.group(1).lower()
|
|
registries[registry_name] = value
|
|
|
|
return registries
|
|
|
|
|
|
def setup_test_project(
|
|
registry_name: str, registry_url: str, project_dir: str, requires_python: str
|
|
):
|
|
"""Create a temporary project directory with a pyproject.toml"""
|
|
pyproject_content = f"""[project]
|
|
name = "{registry_name}-test"
|
|
version = "0.1.0"
|
|
description = "Test registry"
|
|
requires-python = ">={requires_python}"
|
|
|
|
[[tool.uv.index]]
|
|
name = "{registry_name}"
|
|
url = "{registry_url}"
|
|
default = true
|
|
"""
|
|
pyproject_file = Path(project_dir) / "pyproject.toml"
|
|
pyproject_file.write_text(pyproject_content)
|
|
|
|
|
|
def run_test(
|
|
env: dict[str, str],
|
|
uv: Path,
|
|
registry_name: str,
|
|
registry_url: str,
|
|
package: str,
|
|
username: str,
|
|
token: str,
|
|
verbosity: int,
|
|
timeout: int,
|
|
requires_python: str,
|
|
) -> bool:
|
|
print(uv)
|
|
"""Attempt to install a package from this registry."""
|
|
print(
|
|
f"{registry_name} -- Running test for {registry_url} with username {username}"
|
|
)
|
|
if package == DEFAULT_PKG_NAME:
|
|
print(
|
|
f"** Using default test package name: {package}. To choose a different package, set UV_TEST_{registry_name.upper()}_PKG"
|
|
)
|
|
print(f"\nAttempting to install {package}")
|
|
env[f"UV_INDEX_{registry_name.upper()}_USERNAME"] = username
|
|
env[f"UV_INDEX_{registry_name.upper()}_PASSWORD"] = token
|
|
|
|
with tempfile.TemporaryDirectory() as project_dir:
|
|
setup_test_project(registry_name, registry_url, project_dir, requires_python)
|
|
|
|
cmd = [
|
|
uv,
|
|
"add",
|
|
package,
|
|
"--directory",
|
|
project_dir,
|
|
]
|
|
if verbosity:
|
|
cmd.extend(["-" + "v" * verbosity])
|
|
|
|
result = None
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
check=False,
|
|
env=env,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
|
print(f"{Fore.RED}{registry_name}: FAIL{Fore.RESET} \n\n{error_msg}")
|
|
return False
|
|
|
|
success = False
|
|
for line in result.stderr.strip().split("\n"):
|
|
if line.startswith(f" + {package}=="):
|
|
success = True
|
|
if success:
|
|
print(f"{Fore.GREEN}{registry_name}: PASS")
|
|
if verbosity > 0:
|
|
print(f" stdout: {result.stdout.strip()}")
|
|
print(f" stderr: {result.stderr.strip()}")
|
|
return True
|
|
else:
|
|
print(
|
|
f"{Fore.RED}{registry_name}: FAIL{Fore.RESET} - Failed to install {package}."
|
|
)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
print(f"{Fore.RED}{registry_name}: TIMEOUT{Fore.RESET} (>{timeout}s)")
|
|
except FileNotFoundError:
|
|
print(f"{Fore.RED}{registry_name}: ERROR{Fore.RESET} - uv not found")
|
|
except Exception as e:
|
|
print(f"{Fore.RED}{registry_name}: ERROR{Fore.RESET} - {e}")
|
|
|
|
if result:
|
|
if result.stdout:
|
|
print(f"{Fore.RED} stdout:{Fore.RESET} {result.stdout.strip()}")
|
|
if result.stderr:
|
|
print(f"\n{Fore.RED} stderr:{Fore.RESET} {result.stderr.strip()}")
|
|
return False
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""Parse command line arguments"""
|
|
parser = argparse.ArgumentParser(
|
|
description="Test uv add command against multiple registries",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
parser.add_argument(
|
|
"--all",
|
|
action="store_true",
|
|
help="fail if any known registry was not tested",
|
|
)
|
|
parser.add_argument(
|
|
"--uv",
|
|
type=str,
|
|
help="specify a path to the uv binary (default: uv command)",
|
|
)
|
|
parser.add_argument(
|
|
"--timeout",
|
|
type=int,
|
|
default=os.environ.get("UV_TEST_TIMEOUT", DEFAULT_TIMEOUT),
|
|
help=f"timeout in seconds for each test (default: {DEFAULT_TIMEOUT} or UV_TEST_TIMEOUT)",
|
|
)
|
|
parser.add_argument(
|
|
"-v",
|
|
"--verbose",
|
|
action="count",
|
|
default=0,
|
|
help="increase verbosity (-v for debug, -vv for trace)",
|
|
)
|
|
parser.add_argument(
|
|
"--use-op",
|
|
action="store_true",
|
|
help="use 1Password CLI to fetch registry credentials from the specified vault",
|
|
)
|
|
parser.add_argument(
|
|
"--op-vault",
|
|
type=str,
|
|
default="RegistryTests",
|
|
help="name of the 1Password vault to use (default: RegistryTests)",
|
|
)
|
|
parser.add_argument(
|
|
"--required-python",
|
|
type=str,
|
|
default="3.12",
|
|
help="minimum Python version for tests (default: 3.12)",
|
|
)
|
|
parser.add_argument("--color", choices=["always", "auto", "never"], default="auto")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
env = os.environ.copy()
|
|
|
|
if args.color == "always":
|
|
initialize_colorama(force_color=True)
|
|
elif args.color == "never":
|
|
initialize_colorama(force_color=False)
|
|
else:
|
|
initialize_colorama(force_color=sys.stdout.isatty())
|
|
|
|
# If using 1Password, fetch credentials from the vault
|
|
if args.use_op:
|
|
print(f"Fetching credentials from 1Password vault '{args.op_vault}'...")
|
|
try:
|
|
env = fetch_op_items(args.op_vault, env)
|
|
except Exception as e:
|
|
print(f"{Fore.RED}Error accessing 1Password: {e}{Fore.RESET}")
|
|
print(
|
|
f"{Fore.YELLOW}Hint: If you're not authenticated, run 'op signin' first.{Fore.RESET}"
|
|
)
|
|
sys.exit(1)
|
|
|
|
if args.uv:
|
|
# We change the working directory for the subprocess calls, so we have to
|
|
# absolutize the path.
|
|
uv = Path.cwd().joinpath(args.uv)
|
|
else:
|
|
subprocess.run(["cargo", "build"])
|
|
executable_suffix = ".exe" if os.name == "nt" else ""
|
|
uv = cwd.parent.joinpath(f"target/debug/uv{executable_suffix}")
|
|
|
|
passed = []
|
|
failed = []
|
|
skipped = []
|
|
untested_registries = set(KNOWN_REGISTRIES)
|
|
|
|
print("Running tests...")
|
|
for registry_name, registry_url in get_registries(env).items():
|
|
print("----------------")
|
|
|
|
token = env.get(f"UV_TEST_{registry_name.upper()}_TOKEN")
|
|
if not token:
|
|
if args.all:
|
|
print(
|
|
f"{Fore.RED}{registry_name}: UV_TEST_{registry_name.upper()}_TOKEN contained no token. Required by --all"
|
|
)
|
|
failed.append(registry_name)
|
|
else:
|
|
print(
|
|
f"{Fore.YELLOW}{registry_name}: UV_TEST_{registry_name.upper()}_TOKEN contained no token. Skipping test"
|
|
)
|
|
skipped.append(registry_name)
|
|
continue
|
|
|
|
# The private package we will test installing
|
|
package = env.get(f"UV_TEST_{registry_name.upper()}_PKG", DEFAULT_PKG_NAME)
|
|
username = env.get(f"UV_TEST_{registry_name.upper()}_USERNAME", "__token__")
|
|
|
|
if run_test(
|
|
env,
|
|
uv,
|
|
registry_name,
|
|
registry_url,
|
|
package,
|
|
username,
|
|
token,
|
|
args.verbose,
|
|
args.timeout,
|
|
args.required_python,
|
|
):
|
|
passed.append(registry_name)
|
|
else:
|
|
failed.append(registry_name)
|
|
|
|
untested_registries.remove(registry_name)
|
|
|
|
total = len(passed) + len(failed)
|
|
|
|
print("----------------")
|
|
if passed:
|
|
print(f"\n{Fore.GREEN}Passed:")
|
|
for registry_name in passed:
|
|
print(f" * {registry_name}")
|
|
if failed:
|
|
print(f"\n{Fore.RED}Failed:")
|
|
for registry_name in failed:
|
|
print(f" * {registry_name}")
|
|
if skipped:
|
|
print(f"\n{Fore.YELLOW}Skipped:")
|
|
for registry_name in skipped:
|
|
print(f" * {registry_name}")
|
|
|
|
print(f"\nResults: {len(passed)}/{total} tests passed, {len(skipped)} skipped")
|
|
|
|
if args.all and len(untested_registries) > 0:
|
|
print(
|
|
f"\n{Fore.RED}Failed to test all known registries (requested via --all).{Fore.RESET}\nMissing:"
|
|
)
|
|
for registry_name in untested_registries:
|
|
print(f" * {registry_name}")
|
|
print("You must use the exact registry name as listed here")
|
|
sys.exit(1)
|
|
|
|
if total == 0:
|
|
print("\nNo tests were run - have you defined at least one registry?")
|
|
print(" * UV_TEST_<registry_name>_URL")
|
|
print(" * UV_TEST_<registry_name>_TOKEN")
|
|
print(
|
|
" * UV_TEST_<registry_name>_PKG (the private package to test installing)"
|
|
)
|
|
print(' * UV_TEST_<registry_name>_USERNAME (defaults to "__token__")')
|
|
sys.exit(1)
|
|
|
|
sys.exit(0 if len(failed) == 0 else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|