From 76aa51c33738ecf5be0af377258d17e620a32916 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Mon, 10 Aug 2026 09:24:30 +0200 Subject: [PATCH] Switch change log management to towncrier CHANGES.rst was maintained by hand, so every pull request touched the same few lines of the same file and concurrent pull requests conflicted on it. News fragments now live as individual files in changes/, and CHANGES.rst is generated from them at release time by zest.releaser via the zestreleaser.towncrier plugin. The entries of the unreleased 16.5 section have been converted into fragments. A new workflow rejects direct edits of CHANGES.rst and requires a news fragment, unless the pull request carries the no-changelog label. --- .github/workflows/changelog-checker.yaml | 43 ++++++++++++++++ CHANGES.rst | 28 ++--------- CONTRIBUTING.rst | 29 ++++++++++- MANIFEST.in | 3 ++ changes/268.bugfix.rst | 1 + changes/270.bugfix.rst | 1 + changes/298.feature.rst | 1 + changes/README.rst | 63 ++++++++++++++++++++++++ pyproject.toml | 27 ++++++++++ 9 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/changelog-checker.yaml create mode 100644 changes/268.bugfix.rst create mode 100644 changes/270.bugfix.rst create mode 100644 changes/298.feature.rst create mode 100644 changes/README.rst diff --git a/.github/workflows/changelog-checker.yaml b/.github/workflows/changelog-checker.yaml new file mode 100644 index 0000000..dd9533e --- /dev/null +++ b/.github/workflows/changelog-checker.yaml @@ -0,0 +1,43 @@ +name: Check Changelog Entry + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + pull-requests: read + +jobs: + check-changelog: + if: > + !contains(github.event.pull_request.labels.*.name, 'no-changelog') && + github.event.pull_request.user.login != 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Check for direct CHANGES file edits + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + echo "Checking for changelog file in PR #$PR_NUMBER" + changed_files=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/files" | jq -r '.[].filename') + echo "Changed files: $changed_files" + if echo "$changed_files" | grep -qE "^CHANGES\.rst$"; then + echo "Do not edit the changelog file directly." + echo "Create a new file in the \`changes\` directory, see \`changes/README.rst\` for details." + exit 1 + fi + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.x" + - name: Install towncrier + run: python -m pip install -U pip towncrier + - name: Check changelog entry + run: towncrier check --compare-with origin/${{ github.event.pull_request.base.ref }} diff --git a/CHANGES.rst b/CHANGES.rst index e0a9691..4aa1e68 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,32 +1,10 @@ Changelog ========= -16.5 (unreleased) ------------------ - -Bug fixes -+++++++++ - -- Prevent a rerun when a fixture teardown raises an error matching - ``--rerun-except``. Previously only the error from the current test stage - was considered, so a ``--rerun-except``-matching error raised during - teardown was ignored and the test was rerun anyway. - Fixes `#270 `_. - -- Create a new test class instance for each rerun. Previously the instance of - the failed attempt was reused, so state stored on ``self`` leaked into the - rerun and broke test isolation. Fixtures cached at class scope or higher are - still not re-executed. - Fixes `#268 `_. - -Features -++++++++ - -- Add ``--max-suite-reruns`` option to cap the total number of reruns across - the entire test suite. Once the limit is reached, no further reruns occur - regardless of per-test ``--reruns`` or ``@pytest.mark.flaky`` settings. - Fixes `#298 `_. +.. Do not edit this file manually any more, create a change log entry file in +.. the changes directory, see changes/README.rst for details. +.. towncrier release notes start 16.4 (2026-07-01) ----------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 675b3e1..da323ef 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -37,5 +37,30 @@ Preparing Pull Requests #. Follow **PEP 8** for naming and `black `_ for formatting. -#. Add a line item to the current **unreleased** version in ``CHANGES.rst``, - unless the change is trivial. +#. Add a change log entry, unless the change is trivial. Do **not** edit + ``CHANGES.rst`` -- it is generated at release time. Instead create a file in + the ``changes/`` directory named after the GitHub issue or pull request + number, with an extension naming the kind of change, for example + ``changes/270.bugfix.rst``. See `changes/README.rst + `_ + for the available types and the expected style. + + +Making a release +---------------- + +Releases are made with `zest.releaser +`_ together with the +``zestreleaser.towncrier`` plugin, which runs ``towncrier build`` at the right +moment so ``CHANGES.rst`` is assembled from the files in ``changes/``:: + + $ pip install --group release + $ fullrelease + +``pip install --group release`` requires pip 25.1 or newer; with an older pip +use ``pip install "zest.releaser[recommended]" zestreleaser.towncrier`` +instead. + +To preview the change log for the next release without writing anything:: + + $ towncrier build --draft --version 17.0 diff --git a/MANIFEST.in b/MANIFEST.in index d32163a..87b645b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,3 +9,6 @@ exclude *.yaml recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.txt + +graft changes +prune changes diff --git a/changes/268.bugfix.rst b/changes/268.bugfix.rst new file mode 100644 index 0000000..e13ec3f --- /dev/null +++ b/changes/268.bugfix.rst @@ -0,0 +1 @@ +Create a new test class instance for each rerun. Previously the instance of the failed attempt was reused, so state stored on ``self`` leaked into the rerun and broke test isolation. Fixtures cached at class scope or higher are still not re-executed. diff --git a/changes/270.bugfix.rst b/changes/270.bugfix.rst new file mode 100644 index 0000000..9113b7f --- /dev/null +++ b/changes/270.bugfix.rst @@ -0,0 +1 @@ +Prevent a rerun when a fixture teardown raises an error matching ``--rerun-except``. Previously only the error from the current test stage was considered, so a ``--rerun-except``-matching error raised during teardown was ignored and the test was rerun anyway. diff --git a/changes/298.feature.rst b/changes/298.feature.rst new file mode 100644 index 0000000..d910681 --- /dev/null +++ b/changes/298.feature.rst @@ -0,0 +1 @@ +Add ``--max-suite-reruns`` option to cap the total number of reruns across the entire test suite. Once the limit is reached, no further reruns occur regardless of per-test ``--reruns`` or ``@pytest.mark.flaky`` settings. diff --git a/changes/README.rst b/changes/README.rst new file mode 100644 index 0000000..9f55e8d --- /dev/null +++ b/changes/README.rst @@ -0,0 +1,63 @@ +Writing change log entries +========================== + +.. caution:: + Do not edit the ``CHANGES.rst`` file manually, this file is now autogenerated + at release time. This prevents getting conflicts in that file during merge + time. + +File name +--------- + +To add an entry to the change log, take the number of the GitHub issue and you +create a file inside of the ``changes/`` directory, named after that issue +number with an extension telling the type of change, followed by ``.rst``. The +allowed types are: + +* ``feature``: You added a new feature. +* ``bugfix``: You fixed a bug. +* ``doc``: You improved the documentation. +* ``removal``: You deprecated or removed a public API. +* ``breaking``: You changed the public API in a way which requires changes in + the consumers of the API. +* ``misc``: You changed something which is of minor interest to the users of + this package, e. g. the development or test setup. + +Example: If your GitHub issue number is 270 and you fixed the bug reported +there, you create a file named ``270.bugfix.rst`` in the ``changes/`` +directory. You can also let towncrier create it for you:: + + $ towncrier create --content "Fix the thing." 270.bugfix + +If your pull request does not have an issue, use the number of the pull request +itself. + +Each type is rendered as a sub-headline in the change log. If your changes match +multiple types and should show up below different sub-headlines in the change +log or you resolved multiple issues just create multiple files in ``changes/``. +If the files contain the same text, the entries in the changelog will get +deduplicated. + +The part before the type must be the plain issue number -- do not add a +description to it, as it ends up in the change log as the issue reference. If +you need more than one entry of the *same* type for the *same* issue, append a +counter between the type and the extension, e. g. ``270.misc.rst`` and +``270.misc.2.rst``. The counter is only used to keep the file names unique and +to order the entries, it does not show up in the change log. + +Contents of a changes entry +--------------------------- + +In the contents of the file you tell what you have changed. You can use +reStructuredText markup here. Please do *not* repeat the issue number in the +file: the link to the GitHub issue is added automatically from the file name. + +Do not hard wrap the text of an entry: towncrier re-wraps each paragraph to fit +the change log, and hard wrapping produces a ragged result. + +In order to maintain a consistent style in the change log, it is preferred to +keep the entries to the point, in sentence case, and in an imperative tone -- an +entry should complete the sentence “This change will …”. In rare cases, where +one line is not enough, use a summary line in an imperative tone followed by a +blank line separating it from a description of the feature/change in one or more +paragraphs. diff --git a/pyproject.toml b/pyproject.toml index f452f10..0f38e11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,11 @@ entry-points.pytest11.rerunfailures = "pytest_rerunfailures" [dependency-groups] dev = [ "mypy>=2.1", + "towncrier>=25.8", +] +release = [ + "zest-releaser[recommended]>=9.9", + "zestreleaser-towncrier>=2", ] [tool.setuptools.dynamic] @@ -78,6 +83,28 @@ lint.pydocstyle.convention = "google" [tool.check-manifest] ignore = [ ".pre-commit-config.yaml" ] +[tool.towncrier] +directory = "changes" +filename = "CHANGES.rst" +title_format = "{version} ({project_date})" +issue_format = "`#{issue} `_" +underlines = [ + "-", + "+", + "~", +] +wrap = true +# An empty ignore list turns on strict validation of news fragment file names. +ignore = [ ] +type = [ + { directory = "breaking", name = "Breaking Changes", showcontent = true }, + { directory = "removal", name = "Deprecations and Removals", showcontent = true }, + { directory = "feature", name = "Features", showcontent = true }, + { directory = "bugfix", name = "Bug Fixes", showcontent = true }, + { directory = "doc", name = "Documentation", showcontent = true }, + { directory = "misc", name = "Misc", showcontent = true }, +] + [tool.mypy] python_version = "3.10" warn_return_any = true