mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
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:
parent
484717d42f
commit
1995d20298
29 changed files with 1892 additions and 20 deletions
1
scripts/publish/.gitignore
vendored
Normal file
1
scripts/publish/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
astral-test-*
|
179
scripts/publish/test_publish.py
Normal file
179
scripts/publish/test_publish.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue