Add uv publish: Basic upload with username/password or keyring (#7475)

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
konsti 2024-09-24 17:33:06 +02:00 committed by GitHub
parent 484717d42f
commit 1995d20298
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1892 additions and 20 deletions

1
scripts/publish/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
astral-test-*

View file

@ -0,0 +1,179 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "httpx>=0.27,<0.28",
# "packaging>=24.1,<25",
# ]
# ///
"""
Test `uv publish` by uploading a new version of astral-test-<test case> to testpypi,
authenticating by one of various options.
# Setup
**astral-test-token**
Set the `UV_TEST_PUBLISH_TOKEN` environment variables.
**astral-test-password**
Set the `UV_TEST_PUBLISH_PASSWORD` environment variable.
This project also uses token authentication since it's the only thing that PyPI
supports, but they both CLI options.
TODO(konsti): Add an index for testing that supports username/password.
**astral-test-keyring**
```console
uv pip install keyring
keyring set https://test.pypi.org/legacy/?astral-test-keyring __token__
```
The query parameter a horrible hack stolen from
https://github.com/pypa/twine/issues/565#issue-555219267
to prevent the other projects from implicitly using the same credentials.
"""
import os
import re
from argparse import ArgumentParser
from pathlib import Path
from shutil import rmtree
from subprocess import check_call
import httpx
from packaging.utils import parse_sdist_filename, parse_wheel_filename
cwd = Path(__file__).parent
project_urls = {
"astral-test-token": "https://test.pypi.org/simple/astral-test-token/",
"astral-test-password": "https://test.pypi.org/simple/astral-test-password/",
"astral-test-keyring": "https://test.pypi.org/simple/astral-test-keyring/",
"astral-test-gitlab-pat": "https://gitlab.com/api/v4/projects/61853105/packages/pypi/simple/astral-test-gitlab-pat",
}
def get_new_version(project_name: str) -> str:
"""Return the next free path version on pypi"""
data = httpx.get(project_urls[project_name]).text
versions = set()
for filename in list(m.group(1) for m in re.finditer(">([^<]+)</a>", data)):
if filename.endswith(".whl"):
[_name, version, _build, _tags] = parse_wheel_filename(filename)
else:
[_name, version] = parse_sdist_filename(filename)
versions.add(version)
max_version = max(versions)
# Bump the path version to obtain an empty version
release = list(max_version.release)
release[-1] += 1
return ".".join(str(i) for i in release)
def create_project(project_name: str, uv: Path):
if cwd.joinpath(project_name).exists():
rmtree(cwd.joinpath(project_name))
check_call([uv, "init", "--lib", project_name], cwd=cwd)
pyproject_toml = cwd.joinpath(project_name).joinpath("pyproject.toml")
# Set to an unclaimed version
toml = pyproject_toml.read_text()
new_version = get_new_version(project_name)
toml = re.sub('version = ".*"', f'version = "{new_version}"', toml)
pyproject_toml.write_text(toml)
def publish_project(project_name: str, uv: Path):
# Create the project
create_project(project_name, uv)
# Build the project
check_call([uv, "build"], cwd=cwd.joinpath(project_name))
# Upload the project
if project_name == "astral-test-token":
env = os.environ.copy()
env["UV_PUBLISH_TOKEN"] = os.environ["UV_TEST_PUBLISH_TOKEN"]
check_call(
[
uv,
"publish",
"--publish-url",
"https://test.pypi.org/legacy/",
],
cwd=cwd.joinpath(project_name),
env=env,
)
elif project_name == "astral-test-password":
env = os.environ.copy()
env["UV_PUBLISH_PASSWORD"] = os.environ["UV_TEST_PUBLISH_PASSWORD"]
check_call(
[
uv,
"publish",
"--publish-url",
"https://test.pypi.org/legacy/",
"--username",
"__token__",
],
cwd=cwd.joinpath(project_name),
env=env,
)
elif project_name == "astral-test-keyring":
check_call(
[
uv,
"publish",
"--publish-url",
"https://test.pypi.org/legacy/?astral-test-keyring",
"--username",
"__token__",
"--keyring-provider",
"subprocess",
],
cwd=cwd.joinpath(project_name),
)
elif project_name == "astral-test-gitlab-pat":
env = os.environ.copy()
env["UV_PUBLISH_PASSWORD"] = os.environ["UV_TEST_PUBLISH_GITLAB_PAT"]
check_call(
[
uv,
"publish",
"--publish-url",
"https://gitlab.com/api/v4/projects/61853105/packages/pypi",
"--username",
"astral-test-gitlab-pat",
],
cwd=cwd.joinpath(project_name),
env=env,
)
else:
raise ValueError(f"Unknown project name: {project_name}")
def main():
parser = ArgumentParser()
parser.add_argument("projects", choices=list(project_urls) + ["all"], nargs="+")
parser.add_argument("--uv")
args = parser.parse_args()
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:
check_call(["cargo", "build"])
executable_suffix = ".exe" if os.name == "nt" else ""
uv = cwd.parent.parent.joinpath(f"target/debug/uv{executable_suffix}")
if args.projects == ["all"]:
projects = list(project_urls)
else:
projects = args.projects
for project_name in projects:
publish_project(project_name, uv)
if __name__ == "__main__":
main()