Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: read

jobs:
release:
name: Build and create the GitHub release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check that the tag matches the package version
run: |
version=$(python -c "import re, pathlib; print(re.search(r'__version__ = \"(.+?)\"', pathlib.Path('pylogkit/_version.py').read_text()).group(1))")
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
echo "Tag $GITHUB_REF_NAME does not match version $version" >&2
exit 1
fi
- run: python -m pip install build twine
- run: python -m build
- run: twine check dist/*
- name: Create the GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes --title "$GITHUB_REF_NAME"
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- The package is fully type annotated, ships a PEP 561 `py.typed` marker and
is checked with `mypy --strict` in CI. A CI step checks that the wheel
contains the marker (#19).
- The version is single-sourced in `pylogkit/_version.py` and exposed as
`pylogkit.__version__`. A release workflow builds and checks the
distribution when a `v*` tag is pushed, verifies that the tag matches the
version, and creates a GitHub release. The process is documented in
`docs/releasing.md`. Publishing to PyPI is deliberately left out until the
distribution name is settled (#20).

### Changed

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ mypy # strict type checking (the package ships py.typed)
The same checks run in GitHub Actions on every push and pull request, across Python 3.10 to 3.14,
with and without the optional extras, and with python-json-logger 2.x as well as the latest release.

See [CHANGELOG.md](CHANGELOG.md) for what has changed, and the
See [CHANGELOG.md](CHANGELOG.md) for what has changed, [docs/releasing.md](docs/releasing.md) for the
release process, and the
[issue tracker](https://github.com/markbac/py-logkit/issues) for planned work.

## Licence
Expand Down
29 changes: 29 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Releasing

Releases follow [Semantic Versioning](https://semver.org/). The version lives in one place,
`pylogkit/_version.py`, and the build reads it from there.

## Cutting a release

1. Move the entries under `## Unreleased` in `CHANGELOG.md` into a new `## X.Y.Z - YYYY-MM-DD`
heading, and leave an empty `## Unreleased` section above it.
1. Set `__version__` in `pylogkit/_version.py` to `X.Y.Z`.
1. Open a pull request with those two changes and merge it once CI is green.
1. Tag the merge commit and push the tag:

```bash
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin vX.Y.Z
```

Pushing a `v*` tag runs `.github/workflows/release.yml`. It checks that the tag matches
`__version__`, builds the sdist and wheel, runs `twine check`, and attaches both to a GitHub release
whose notes are generated from the merged pull requests.

## Publishing to PyPI

The workflow does not publish to PyPI yet. The name `pylogkit` is already used there by an
unrelated project (see issue #44), so a distribution name has to be chosen first. Once it is,
add a publish job that uses
[trusted publishing](https://docs.pypi.org/trusted-publishers/) with the `id-token: write`
permission, so that no API token is stored in the repository.
2 changes: 2 additions & 0 deletions pylogkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
logger.info("Hello, logging!")
"""

from pylogkit._version import __version__
from pylogkit.log_setup import (
ContextualLoggerAdapter,
clear_log_context,
Expand All @@ -22,6 +23,7 @@
)

__all__ = [
"__version__",
"ContextualLoggerAdapter",
"clear_log_context",
"get_log_context",
Expand Down
7 changes: 7 additions & 0 deletions pylogkit/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Single source of truth for the package version.

The build reads ``__version__`` from this file (see ``pyproject.toml``), so it
can be read without importing the package and its dependencies.
"""

__version__ = "0.1.0"
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pylogkit"
version = "0.1.0"
dynamic = ["version"]
description = "A structured and colourised logging toolkit for Python applications"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -38,6 +38,9 @@ Homepage = "https://github.com/markbac/py-logkit"
Issues = "https://github.com/markbac/py-logkit/issues"
Changelog = "https://github.com/markbac/py-logkit/blob/main/CHANGELOG.md"

[tool.setuptools.dynamic]
version = { attr = "pylogkit._version.__version__" }

[tool.setuptools.package-data]
pylogkit = ["py.typed"]

Expand Down
26 changes: 26 additions & 0 deletions tests/test_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def test_public_api_is_annotated():
missing = []
for name in pylogkit.__all__:
obj = getattr(pylogkit, name)
if not callable(obj):
continue
members = [obj]
if inspect.isclass(obj):
members = [
Expand All @@ -56,3 +58,27 @@ def test_public_api_is_annotated():
missing.append(f"{name}:{getattr(func, '__qualname__', func)}")

assert not missing, missing


def test_version_is_single_sourced():
"""``pyproject.toml`` reads the version from ``_version.py`` instead of repeating it."""
import re

import pylogkit
from pylogkit import _version

text = (ROOT / "pyproject.toml").read_text(encoding="utf-8")

assert 'dynamic = ["version"]' in text
assert "pylogkit._version.__version__" in text
assert not re.search(r'^version = "', text, re.M)
assert pylogkit.__version__ == _version.__version__
assert re.fullmatch(r"\d+\.\d+\.\d+", _version.__version__)


def test_release_workflow_checks_tag_against_version():
"""The release workflow exists and refuses a tag that disagrees with the version."""
workflow = (ROOT / ".github" / "workflows" / "release.yml").read_text(encoding="utf-8")

assert 'tags: ["v*"]' in workflow
assert "_version.py" in workflow
Loading