Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4db4bb1
docs(specs): add release, changelog, and SPDX design
sbgaia Jul 29, 2026
1633f49
docs(plans): add release, changelog, and SPDX implementation plan
sbgaia Jul 29, 2026
8100d77
docs(plans): require new scripts to be committed executable
sbgaia Jul 29, 2026
85c3e3b
feat(license): auto-insert and verify SPDX headers
sbgaia Jul 29, 2026
4853400
docs(plans): fix the license source and reuse override in the plan
sbgaia Jul 29, 2026
ef092a4
fix(license): correct root license text and reuse override for plan docs
sbgaia Jul 29, 2026
1690ab7
chore(style): apply mdformat to spec and plan docs
sbgaia Jul 29, 2026
d994605
feat(changelog): generate CHANGELOG.md with git-cliff
sbgaia Jul 29, 2026
b318ef9
fix(plans): fix changelog generation for the very first release
sbgaia Jul 29, 2026
a374951
docs(changelog): document the --prepend duplicate-header caveat
sbgaia Jul 29, 2026
a0422df
feat(release): add release preparation script
sbgaia Jul 29, 2026
06bab37
ci(release): publish GitHub Releases from version tags
sbgaia Jul 29, 2026
a8292bc
feat(bootstrap): substitute SPDX and release metadata
sbgaia Jul 29, 2026
bb1191b
fix(docs): exclude development plans from the sphinx build
sbgaia Jul 29, 2026
e305fc9
test(template): assert release and SPDX bootstrap substitution
sbgaia Jul 29, 2026
a211f2d
docs: document the commit, license header, and release workflow
sbgaia Jul 30, 2026
e6a4053
ci: fix CI errors
sbgaia Aug 5, 2026
ccaa380
feat(dependabot): update package ecosystem from pip to uv
sbgaia Aug 5, 2026
6d3bec7
fix(dependabot): fix time format in configurationpre
sbgaia Aug 5, 2026
e483e23
feat(pre-commit): add check for unquoted YAML clock values and update…
sbgaia Aug 5, 2026
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
11 changes: 7 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
version: 2
updates:
- package-ecosystem: pip
# The `uv` ecosystem understands pyproject.toml *and* uv.lock. The `pip`
# ecosystem does not update uv.lock, so transitive pins there (which CI
# installs, because every job runs with --locked) would never be bumped.
- package-ecosystem: uv
directory: /
schedule:
interval: weekly
day: monday
time: 06:00
time: '06:00'
timezone: Europe/Rome
open-pull-requests-limit: 10
commit-message:
Expand All @@ -21,7 +24,7 @@ updates:
schedule:
interval: weekly
day: monday
time: 06:30
time: '06:30'
timezone: Europe/Rome
open-pull-requests-limit: 5
commit-message:
Expand All @@ -37,7 +40,7 @@ updates:
schedule:
interval: weekly
day: monday
time: 07:00
time: '07:00'
timezone: Europe/Rome
open-pull-requests-limit: 5
commit-message:
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Release

on:
push:
tags:
- v*

permissions:
contents: read

env:
PYTHON_VERSION: '3.10'

jobs:
release:
name: Publish GitHub Release
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: ./.github/actions/setup-python-uv
with:
python-version: ${{ env.PYTHON_VERSION }}
# tomllib is 3.11+, and PYTHON_VERSION is 3.10 to match this template's
# floor, so the version is read with a regex instead. The pattern mirrors
# VERSION_LINE in scripts/release.py, which writes this same line.
- name: Verify tag matches the project version
run: |
tag_version="${GITHUB_REF_NAME#v}"
project_version=$(uv run --locked python -c \
"import pathlib, re; print(re.search(r'(?m)^version\s*=\s*\"([^\"]+)\"', pathlib.Path('pyproject.toml').read_text()).group(1))")
if [ "$tag_version" != "$project_version" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match pyproject.toml version $project_version"
exit 1
fi
- name: Build and verify package artifacts
run: uv run --locked --extra dev tox run -e build
- name: Extract release notes
run: |
uv run --locked --extra dev python scripts/gen_changelog.py \
--current --strip header -o release-notes.md
if [ ! -s release-notes.md ]; then
echo "::error::No changelog content found for $GITHUB_REF_NAME"
exit 1
fi
cat release-notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
body_path: release-notes.md
files: |
dist/*.whl
dist/*.tar.gz
draft: false
prerelease: false

# Publishing to PyPI is opt-in. To enable it:
# 1. Create a PyPI Trusted Publisher for this repository, pointing at
# workflow `release.yaml` and environment `pypi`.
# See https://docs.pypi.org/trusted-publishers/
# 2. Create a GitHub environment named `pypi`.
# 3. Uncomment the job below.
# No API token is needed; authentication uses OIDC.
#
# publish:
# name: Publish to PyPI
# needs: release
# runs-on: ubuntu-latest
# timeout-minutes: 15
# environment: pypi
# permissions:
# id-token: write
# steps:
# - uses: actions/checkout@v6
# - uses: ./.github/actions/setup-python-uv
# with:
# python-version: ${{ env.PYTHON_VERSION }}
# - name: Build distributions
# run: uv run --locked --extra dev tox run -e build
# - name: Publish
# uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
.ruff_cache/
.mypy_cache/
cover/

# Translations
Expand Down
33 changes: 32 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,24 @@ repos:
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.16.0
hooks:
# --preserve-quotes matters: ruamel follows YAML 1.2, where a bare 06:00
# is a string, so without it the formatter strips "redundant" quotes that
# YAML 1.1 parsers (Ruby's Psych, used by Dependabot) need in order to
# read the value as a string instead of the integer 21600.
- id: pretty-format-yaml
args: [--autofix, --indent, '2']
args: [--autofix, --indent, '2', --preserve-quotes]

# Runs after the formatter, because a bare HH:MM is a string to every
# Python YAML parser: check-yaml and JSON Schema validation both accept
# the value Dependabot rejects, so it needs a dedicated check.
- repo: local
hooks:
- id: check-yaml-sexagesimal
name: check for unquoted YAML clock values
entry: python scripts/check_yaml_sexagesimal.py
language: python
types: [yaml]

# Check for common spelling mistakes
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
Expand All @@ -58,6 +74,21 @@ repos:
- id: ruff-format
files: ^((project_name|tests|examples)/.+)?[^/]+\.(py|pyi)$

# SPDX headers: insert missing ones, then verify REUSE compliance
- repo: local
hooks:
- id: reuse-annotate
name: add SPDX headers
entry: python scripts/add_spdx_headers.py
language: python
additional_dependencies: ['reuse>=6.2,<7']
types: [python]

- repo: https://github.com/fsfe/reuse-tool
rev: v6.2.0
hooks:
- id: reuse


# Global file exclusions
exclude: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
35 changes: 32 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ Build and smoke-test the package artifacts:
uv run tox -e build
```

Preview the generated changelog:

```bash
uv run tox -e changelog
```

Prepare a release — bumps the version, regenerates the changelog, commits,
and tags:

```bash
uv run tox -e release -- 0.1.0
uv run tox -e release -- 0.1.0 --push
```

Pushing the tag triggers the `Release` workflow, which builds the
distributions and publishes a GitHub Release.

Build and smoke-test the Docker image:

```bash
Expand Down Expand Up @@ -111,6 +128,18 @@ A good pull request should include:

## Release notes

Update `CHANGELOG.md` for changes that affect generated projects, supported
Python versions, dependency management, CI behavior, documentation publishing,
or the public template workflow.
`CHANGELOG.md` is generated from commit messages by git-cliff — do not edit it
by hand. What lands in it is decided by the Conventional Commits prefix on each
commit, so write commit subjects that read as release notes on their own,
especially for changes that affect generated projects, supported Python
versions, dependency management, CI behavior, documentation publishing, or the
public template workflow.

Preview the result before opening a pull request:

```bash
uv run tox -e changelog
```

The prefix-to-section mapping and the full release procedure are documented in
the "Commit, license, and release workflow" section of `README.md`.
40 changes: 20 additions & 20 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
This is free and unencumbered software released into the public domain.
BSD 2-Clause License

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
Copyright (c) 2026 the Python Template contributors

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

For more information, please refer to <https://unlicense.org>
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 changes: 24 additions & 0 deletions LICENSES/BSD-2-Clause.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BSD 2-Clause License

Copyright (c) 2026 the Python Template contributors

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loading
Loading