Skip to content

Commit 8dc14ae

Browse files
Publish palace-util and palace-opds to PyPI on release (PP-4076) (#3238)
## Description Two things: 1. **Dynamic versioning** for both workspace packages. Each package's `pyproject.toml` now declares `dynamic = ["version"]` and sources the version from a committed `_version.py` stub via hatchling's built-in `regex` version source. The stub carries a dev placeholder (`0.0.0.dev0`, `__commit__ = None`) so `uv build --package …` works out-of-box for local and CI builds without an explicit version override. The release workflow overwrites `_version.py` with real dunamai-computed values right before `uv build`, so the wheel metadata picks up the real version. 2. **`publish-pypi.yml` workflow** triggered by published GitHub Releases. Matrix-builds both packages in parallel, uses PyPI's [Trusted Publishing (OIDC)](https://docs.pypi.org/trusted-publishers/) flow. The workflow also pins the intra-workspace dep, so `palace-opds` always depends on the most recent palace-util. ## Motivation and Context With `palace-util` and `palace-opds` extracted as standalone packages, they should be installable from PyPI so downstream Palace services (and the wider OPDS-consuming community for `palace-opds`) can depend on them without pulling in the `palace-manager` monorepo. `palace-manager` itself is intentionally NOT published here — it needs additional work first (alembic and friends prevent a clean wheel build). ## One-time PyPI setup ~~I need to setup trusted publishing in pypi before we cut a release with this. This is a work in progress. 🚧~~ This setup is complete now. ## How Has This Been Tested? I set this up in my own fork, and published test packages to test.pypi.org. Using the same workflow they published correctly for me: - https://github.com/jonathangreen/circulation/actions/runs/24460001637 - https://test.pypi.org/project/palace-opds/ - https://test.pypi.org/project/palace-util/ ## Checklist - [x] I have updated the documentation accordingly. - [x] All new and existing tests passed.
1 parent 36f2900 commit 8dc14ae

8 files changed

Lines changed: 109 additions & 4 deletions

File tree

.github/workflows/publish-pypi.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Publish to PyPI
2+
3+
# Publishes `palace-util` and `palace-opds` when a GitHub Release is
4+
# published. Each package is built and uploaded independently via PyPI's
5+
# Trusted Publishing (OIDC) flow. https://docs.pypi.org/trusted-publishers/
6+
#
7+
# No action is needed on new releases beyond publishing a GitHub Release with
8+
# a tag that dunamai can parse as semver (e.g. `v1.0.0` or `1.0.0`).
9+
10+
on:
11+
release:
12+
types: [published]
13+
14+
env:
15+
PYTHON_VERSION: "3.12"
16+
17+
jobs:
18+
publish:
19+
name: Publish ${{ matrix.package }} to PyPI
20+
runs-on: ubuntu-24.04
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
include:
25+
- package: palace-util
26+
source_dir: packages/palace-util/src/palace/util
27+
- package: palace-opds
28+
source_dir: packages/palace-opds/src/palace/opds
29+
permissions:
30+
# Required for PyPI Trusted Publishing (OIDC).
31+
id-token: write
32+
contents: read
33+
34+
steps:
35+
- uses: actions/checkout@v6
36+
with:
37+
persist-credentials: false
38+
# dunamai needs the full tag history.
39+
fetch-depth: 0
40+
41+
- name: Install uv
42+
uses: astral-sh/setup-uv@v8.0.0
43+
with:
44+
enable-cache: true
45+
cache-dependency-glob: uv.lock
46+
python-version: ${{ env.PYTHON_VERSION }}
47+
activate-environment: true
48+
49+
- name: Install Dunamai
50+
run: uv sync --frozen --only-group ci
51+
52+
- name: Compute version metadata
53+
id: version
54+
run: |
55+
echo "version=$(dunamai from git --style semver)" >> "$GITHUB_OUTPUT"
56+
echo "commit=$(dunamai from git --format '{commit}' --full-commit)" >> "$GITHUB_OUTPUT"
57+
58+
- name: Write _version.py
59+
# hatch reads __version__ from this file via [tool.hatch.version]
60+
# regex source; overwriting it here is how the release version lands
61+
# in the wheel metadata.
62+
run: |
63+
cat > ${{ matrix.source_dir }}/_version.py <<EOF
64+
__version__ = "${{ steps.version.outputs.version }}"
65+
__commit__: str | None = "${{ steps.version.outputs.commit }}"
66+
EOF
67+
68+
- name: Pin intra-workspace dependency
69+
# uv build preserves workspace deps as bare names in Requires-Dist
70+
# (e.g. `palace-util` with no version). For PyPI we need an exact pin
71+
# so a consumer installing palace-opds resolves the matching
72+
# palace-util version.
73+
if: matrix.package == 'palace-opds'
74+
run: uv add --package palace-opds "palace-util==${{ steps.version.outputs.version }}"
75+
76+
- name: Build ${{ matrix.package }}
77+
run: uv build --package ${{ matrix.package }}
78+
79+
- name: Publish to PyPI
80+
uses: pypa/gh-action-pypi-publish@release/v1
81+
with:
82+
packages-dir: dist
83+
print-hash: true

packages/palace-opds/pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ dependencies = [
1111
"uritemplate==4.2.0",
1212
]
1313
description = "Pydantic models for OPDS 2.0, RWPM, ODL, LCP, and Palace Project extensions."
14+
dynamic = ["version"]
1415
license = "Apache-2.0"
1516
name = "palace-opds"
1617
readme = "README.md"
1718
requires-python = ">=3.12,<4"
18-
version = "0"
1919

2020
[project.urls]
2121
Homepage = "https://thepalaceproject.org"
@@ -24,6 +24,9 @@ Repository = "https://github.com/ThePalaceProject/circulation"
2424
[tool.hatch.build.targets.wheel]
2525
packages = ["src/palace"]
2626

27+
[tool.hatch.version]
28+
path = "src/palace/opds/_version.py"
29+
2730
[tool.isort]
2831
combine_as_imports = true
2932
known_first_party = ["palace.opds"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from palace.opds._version import __commit__, __version__
2+
3+
__all__ = ["__commit__", "__version__"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# WARNING!
2+
# This file is dynamically generated during the CI build process.
3+
# Any changes made here will be overwritten. Do not edit this file manually.
4+
5+
__version__ = "0.0.0.dev0"
6+
__commit__: str | None = None

packages/palace-util/pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ dependencies = [
88
"python-dateutil>=2.8,<3",
99
]
1010
description = "Shared utilities for Palace Project packages."
11+
dynamic = ["version"]
1112
license = "Apache-2.0"
1213
name = "palace-util"
1314
readme = "README.md"
1415
requires-python = ">=3.12,<4"
15-
version = "0"
1616

1717
[project.urls]
1818
Homepage = "https://thepalaceproject.org"
@@ -21,6 +21,9 @@ Repository = "https://github.com/ThePalaceProject/circulation"
2121
[tool.hatch.build.targets.wheel]
2222
packages = ["src/palace"]
2323

24+
[tool.hatch.version]
25+
path = "src/palace/util/_version.py"
26+
2427
[tool.isort]
2528
combine_as_imports = true
2629
known_first_party = ["palace.util"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from palace.util._version import __commit__, __version__
2+
3+
__all__ = ["__commit__", "__version__"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# WARNING!
2+
# This file is dynamically generated during the CI build process.
3+
# Any changes made here will be overwritten. Do not edit this file manually.
4+
5+
__version__ = "0.0.0.dev0"
6+
__commit__: str | None = None

uv.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)