You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Generated packages now auto-publish to PyPI on `vX.Y.Z` tag pushes via OIDC Trusted
Publishing — no API tokens stored in the repo. The new `cd.yaml` workflow defaults to
`contents: read`; only the `publish` job holds `id-token: write`, gated on a `pypi`
GitHub environment that can optionally require manual approval.
Publishing docs are split along their two audiences:
- `docs/publishing.md` ("First publication to PyPI") covers the one-time Trusted
Publisher registration and `pypi` environment setup on the repo — the steps a
maintainer only does once per project.
- `template/docs/developer.md.jinja` gets a `## Release` section owning the recurring
flow: `hatch version` bump, tag, push, and the warning that the tag and `__about__.py`
version must agree. An `important` admonition at the top, branched on the `docs`
engine so both MyST and MkDocs render correctly, links back to the first-publication
guide.
- `docs/index.md` surfaces the capability: a `### Publish to PyPI` entry under `## Next
steps` (noting you can defer publishing, but pushing an early `0.0.1` release is the
way to claim the name), plus a bullet in the features list.
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,7 @@ Template for Python packages based on [`copier`](https://copier.readthedocs.io/e
15
15
* ⚙️ **GitHub Actions**:
16
16
* Deploy documentation to [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) or [Read the Docs](https://about.readthedocs.com/).
17
17
* Run pre-commit checks and tests on every pull request.
18
+
* Publish to [PyPI](https://pypi.org/) on `vX.Y.Z` tags via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens.
Copy file name to clipboardExpand all lines: docs/index.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,7 @@ A `copier`-based template for Python packages.
13
13
* ⚙️ **GitHub Actions**:
14
14
* Deploy documentation to [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) or [Read the Docs](https://about.readthedocs.com/).
15
15
* Run pre-commit checks and tests on every pull request.
16
+
* Publish to [PyPI](https://pypi.org/) on `vX.Y.Z` tags via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens.
16
17
17
18
## Usage
18
19
@@ -29,6 +30,13 @@ And answer the questions to generate a new Python package.
29
30
30
31
After copying the template, you might still have to do some additional configuration.
31
32
33
+
### Publish to PyPI
34
+
35
+
The generated package ships with a `cd.yaml` workflow that publishes to PyPI on `vX.Y.Z` tag pushes via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/).
36
+
See [Publishing to PyPI](publishing.md) for the one-time setup steps.
37
+
38
+
You don't have to publish right away, but if you care about the project name, push an early `0.0.1` release to claim it on PyPI before someone else does.
The generated package ships with a `.github/workflows/cd.yaml` workflow that publishes to PyPI on `vX.Y.Z` tag pushes, via [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — so you never need to store a PyPI API token as a repository secret.
4
+
5
+
Before the **first** release can succeed, you have to do the steps below once.
6
+
Subsequent releases only require a tag push; see the generated package's own developer guide for that.
7
+
8
+
## One-time setup
9
+
10
+
1.**Push the generated package to GitHub.**
11
+
2.**Create a PyPI project** (or a [pending publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) if the project doesn't exist yet).
12
+
3.**Register the Trusted Publisher.** On PyPI, go to *Your projects → Publishing* and add a new publisher with:
13
+
-**Owner**: your GitHub user or organisation
14
+
-**Repository**: the generated repository name
15
+
-**Workflow name**: `cd.yaml`
16
+
-**Environment name**: `pypi`
17
+
4.**Create the `pypi` environment** on the GitHub repository (*Settings → Environments → New environment*).
18
+
The environment name must match what you registered on PyPI, and must exist before you push your first release tag — otherwise the `publish` job will fail to start.
19
+
20
+
You can optionally add required reviewers on the `pypi` environment to gate releases on manual approval.
| `INP001` | [implicit-namespace-package](https://docs.astral.sh/ruff/rules/implicit-namespace-package/) | When tests are not part of the package, there is no need for `__init__.py` files. |
357
357
| `S101` | [assert](https://docs.astral.sh/ruff/rules/assert/) | Asserts should not be used in production environments, but are fine for tests. |
358
+
359
+
## Release
360
+
361
+
{%ifdocs == 'myst' -%}
362
+
:::{important}
363
+
Before the **first** release works, the repository has to be registered as a PyPI [Trusted Publisher](https://docs.pypi.org/trusted-publishers/) and a `pypi` GitHub environment has to exist.
364
+
See the [`python-copier` first-publication guide](https://mbercx.github.io/python-copier/publishing/) for that one-time setup — you only do it once per project.
365
+
:::
366
+
{%- endif%}
367
+
{%- ifdocs == 'mkdocs'%}
368
+
!!! important
369
+
Before the **first** release works, the repository has to be registered as a PyPI [Trusted Publisher](https://docs.pypi.org/trusted-publishers/) and a `pypi` GitHub environment has to exist.
370
+
See the [`python-copier` first-publication guide](https://mbercx.github.io/python-copier/publishing/) for that one-time setup — you only do it once per project.
371
+
{%- endif%}
372
+
373
+
Releases of `{{ package_name }}` are cut by pushing a `vX.Y.Z` tag to GitHub.
374
+
The `cd` workflow under `.github/workflows/cd.yaml` then builds an sdist and wheel with Hatch and publishes them to PyPI.
375
+
376
+
1. Bump the version in `src/{{ package_name }}/__about__.py` to the new release version.
377
+
The easiest way is:
378
+
379
+
hatch version <new-version>
380
+
381
+
Commit the bump on `main` (e.g. via a PR).
382
+
383
+
2. Tag the bump commit and push the tag:
384
+
385
+
git tag -a v<new-version> -m '🚀 Release v<new-version>'
386
+
git push origin v<new-version>
387
+
388
+
3. The `cd` workflow picks up the tag, builds the distributions, and publishes them to PyPI.
389
+
390
+
The git tag and the version in `__about__.py` must agree.
391
+
PyPI only sees the version baked into the built distribution, so a mismatch will silently publish under the wrong version (or be rejected as a duplicate of an existing release), and re-tagging after the fact is awkward.
0 commit comments