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
19 changes: 19 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,22 @@ jobs:
run: tox run --skip-pkg-install -e ${{ matrix.tox_env }}
env:
UV_PYTHON_PREFERENCE: only-managed

changelog:
name: "📝 News fragment"
runs-on: ubuntu-24.04
# A PR must add a docs/changelog/<pr>.<type>.rst fragment; automated dependency and pre-commit bumps are exempt.
if: >-
github.event_name == 'pull_request' && !contains(fromJSON('["dependabot[bot]", "pre-commit-ci[bot]"]'),
github.actor)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: "🔄 Install the latest version of uv"
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: false
- name: "📝 Require a news fragment"
run: uvx towncrier check --compare-with "origin/${GITHUB_BASE_REF}"
76 changes: 76 additions & 0 deletions .github/workflows/prepare-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: 🛠️ Prepare release
on:
workflow_dispatch:
inputs:
bump:
description: "Version bump"
required: true
default: auto
type: choice
options: [auto, major, minor, patch]

permissions:
contents: read

jobs:
prepare-release:
name: 📝 build changelog and tag
runs-on: ubuntu-24.04
# RELEASE_TOKEN is an environment-scoped secret; selecting the environment resolves it so the checkout can push.
environment:
name: release
permissions:
contents: write
steps:
- name: 📥 Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # full history so the version calculation sees the latest tag
token: ${{ secrets.RELEASE_TOKEN }} # zizmor: ignore[artipacked]
- name: 📦 Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: false
- name: 🔢 Compute next version
id: version
env:
BUMP: ${{ inputs.bump }}
run: |
current=$(git tag --list --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
current=${current:-0.0.0}
IFS='.' read -r major minor patch <<< "$current"
bump="$BUMP"
if [ "$bump" = "auto" ]; then
if ls docs/changelog/*.breaking.rst >/dev/null 2>&1; then
bump=major
elif ls docs/changelog/*.feature.rst >/dev/null 2>&1; then
bump=minor
else
bump=patch
fi
fi
case "$bump" in
major) next="$((major + 1)).0.0" ;;
minor) next="$major.$((minor + 1)).0" ;;
patch) next="$major.$minor.$((patch + 1))" ;;
esac
echo "Bumping $current -> $next ($bump)"
echo "version=$next" >> "$GITHUB_OUTPUT"
- name: 📝 Build changelog, commit, and tag
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q .; then
echo "Tag $VERSION already exists, nothing to do"
exit 1
fi
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com"
uvx towncrier build --yes --version "$VERSION"
git add docs/changelog docs/changelog.rst
git commit -m "Release $VERSION"
git tag "$VERSION"
# Push the branch first, then the tag. The tag is pushed with RELEASE_TOKEN (a PAT), so it triggers the
# release workflow on the tag ref, where the build is verified against the changelog and published to PyPI.
git push origin HEAD:main
git push origin "$VERSION"
95 changes: 16 additions & 79 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
name: 🚀 Release
on:
workflow_dispatch:
inputs:
release:
description: "🚀 Release (semver bump)?"
required: true
default: "no"
type: choice
options: ["no", patch, minor, major]
push:
branches: ["main"]
tags: ["[0-9]+.[0-9]+.[0-9]+"]
pull_request:

concurrency:
group: >-
${{ github.workflow }}-${{ github.ref }}${{
github.event.inputs.release && github.event.inputs.release != 'no' && '-release' || ''
}}
cancel-in-progress: ${{ github.event.inputs.release == 'no' || github.event.inputs.release == null }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}

permissions:
contents: read
Expand All @@ -32,37 +21,16 @@ jobs:
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
outputs:
version: ${{ steps.v.outputs.version }}
changelog: ${{ steps.v.outputs.changelog }}
steps:
- name: 📥 Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
fetch-depth: 0 # full history so hatch-vcs derives the version from the tag
persist-credentials: false
- name: 📦 Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: false # this workflow builds the artifact that gets published; no cache to poison
- name: 📝 Generate changelog
id: v
if: ${{ !startsWith(github.ref, 'refs/tags/') }} # on a tag push we build to publish, no changelog needed
run: >-
uv run tasks/changelog.py "$RELEASE_TYPE" "$EVENT_NUMBER" "$BASE_SHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TYPE:
${{ github.event.inputs.release == 'no' || github.event.inputs.release == null && 'patch' ||
github.event.inputs.release }}
EVENT_NUMBER: ${{ github.event.number }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
- name: 🏷️ Create temporary tag for hatch-vcs
if: github.event.inputs.release != 'no' && github.event.inputs.release != null
run: git tag "$STEPS_V_OUTPUTS_VERSION"
env:
STEPS_V_OUTPUTS_VERSION: ${{ steps.v.outputs.version }}
- name: 📦 Build package
run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
- name: 📤 Store the distribution packages
Expand All @@ -71,63 +39,32 @@ jobs:
name: ${{ env.dists-artifact-name }}
path: dist/*

release:
name: 🏷️ tag
publish:
name: 🚀 publish
needs: [build]
if: github.event.inputs.release != 'no' && github.event.inputs.release != null && github.ref == 'refs/heads/main'
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
# RELEASE_TOKEN is an environment-scoped secret; without selecting the environment it resolves empty and the
# checkout fails with "Input required and not supplied: token".
environment:
name: release
url: https://pypi.org/project/filelock/${{ github.ref_name }}
permissions:
id-token: write
contents: write
steps:
- name: 📥 Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ secrets.RELEASE_TOKEN }} # zizmor: ignore[artipacked]
- name: 📤 Commit changelog and tag
persist-credentials: false
- name: 🛡️ Require a changelog section for this release
env:
CHANGELOG: ${{ needs.build.outputs.changelog }}
VERSION: ${{ needs.build.outputs.version }}
VERSION: ${{ github.ref_name }}
# A tag reaches PyPI only through prepare-release, which builds the changelog first. A tag pushed by hand skips
# that, so refuse to publish a version the changelog does not document rather than ship it silently.
run: |
if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q .; then
echo "Tag $VERSION already exists, nothing to do"
exit 0
if ! grep -qE "^ ${VERSION//./\\.} \(" docs/changelog.rst; then
echo "::error::docs/changelog.rst has no section for $VERSION; release via the Prepare release workflow"
exit 1
fi
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com"
header="$VERSION ($(date -u +%Y-%m-%d))"
separator=$(printf '%0.s*' $(seq 1 $((${#header} + 1))))
{
head -4 docs/changelog.rst
printf '%s\n %s\n%s\n\n' "$separator" "$header" "$separator"
printf '%s\n\n' "$CHANGELOG"
tail -n +5 docs/changelog.rst
} > docs/changelog.tmp
mv docs/changelog.tmp docs/changelog.rst
git add docs/changelog.rst
git commit -m "Release $VERSION"
git tag "$VERSION"
# Push the branch, then the tag. The tag is pushed with RELEASE_TOKEN (a PAT, not GITHUB_TOKEN), so it
# triggers this same workflow on the tag ref, where the publish job below uploads to PyPI. Publishing keys
# off the tag, not this dispatch run, so any tag — including one pushed by hand — always reaches PyPI.
git push origin main
git push origin "$VERSION"

publish:
name: 🚀 publish
needs: [build]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
environment:
name: release
url: https://pypi.org/project/filelock/${{ github.ref_name }}
permissions:
id-token: write
contents: write
steps:
- name: ⬇️ Download all the dists
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand Down
35 changes: 35 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@
Changelog
###########

.. towncrier-draft-entries:: Unreleased

.. towncrier release notes start

********************
3.29.7 (2026-07-07)
********************

- _util: drop the dead st_mtime=0 short-circuit in raise_on_not_writable_file :pr:`589` - by :user:`HrachShah`
- soft_rw: evict a non-regular write marker without reading it :pr:`588`
- asyncio: detect cross-instance reentrant deadlocks before the poll loop :pr:`586` - by :user:`HrachShah`

********************
3.29.6 (2026-07-06)
********************

- test: silence fork DeprecationWarning on 3.15 :pr:`585`
- _util: drop the dead st_mtime=0 short-circuit in raise_on_not_writable_file :pr:`582` - by :user:`HrachShah`
- serialize singleton construction in FileLockMeta :pr:`581` - by :user:`dxbjavid`
- surface GitHub Sponsors and thanks.dev

********************
3.29.5 (2026-07-02)
********************

- lifetime: reject negative, non-numeric, and bool values at the setter :pr:`573` - by :user:`HrachShah`
- roll back a read acquire's open transaction when its SELECT fails :pr:`575` - by :user:`dxbjavid`
- keep Unix lock files after release :pr:`577` - by :user:`itscloud0`
- use a private break name in break_lock_file :pr:`576` - by :user:`dxbjavid`
- don't complete a writer acquire on a peer's reclaimed marker :pr:`571` - by :user:`dxbjavid`
- don't follow symlinks in raise_on_not_writable_file :pr:`567` - by :user:`dxbjavid`
- only unlink the writer marker on release if it is still ours :pr:`566` - by :user:`dxbjavid`
- don't follow symlinks when refreshing soft read/write lock markers :pr:`565` - by :user:`dxbjavid`
- serialize read/write release rollback against a concurrent acquire :pr:`563` - by :user:`dxbjavid`

********************
3.29.4 (2026-06-13)
********************
Expand Down
1 change: 1 addition & 0 deletions docs/changelog/593.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ignore ``lifetime`` on native locks with a warning; only ``SoftFileLock`` honors it.
1 change: 1 addition & 0 deletions docs/changelog/594.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stop mutating the Unix lock file before ``flock`` is held.
1 change: 1 addition & 0 deletions docs/changelog/596.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bind the Windows reparse-point check to the locked handle to close a symlink-swap race.
1 change: 1 addition & 0 deletions docs/changelog/597.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Evict a non-regular soft lock file without reading it.
1 change: 1 addition & 0 deletions docs/changelog/614.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Write a soft lock's holder record atomically so a short write cannot create overlapping holders.
1 change: 1 addition & 0 deletions docs/changelog/615.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make native lock release transactional so a failed unlock is retried, not dropped.
1 change: 1 addition & 0 deletions docs/changelog/617.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Open the Windows lock file via ``NtCreateFile`` so a real access denial is raised, not reported as a timeout.
1 change: 1 addition & 0 deletions docs/changelog/618.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``context_error_policy`` to surface body and release failures as a ``BaseExceptionGroup``.
1 change: 1 addition & 0 deletions docs/changelog/619.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``close_error_policy`` to control an ``os.close`` failure after the OS unlock.
1 change: 1 addition & 0 deletions docs/changelog/620.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``lock_descriptor`` and ``unlock_descriptor`` to lock a caller-owned file descriptor.
1 change: 1 addition & 0 deletions docs/changelog/621.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Canonicalize singleton keys so equivalent path spellings share an instance, without following a final symlink.
1 change: 1 addition & 0 deletions docs/changelog/622.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``fallback_to_soft`` to fail closed instead of downgrading to ``SoftFileLock`` on ``ENOSYS``.
1 change: 1 addition & 0 deletions docs/changelog/623.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Correct the Unix lock-file cleanup and ``flock`` documentation.
1 change: 1 addition & 0 deletions docs/changelog/624.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``preserve_lock_file`` so native locks keep the lock pathname on release.
1 change: 1 addition & 0 deletions docs/changelog/625.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add an ``on_acquired`` hook that runs a callback on the locked descriptor once the lock is held.
1 change: 1 addition & 0 deletions docs/changelog/626.packaging.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Build the changelog from towncrier news fragments, render pending fragments as an ``Unreleased`` docs section, and refuse to publish a tag the changelog does not document.
12 changes: 12 additions & 0 deletions docs/changelog/template.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% set header = versiondata.version + " (" + versiondata.date + ")" %}
{{ "*" * (header | length + 1) }}
{{ header }}
{{ "*" * (header | length + 1) }}

{% for section, _ in sections.items() %}
{% for category, val in definitions.items() if category in sections[section] %}
{% for text, values in sections[section][category].items() %}
- {{ text }}{{ (" " + values | join(", ")) if values else "" }}
{% endfor %}
{% endfor %}
{% endfor %}
Loading