Skip to content

Support Python 3.10 through 3.14, and publish to PyPI on release - #236

Merged
mverteuil merged 5 commits into
masterfrom
modernize-python-support
Aug 11, 2026
Merged

mverteuil merged 5 commits into
masterfrom
modernize-python-support

Conversation

@mverteuil

@mverteuil mverteuil commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Barbara could not be installed on Python 3.12 or newer at all, which blocks anyone using it to manage env configuration on a current interpreter. There is no workaround available on the consuming side — the package itself needed fixing.

What was wrong

Three independent blockers, not one:

  1. from typing.re import Pattern in barbara/variables.py. The typing.re namespace was removed in 3.12. This is the error people hit first, but fixing it alone is not enough.
  2. pyyaml resolved to 5.4.1, which publishes no 3.12+ wheels and whose sdist fails to build against Cython 3 ('build_ext' object has no attribute 'cython_sources'). The install dies here even with the import fixed. Master's widened >=5,<7 range now permits 6.x, but the lockfile still pinned 5.4.1.
  3. poetry-version was a runtime dependency, used only to supply barb --version. It works by parsing pyproject.toml, which is not present in an installed wheel, so it was already unreliable regardless of Python version.

Also fixed along the way: the build backend was poetry.masonry.api, which current poetry-core no longer provides.

Changes

Python support

  • re.Pattern instead of typing.re.Pattern.
  • barb --version now reads importlib.metadata; poetry-version dependency dropped.
  • Widened click to allow 8.x and python-dotenv to allow 1.x. Dev dependencies un-pinned from their majors, keeping the floors Dependabot established.
  • Fully re-locked. Note poetry lock --no-update was insufficient — it preserved the broken pyyaml 5.4.1 pin.
  • Python floor is now 3.10. Dropped 3.8 and 3.9 as both are past PSF end-of-life; CI matrix extended to 3.14.
  • CI poetry 1.4.1 → 1.8.3, matching the lockfile generator and post-dating 3.12 support.
  • pre-commit: flake8 6.0.0 → 7.3.0 (its pyflakes crashes on 3.12+ with module 'ast' has no attribute 'Str') and black 23.1.0 → 25.9.0 (23.1 rejects py312 and later as target versions). The black bump reformats nothing.
  • Action versions bumped. setup-python@v2 cannot reliably provision 3.13 or 3.14, and actionlint reports the v2 actions as too old to run on current runners — both would have undermined the widened matrix.

PyPI publishing

New publish.yml, triggered when a GitHub Release is published. It builds the wheel and sdist and uploads them using PyPI Trusted Publishing, so there is no long-lived API token stored as a repository secret and nothing to rotate — the job exchanges a short-lived OIDC token instead. Publishing is a separate job bound to a pypi environment so approval can be gated there, and the build job refuses to publish when the release tag disagrees with the version in pyproject.toml.

Two fixes beyond the original scope

A real bug in GitCommitVariable.generate(). It caught CalledProcessError, which covers running outside a repository, but not FileNotFoundError from the git binary being missing entirely. That is an unhandled traceback on any host without git. Now catches OSError too, with tests for both fallback paths.

The Dockerfile could not build. It used curl (absent from python:*-slim), referenced DIST_DIR in ENV without re-declaring it as an ARG after FROM, installed poetry twice, mounted a cache over the poetry install, used the deprecated --no-dev, and had ENTRYPOINT commented out. Replaced with a two-stage build that produces a wheel and installs it into a clean runtime, with git present so @@GIT_COMMIT@@ works.

Verification

  • All six CI jobs pass: 3.10, 3.11, 3.12, 3.13, 3.14 and pre-commit.
  • 29 tests pass locally on each of those versions.
  • Clean install into a fresh 3.12 venv resolves click 8.4.2 / pyyaml 6.0.3 / python-dotenv 1.2.2; barb --version and --help work; barb -z against examples/env-local.yml produces a correct env-file.
  • Docker image builds and, against a mounted repository, resolves real commit hashes for both @@GIT_COMMIT:7@@ and @@GIT_COMMIT:40@@.
  • publish.yml passes actionlint and yamllint; the tag guard was exercised locally against matching, v-prefixed and mismatched tags.

Before the first release

Trusted Publishing needs one-time setup that cannot be done from this repo. Until both steps are done, the publish workflow will fail at the upload step.

  1. On PyPI, under the barbara project, go to Publishing and add a GitHub publisher: owner mathsucks, repository barbara, workflow filename publish.yml, environment pypi.
  2. In this repository's Settings → Environments, create an environment named pypi. Add a required reviewer if you want releases to pause for approval.

Then publishing a GitHub Release tagged v2.4.0 (or 2.4.0) ships it. Nothing needs to go into repository secrets.

Barbara could not be installed on Python 3.12+ at all, which blocked
setting up projects that depend on it for env configuration.

Three separate blockers:

- `from typing.re import Pattern` in variables.py. The `typing.re`
  namespace was removed in 3.12; use `re.Pattern` directly.
- `pyyaml = "==5.*"` resolved to 5.4.1, which has no 3.12+ wheels and
  whose sdist fails to build against Cython 3. Widened to allow 6.x and
  re-locked, since `poetry lock --no-update` preserved the broken pin.
- `poetry-version` was a runtime dependency used only to supply
  `barb --version`. It works by parsing pyproject.toml, which is absent
  from an installed wheel, so it was already unreliable. Replaced with
  importlib.metadata and dropped the dependency.

Also widened click to allow 8.x, un-pinned the dev dependencies from
their majors, and fixed the build backend: `poetry.masonry.api` is no
longer provided by current poetry-core.

Dropped 3.8 and 3.9, both past PSF end-of-life, and extended the CI
matrix to 3.14. Poetry in CI moves to 1.8.3 to match the lockfile
generator. flake8 and black hooks are bumped because flake8 6.0.0
crashes on 3.12+ (`module 'ast' has no attribute 'Str'`) and black
23.1.0 rejects py312 and later as target versions.
GitCommitVariable.generate() caught CalledProcessError, which covers
running outside a repository, but not FileNotFoundError from the git
binary being absent entirely. That surfaced as an unhandled traceback
in the Docker image, and would do the same on any host without git.

Catch OSError as well and cover both fallback paths with tests.
The in-progress version on this branch could not build:

- `curl | python -` to install poetry, but curl is not present in
  python:*-slim, so the layer failed outright.
- DIST_DIR was referenced in ENV without being re-declared as an ARG
  after FROM, so PYTHONPATH resolved against an empty string.
- Poetry was installed twice, once via pip and once via the installer.
- The cache mount targeted POETRY_HOME, mounting over the poetry
  install itself.
- `poetry install --no-dev` is deprecated in poetry 1.8.
- ENTRYPOINT was commented out, so the image did nothing when run.

Replaced with a two-stage build: the builder produces a wheel, the
runtime installs it and keeps only the result. git is installed in the
runtime stage because the @@GIT_COMMIT@@ template variable shells out
to it.

Verified: builds clean, `barb --version` and `--help` work, and
generating an env-file against a mounted repository resolves real
commit hashes for both @@GIT_COMMIT:7@@ and @@GIT_COMMIT:40@@.
Adds a release-triggered workflow that builds the wheel and sdist and
uploads them to PyPI. Authentication uses PyPI Trusted Publishing, so
there is no long-lived API token stored as a repository secret and
nothing to rotate; the job exchanges a short-lived OIDC token instead,
which is why the publish job requests `id-token: write`.

Publishing is split into a separate job bound to a `pypi` environment,
so release approval and the ability to mint the OIDC token can be
gated there rather than being available to the build job.

The build job refuses to publish when the release tag disagrees with
the version in pyproject.toml, so a mistagged release fails loudly
instead of uploading a surprise version.

Also bumps the action versions in the existing workflows. setup-python
v2 cannot reliably provision 3.13 or 3.14, and actionlint reports the
v2 actions as too old to run on current runners, both of which would
undermine the matrix this branch just widened.
@mverteuil
mverteuil force-pushed the modernize-python-support branch from 7ea8b0e to f4e4eea Compare August 11, 2026 18:29
@mverteuil mverteuil changed the title Support Python 3.10 through 3.14 Support Python 3.10 through 3.14, and publish to PyPI on release Aug 11, 2026
@mverteuil
mverteuil merged commit 2a03683 into master Aug 11, 2026
6 checks passed
@mverteuil
mverteuil deleted the modernize-python-support branch August 11, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant