Skip to content

Commit cb401bc

Browse files
authored
Add publish to pypi workflow (#7)
1 parent 4013ec6 commit cb401bc

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

.github/workflows/publish-pypi.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish to PyPi
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
publish:
8+
name: Build, verify, & upload package to PyPi
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
- name: Prepare common Python build environment
15+
uses: ./.github/actions/python-build-env-setup
16+
with:
17+
python-version: "3.13"
18+
19+
- name: Pip cache
20+
uses: actions/cache@v4
21+
with:
22+
path: ~/.cache/pip
23+
key: ${{ runner.os }}-pip
24+
restore-keys: |
25+
${{ runner.os }}-pip
26+
27+
- name: Build, verify, and upload to PyPI
28+
run: |
29+
pip install --upgrade nox
30+
nox -s build publish_pypi
31+
env:
32+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

noxfile.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from pathlib import Path
12
import nox
3+
import shutil
24

35
nox.options.stop_on_first_error = True
46
nox.options.reuse_existing_virtualenvs = False
7+
BUILD_DIRS = ["build", "dist"]
58

69
# Default sessions - all tests, but not packaging
710
nox.options.sessions = [
@@ -31,3 +34,74 @@ def lint(session):
3134
"uv", "tool", "run", "black", "--verbose", "--check", "--diff", "--color", "."
3235
)
3336
session.run("uv", "tool", "run", "ruff", "--verbose", "check", ".")
37+
38+
39+
@nox.session
40+
def watch(session):
41+
"""Build and serve live docs for editing"""
42+
session.install("-e", ".[docs]")
43+
44+
session.run("mkdocs", "serve")
45+
46+
47+
@nox.session
48+
def examples(session):
49+
session.install("-e", ".[test]")
50+
51+
options = session.posargs
52+
53+
# Because these example scripts can be long-running, output the
54+
# example's stdout so we know what's happening
55+
session.run("pytest", "--no-cov", "examples/", "-s", *options)
56+
57+
58+
@nox.session
59+
def build(session):
60+
"""Build package"""
61+
# check preexisting
62+
exist_but_should_not = [p for p in BUILD_DIRS if Path(p).is_dir()]
63+
if exist_but_should_not:
64+
session.error(
65+
f"Pre-existing {', '.join(exist_but_should_not)}. "
66+
"Run clean session and try again"
67+
)
68+
69+
session.install("build", "twine", "check-wheel-contents")
70+
71+
session.run(*"python -m build --sdist --wheel".split())
72+
session.run("check-wheel-contents", "dist")
73+
74+
75+
@nox.session
76+
def clean(session):
77+
"""Remove build directories"""
78+
to_remove = [Path(d) for d in BUILD_DIRS if Path(d).is_dir()]
79+
for p in to_remove:
80+
shutil.rmtree(p)
81+
82+
83+
@nox.session
84+
def publish_testpypi(session):
85+
"""Publish to TestPyPi using API token"""
86+
_publish(session, "testpypi")
87+
88+
89+
@nox.session
90+
def publish_pypi(session):
91+
"""Publish to PyPi using API token"""
92+
_publish(session, "pypi")
93+
94+
95+
def _publish(session, repository):
96+
missing = [p for p in BUILD_DIRS if not Path(p).is_dir()]
97+
if missing:
98+
session.error(
99+
f"Missing one or more build directories: {', '.join(missing)}. "
100+
"Run build session and try again"
101+
)
102+
103+
session.install("twine")
104+
105+
files = [str(f) for f in Path("dist").iterdir()]
106+
session.run("twine", "check", *files)
107+
session.run("twine", "upload", f"--repository={repository}", "-u=__token__", *files)

0 commit comments

Comments
 (0)