Skip to content

Releasing

A release is a tag push. .github/workflows/python-publish.yml verifies the tag against docxtpl.__version__, runs the tests, builds with uv, attaches the distributions to the GitHub release and publishes them to PyPI over trusted publishing — no API token exists anywhere, and none can leak.

One-time setup on PyPI

docxtpl-ng has never been published, so the publisher is registered before the project exists, as a pending publisher: pypi.org/manage/account/publishing.

Field Value
PyPI Project Name docxtpl-ng
Owner toxicphreAK
Repository name docxtpl-ng
Workflow name python-publish.yml
Environment name pypi

The workflow name is the file name, not the name: inside the file — PyPI matches on .github/workflows/<this>. The environment name must match the environment: key of the deploy job exactly; an empty box there would let any workflow run in the repository publish, which is the thing the environment is there to prevent.

The first successful publish converts the pending publisher into a normal one and creates the project.

What is already in place

  • the pypi environment exists on the repository, and its deployment policy restricts it to tags matching v*. The workflow triggers on the same pattern, so a stray tag can neither start a publish nor reach the environment if the trigger is ever widened;
  • GitHub Pages is enabled and serving the documentation from the Documentation workflow;
  • CI runs the tests on Python 3.9 through 3.14, plus ruff and pyright, and validates rendered documents against the ISO/IEC 29500 schemas.

Cutting a release

  1. Update HISTORY.md: give the version a heading and a date.
  2. Set __version__ in src/docxtpl/__init__.py. The tag must match it, with an optional leading v — the workflow fails the build if it does not.
  3. Check the distributions build and describe themselves correctly:

    make check          # -- uv build, then twine check --
    
  4. Commit, then tag and push:

    git tag v0.1.0
    git push origin v0.1.0
    

Pushing the tag is what publishes. There is no upload target in the Makefile on purpose: a release should be a thing that happened to the repository, not a thing that happened on somebody's laptop.

Version numbers

PEP 440, and the distinction that matters is what pip install docxtpl-ng gives someone by default:

  • 0.1.0 — a normal release. Installed by default;
  • 0.1.0a1, 0.1.0rc1 — a pre-release. Not installed unless the user asks for it with --pre or pins it exactly.

While the API is settling, the 0. major is doing the work of saying so, and the Development Status :: 3 - Alpha classifier says it again on the project page.

Dependency floors

python-docx-ng>=2.0.0 is a floor the test suite is actually run against, not a guess — see the note in pyproject.toml. Two things follow:

  • raising the floor is a deliberate act: it needs a reason and a test run against the new floor;
  • the floor must not name a pre-release. A specifier like >=2.0.0.dev0 opts that dependency into pre-releases for everyone who installs this package, so a future 2.2.0.dev1 would be picked up by an ordinary pip install.