Skip to content

Commit

Permalink
A complete, self-contained example for trusted publishing with uv
Browse files Browse the repository at this point in the history
  • Loading branch information
konstin committed Oct 28, 2024
0 parents commit 24971e1
Show file tree
Hide file tree
Showing 13 changed files with 457 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Basic CI setup: Lint with ruff, run tests with pytest
name: Test

on:
pull_request:
push:
branches:
- main

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- name: Ruff lint
run: uv run ruff check .
- name: Ruff format
run: uv run ruff format --diff .
# This isn't a general Python lint, this style is just used in this repository
- name: Prettier format
run: npx prettier --prose-wrap always --check "**/*.md"

test:
name: Run tests
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv run pytest
177 changes: 177 additions & 0 deletions .github/workflows/errors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Test the error messages when the trusted publishing configuration is incorrect
name: Release wrong name

on:
workflow_dispatch:
inputs:
ref:
description: "The commit SHA, tag, or branch of uv. Uses the last release if not specified."
default: ""
type: string

jobs:
get-binary-linux:
runs-on: ubuntu-latest
name: Get binary
steps:
- if: ${{ inputs.ref }}
uses: actions/checkout@v4
with:
repository: "astral-sh/uv"
ref: ${{ inputs.ref }}

- if: ${{ inputs.ref }}
uses: rui314/setup-mold@v1
- if: ${{ inputs.ref }}
name: Setup musl
run: |
sudo apt-get install musl-tools
rustup target add x86_64-unknown-linux-musl
- if: ${{ inputs.ref }}
uses: Swatinem/rust-cache@v2
- if: ${{ inputs.ref }}
name: Build uv
run: cargo build --target x86_64-unknown-linux-musl
- if: ${{ inputs.ref }}
name: Strip uv
run: strip ./target/x86_64-unknown-linux-musl/debug/uv

- if: ${{ inputs.ref }}
name: Upload uv
uses: actions/upload-artifact@v4
with:
name: uv
path: ./target/x86_64-unknown-linux-musl/debug/uv

- if: ${{ !inputs.ref }}
run: |
wget https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-musl.tar.gz
tar xf uv-x86_64-unknown-linux-musl.tar.gz
- if: ${{ !inputs.ref }}
name: Upload uv
uses: actions/upload-artifact@v4
with:
name: uv
path: ./uv-x86_64-unknown-linux-musl/uv

pypi-wrong-name:
name: Publish wrong name
needs: get-binary-linux
runs-on: ubuntu-latest
# Environment and permissions trusted publishing.
environment:
# Create this environment in the GitHub repository under Settings -> Environments
name: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Download uv
uses: actions/download-artifact@v4
with:
name: uv
- name: Prepare uv
run: chmod +x ./uv

- run: ./uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.tar.gz tests/smoke_test.py
- run: ./uv publish --trusted-publishing always
# The part below with testpypi only because it's a demo repo, remove the next two lines for production usage
env:
UV_PUBLISH_URL: https://test.pypi.org/legacy/

# Fails because the workflow name is wrong, but without `--trusted-publishing always`
pypi-wrong-name-no-trusted-publishing:
name: Publish wrong name alt
needs: get-binary-linux
runs-on: ubuntu-latest
# Environment and permissions trusted publishing.
environment:
# Create this environment in the GitHub repository under Settings -> Environments
name: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Download uv
uses: actions/download-artifact@v4
with:
name: uv
- name: Prepare uv
run: chmod +x ./uv

- uses: astral-sh/setup-uv@v3
- run: ./uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.tar.gz tests/smoke_test.py
- run: ./uv publish
# The part below with testpypi only because it's a demo repo, remove the next two lines for production usage
env:
UV_PUBLISH_URL: https://test.pypi.org/legacy/

# Fails because the permission section is missing
pypi-missing-permissions:
name: Publish missing permissions
needs: get-binary-linux
runs-on: ubuntu-latest
# Environment trusted publishing.
environment:
# Create this environment in the GitHub repository under Settings -> Environments
name: release
# Here the permission section is skipped
steps:
- uses: actions/checkout@v4
- name: Download uv
uses: actions/download-artifact@v4
with:
name: uv
- name: Prepare uv
run: chmod +x ./uv

- run: ./uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.tar.gz tests/smoke_test.py
- run: ./uv publish --trusted-publishing always
# The part below with testpypi only because it's a demo repo, remove the next two lines for production usage
env:
UV_PUBLISH_URL: https://test.pypi.org/legacy/

# Fails because the environment section is missing
pypi-missing-environment:
name: Publish missing environment
needs: get-binary-linux
runs-on: ubuntu-latest
# Here the environment section is skipped
# Permissions trusted publishing.
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Download uv
uses: actions/download-artifact@v4
with:
name: uv
- name: Prepare uv
run: chmod +x ./uv

- uses: astral-sh/setup-uv@v3
- run: ./uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: ./uv run --isolated --no-project -p 3.13 --with dist/*.tar.gz tests/smoke_test.py
- run: ./uv publish --trusted-publishing always
# The part below with testpypi only because it's a demo repo, remove the next two lines for production usage
env:
UV_PUBLISH_URL: https://test.pypi.org/legacy/
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release

on:
push:
tags:
# Publish on any tag starting with a `v`, e.g. v1.2.3
- v*

jobs:
pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
# Environment and permissions trusted publishing.
environment:
# Create this environment in the GitHub repository under Settings -> Environments
name: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: uv run --isolated --no-project -p 3.13 --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: uv run --isolated --no-project -p 3.13 --with dist/*.tar.gz tests/smoke_test.py
- run: uv publish --trusted-publishing always
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# uv trusted publishing examples

Trusted publishing allows uploading package from GitHub Action to PyPI without
manually setting a secret token. Instead, you specify on PyPI a GitHub Actions
workflow that is allowed to publish the package.

This repository contains a full, self-contained example for trusted publishing
with uv. The release workflow can be found in
[.github/workflows/release.yml](.github/workflows/release.yml). On PyPI, the
matching configuration is set under
`https://pypi.org/manage/project/<package-name>/settings/publishing/`:

![Screenshot from PyPI.
Manage current publishers.
Publisher: GitHub.
Details:
Repository: astral-sh/trusted-publishing-examples
Workflow: release.yml
Environment name: release](data/trusted-publishing-config-pypi.png)

You can find the published package at
https://pypi.org/project/trusted-publishing-examples/.

[.github/workflows/ci.yml](.github/workflows/ci.yml) is a minimal test and lint
workflow for a Python package, while
[.github/workflows/errors.yml](.github/workflows/errors.yml) is for testing uv
itself only.

## Documentation

- uv's side: https://docs.astral.sh/uv/guides/publish/
- PyPI's side: https://docs.pypi.org/trusted-publishers/
Binary file added data/trusted-publishing-config-pypi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "trusted-publishing-examples"
version = "0.1.2"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "konstin", email = "[email protected]" }
]
requires-python = ">=3.13"
dependencies = [
"numpy>=2.1.2",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = [
"pytest>=8.3.3",
"ruff>=0.7.1",
]
7 changes: 7 additions & 0 deletions src/trusted_publishing_examples/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import numpy


def hello(n: int) -> str:
"""Greet the sum from 0 to n (exclusive end)."""
sum_n = numpy.arange(n).sum()
return f"Hello {sum_n}!"
Empty file.
12 changes: 12 additions & 0 deletions tests/smoke_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Check that basic features work.
Catch cases where e.g. files are missing so the import doesn't work. It is
recommended to check that e.g. assets are included."""

from trusted_publishing_examples import hello

message = hello(101)
if message == "Hello 5050!":
print("Smoke test succeeded")
else:
raise RuntimeError(message)
7 changes: 7 additions & 0 deletions tests/test_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from trusted_publishing_examples import hello


def test_hello():
assert hello(0) == "Hello 0!"
assert hello(1) == "Hello 0!"
assert hello(1000) == "Hello 499500!"
Loading

0 comments on commit 24971e1

Please sign in to comment.