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
9 changes: 7 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ jobs:
echo 'Changes that the release commit would have carried:'
echo
echo '```'
git --no-pager diff --stat -- pyproject.toml uv.lock CHANGELOG.md
git --no-pager diff --stat -- pyproject.toml uv.lock CHANGELOG.md changelog.d
echo '```'
echo
echo 'Build artifacts:'
Expand Down Expand Up @@ -305,7 +305,12 @@ jobs:
VERSION: ${{ needs.build.outputs.version }}
run: |
set -euo pipefail
git add pyproject.toml uv.lock CHANGELOG.md
# `build` merged every fragment in changelog.d/ into CHANGELOG.md and
# deleted it, but an artifact cannot carry a deletion. This is the
# same commit, and `build` fails on any file there other than
# fragments, README.md and dotfiles, so delete the same files here.
find changelog.d -maxdepth 1 -type f -name '*.md' ! -name README.md ! -name '.*' -delete
git add pyproject.toml uv.lock CHANGELOG.md changelog.d
if git diff --cached --quiet; then
echo "Nothing to commit; releasing HEAD as it is."
else
Expand Down
15 changes: 9 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

This file records what changed for users of the library, in particular
behaviour that changed under an unchanged API. Entries are added by hand, in
the pull request that earns them, and each one opens with a bold one-line
summary: `- **What changed.** The details...`. The GitHub release notes list
those summaries and link back here, and a release fails if `## [Unreleased]`
is empty or an entry has no summary. Entries before 0.3.8 predate this format.
See [Releasing](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#releasing).
behaviour that changed under an unchanged API. The pull request that earns an
entry adds it as a fragment in `changelog.d/`, and the release merges the
fragments in here, so `## [Unreleased]` is usually empty between releases. Each
entry opens with a bold one-line summary: `- **What changed.** The details...`.
The GitHub release notes list those summaries and link back here, and a release
fails if there is nothing to release or an entry has no summary. Entries before
0.3.8 predate this format. See
[Changelog fragments](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#changelog-fragments)
and [Releasing](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#releasing).

Note that 0.3.3 was never released; 0.3.4 follows 0.3.2.

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,4 @@ Five non-abstract atomic primitives live on the base class with non-atomic fallb
- Forward references are mostly quoted annotations with `TYPE_CHECKING` imports; only a couple of modules use `from __future__ import annotations`.
- All public functions must have complete type annotations.
- Coverage threshold is 90% (enforced by `pytest-cov`).
- Changelog entries go in `changelog.d/<issue>.<section>.md` fragments (bold summary first, no leading `- `, no issue link), not in `CHANGELOG.md`; the release merges them. See `docs/DEVELOPMENT.md#changelog-fragments`.
41 changes: 41 additions & 0 deletions changelog.d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Changelog fragments

Pull requests do not edit `CHANGELOG.md`. Each one that changes behaviour, adds
public API, or fixes something a user could have hit adds a fragment here
instead, and the release merges the fragments into `CHANGELOG.md` and deletes
them. Two open pull requests therefore never conflict over the changelog.

## File name

`<issue>.<section>.md`, for example `65.added.md`, where `<section>` is one of
`added`, `changed`, `deprecated`, `removed`, `fixed`, `security`.

A second entry for the same issue and section goes in `<issue>.<section>.2.md`,
a third in `<issue>.<section>.3.md`, and so on. One file holds one entry.

## Content

One changelog entry, opening with a bold one-line summary. Leave out the
leading `- ` and the issue link: the release adds both, the link taken from the
file name. Wrap lines however you like; indent a nested list by two spaces.

```markdown
**`CacheManager.add()` stores a value only if the key is absent.** It uses the
same key prefix, JSON encoding and `default_ttl` as `set()`.
```

becomes, under `### Added`:

```markdown
- **`CacheManager.add()` stores a value only if the key is absent.** It uses the
same key prefix, JSON encoding and `default_ttl` as `set()`. ([#65](https://github.com/allen0099/FastAPI-CacheX/issues/65))
```

The release notes list only the bold summary, so write it for someone deciding
whether the release matters to them.

`tests/test_changelog_release.py` checks every fragment here, so a malformed one
(a bad name, an unknown section, no bold summary, a leading `- `, its own issue
link) fails CI in the pull request that adds it. This README and dotfiles are
not fragments. See
[Releasing](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#releasing).
19 changes: 12 additions & 7 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@ Please refer to our [Development Guide](DEVELOPMENT.md) for detailed instruction
1. Update the matching guide under `docs/` (and the README if the change belongs on the front page) when you change the interface. Only the English
pages need updating: the [Traditional Chinese translation](DEVELOPMENT.md#traditional-chinese-translation)
is allowed to lag behind them
2. Add an entry to the `## [Unreleased]` section of
[CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md) if your change alters behaviour, adds public
API, or fixes something a user could have hit. Open the entry with a bold
one-line summary, `- **What changed.** The details...`: the release notes
list only those summaries, and CI fails on an entry without one. A release
also refuses to run on an empty section, so an omission surfaces — but only
at release time, and only as "somebody forgot", never as which PR it was
2. Add a changelog fragment if your change alters behaviour, adds public
API, or fixes something a user could have hit: a file
`changelog.d/<issue>.<section>.md` (section `added`, `changed`,
`deprecated`, `removed`, `fixed` or `security`) holding the entry, opening
with a bold one-line summary, `**What changed.** The details...`, without a
leading `- ` or the issue link — the release adds both. Do not edit
[CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md)
directly; see [Changelog fragments](DEVELOPMENT.md#changelog-fragments). The
release notes list only the summaries, and CI fails on a malformed fragment.
A release also refuses to run with nothing to release, so an omission
surfaces — but only at release time, and only as "somebody forgot", never as
which PR it was
3. Update the documentation with any new dependencies, features, or changes.
New public API needs a docstring and, if it lives in a module not yet
covered, an entry under `docs/api/`; check the site with
Expand Down
69 changes: 58 additions & 11 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,18 @@ The workflow runs in this order:
written.
2. **The version.** `uv version` applies the bump or the exact version. If
`vX.Y.Z` is already tagged, locally or on the remote, the run stops here.
3. **The changelog.** `scripts/changelog_release.py` renames `## [Unreleased]`
3. **The changelog.** `scripts/changelog_release.py` merges the fragments in
`changelog.d/` into `## [Unreleased]` (see
[Changelog fragments](#changelog-fragments)), renames it
to `## [X.Y.Z] - YYYY-MM-DD`, opens a fresh empty `## [Unreleased]` above
it, rewrites the compare links at the bottom, and writes the release body:
each entry's bold summary and issue links, and a link to the full entries
on the documentation site.
4. **The build.** `uv build`. The bumped files, the release notes and
`dist/` are uploaded as one artifact, which the next two jobs download
instead of building anything again.
5. **The permanent part**, kept together at the end: commit the version bump
and the promoted changelog, push it to master, tag, push the tag by refspec,
5. **The permanent part**, kept together at the end: commit the version bump,
the promoted changelog and the removal of the merged fragments, push it to master, tag, push the tag by refspec,
create the GitHub release from the promoted section, publish to PyPI.

Steps 1–4 are the `build` job, which installs every dev dependency and so gets
Expand All @@ -342,8 +344,9 @@ of the release path was a release. A dry run may be dispatched on any branch,
which is how a change to the workflow itself is rehearsed before it is merged.

A dry run answers two questions, and the job summary reports both: whether the
bump and the promotion actually landed in `pyproject.toml`, `uv.lock` and
`CHANGELOG.md` (shown as a `git diff --stat`, staged by nothing), and what
bump and the promotion actually landed in `pyproject.toml`, `uv.lock`,
`CHANGELOG.md` and `changelog.d/` (shown as a `git diff --stat`, staged by
nothing), and what
would have been published. The release notes, the built `dist/` and the bumped
files are attached to the run as an artifact, because the notes are markdown and reading them in
the job summary renders them a second time — which is not what the release page
Expand All @@ -361,9 +364,9 @@ different path, not of this one.
`CHANGELOG.md` used to be maintained entirely by hand and nothing enforced it:
the release notes came from `git log --pretty=format:"- %s (%h)"`, so a release
happened whether or not anyone had written down what it meant. That is no
longer true. The release body is built from the `## [Unreleased]` section, and
an empty one fails the run — for a hand-maintained file, "nobody wrote it down"
is far more likely than "nothing changed". The commit list has not been lost:
longer true. The release body is built from the `## [Unreleased]` section,
fragments included, and an empty one fails the run — for entries written by
hand, "nobody wrote it down" is far more likely than "nothing changed". The commit list has not been lost:
the release body ends with a compare link against the previous tag.

The changelog and the release page serve different readers. The changelog
Expand Down Expand Up @@ -411,6 +414,50 @@ A hand-bump is also what broke the release on 2026-09-05, back when the commit
step treated "nothing to commit" as a failure.

When a pull request changes behaviour, adds public API, or fixes something a
user could have hit, add the entry to `## [Unreleased]` in the same PR. The
release will fail on an empty section, but it cannot tell you *which* PR forgot
its entry — only that somebody did.
user could have hit, add its entry in the same PR, as a fragment. The release
will fail when there is nothing to release, but it cannot tell you *which* PR
forgot its entry — only that somebody did.

### Changelog fragments

Pull requests do not edit `CHANGELOG.md`. When every PR appended to
`## [Unreleased]`, merging one put every other open PR in conflict. Instead,
each PR adds one file per entry to `changelog.d/`:

- **Name:** `<issue>.<section>.md`, where `<section>` is `added`, `changed`,
`deprecated`, `removed`, `fixed` or `security`. A second entry for the same
issue and section is `<issue>.<section>.2.md`, then `.3.md`, and so on; a
file holds exactly one entry.
- **Content:** the entry, opening with its bold one-line summary, *without*
the leading `- ` and *without* the issue link. The release adds both, taking
the link from the file name. Line breaks are up to you; indent a nested list
by two spaces.

`changelog.d/65.added.md`:

```markdown
**Add `CacheManager.add()` for store-if-absent writes.** It uses the same
key prefix, JSON encoding and `default_ttl` as `set()`.
```

is released under `### Added` as:

```markdown
- **Add `CacheManager.add()` for store-if-absent writes.** It uses the same
key prefix, JSON encoding and `default_ttl` as `set()`. ([#65](https://github.com/allen0099/FastAPI-CacheX/issues/65))
```

At release time `scripts/changelog_release.py` checks every fragment before it
writes anything, merges them into `## [Unreleased]` — sections in Keep a
Changelog order (Added, Changed, Deprecated, Removed, Fixed, Security), each
after any entry already written there by hand, fragments ordered by issue
number — then promotes the section and deletes the fragments. The `release`
job commits the deletions with `CHANGELOG.md`. `changelog.d/README.md` and
dotfiles are not fragments; any other file there that is not a well-formed
fragment (a bad name, an unknown section, no bold summary, a leading `- `, its
own issue link) fails the release with nothing changed.
`test_the_repository_changelog_can_be_released` merges the pending fragments
the same way, so such a file fails CI in the pull request that adds it.

An entry that belongs to no issue can still be written straight into
`## [Unreleased]` by hand; the release merges it with the fragments.
2 changes: 1 addition & 1 deletion i18n/zh-TW/docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
## Pull Request 流程 {#pull-request-process}

1. 變更介面時,請更新 `docs/` 底下對應的指南(若變更應出現在首頁,也請更新 README)。只有英文頁面需要更新:[繁體中文翻譯](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#traditional-chinese-translation)(英文)允許落後於英文版。
2. 若你的變更改變了行為、新增了公開 API,或修正了使用者可能遇到的問題,請在 [CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md) 的 `## [Unreleased]` 段落新增一筆項目。項目以粗體的一行摘要開頭,寫成 `- **What changed.** The details...`:發行說明只會列出這些摘要,缺少摘要的項目會讓 CI 失敗。發行時若該段落是空的,發行流程也會拒絕執行,因此遺漏終究會被發現,但要到發行時才會發現,而且只會知道「有人忘了寫」,無法得知是哪個 PR。
2. 若你的變更改變了行為、新增了公開 API,或修正了使用者可能遇到的問題,請新增一個 changelog 片段:檔案 `changelog.d/<issue>.<section>.md`(section 為 `added`、`changed`、`deprecated`、`removed`、`fixed` 或 `security`),內容為該筆項目,以粗體的一行摘要開頭,寫成 `**What changed.** The details...`,不要加上開頭的 `- ` 或 issue 連結——發行時會自動補上。請不要直接編輯 [CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md),詳見 [Changelog 片段](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#changelog-fragments)(英文)。發行說明只會列出這些摘要,格式錯誤的片段會讓 CI 失敗。沒有任何可發行的項目時,發行流程也會拒絕執行,因此遺漏終究會被發現,但要到發行時才會發現,而且只會知道「有人忘了寫」,無法得知是哪個 PR。
3. 為任何新的依賴、功能或變更更新文件。新的公開 API 需要 docstring;若它位於尚未涵蓋的模組中,還需要在 `docs/api/` 底下新增項目。請以 `uv run zensical build --strict` 檢查網站(見[文件網站](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#documentation-site)(英文))。
4. 取得至少一位其他開發者的同意後,PR 即可合併。

Expand Down
Loading
Loading