diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7865d4eda3c..72baece121a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,20 +1,35 @@
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
version: 2
+
updates:
- package-ecosystem: "github-actions"
# Workflow files in .github/workflows will be checked
directory: "/"
- schedule:
- interval: "weekly"
- labels: ["skip news", "C: dependencies"]
+ labels: ["ci: skip news", "C: dependencies", "C: maintenance"]
+ cooldown:
+ default-days: 7
+
+ - package-ecosystem: "pip"
+ directory: "/"
+ labels: ["ci: skip news", "C: dependencies"]
+ cooldown:
+ default-days: 7
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ patterns: ["pypa/cibuildwheel"]
+ multi-ecosystem-group: "cibuildwheel"
cooldown:
default-days: 7
- package-ecosystem: "pip"
directory: "/"
- schedule:
- interval: "weekly"
- labels: ["skip news", "C: dependencies"]
+ patterns: ["cibuildwheel"]
+ multi-ecosystem-group: "cibuildwheel"
cooldown:
default-days: 7
+
+multi-ecosystem-groups:
+ cibuildwheel:
+ labels: ["ci: skip news", "C: dependencies"]
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index ee562e12f2e..e69dde68c3b 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -17,8 +17,9 @@ jobs:
persist-credentials: false
- name: Grep CHANGES.md for PR number
- if: contains(github.event.pull_request.labels.*.name, 'skip news') != true
+ if: >
+ contains(github.event.pull_request.labels.*.name, 'ci: skip news') != true
run: |
grep -Pz "\((\n\s*)?#${{ github.event.pull_request.number }}(\n\s*)?\)" CHANGES.md || \
- (echo "Please add '(#${{ github.event.pull_request.number }})' change line to CHANGES.md (or if appropriate, ask a maintainer to add the 'skip news' label)" && \
+ (echo "Please add '(#${{ github.event.pull_request.number }})' change line to CHANGES.md (or if appropriate, ask a maintainer to add the 'ci: skip news' label)" && \
exit 1)
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
index 6ae5e5fae66..9d4e23a2877 100644
--- a/.github/workflows/fuzz.yml
+++ b/.github/workflows/fuzz.yml
@@ -2,25 +2,17 @@ name: fuzz
on:
push:
- paths:
- - tox.ini
- - .github/workflows/fuzz.yml
- - scripts/fuzz.py
- - src/**
- - tests/**
- - pyproject.toml
-
+ branches: main
pull_request:
paths:
- - tox.ini
- .github/workflows/fuzz.yml
- scripts/fuzz.py
- - src/**
- - tests/**
- - pyproject.toml
+ schedule:
+ - cron: "0 0 * * *"
+ workflow_dispatch:
concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
@@ -28,13 +20,6 @@ permissions:
jobs:
fuzz:
- # We want to run on external PRs, but not on our own internal PRs as they'll be run
- # by the push to the branch. Without this if check, checks are duplicated since
- # internal PRs match both the push and pull_request events.
- if:
- github.event_name == 'push' || github.event.pull_request.head.repo.full_name !=
- github.repository
-
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -55,4 +40,69 @@ jobs:
pip-install: --group tox
- name: Run fuzz tests
- run: tox -e fuzz
+ id: fuzz
+ run: tox -e fuzz --result-json $python_ver
+ env:
+ python_ver: ${{ matrix.python-version }}
+
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ if: failure() && steps.fuzz.outcome == 'failure'
+ with:
+ name: ${{ matrix.python-version }}
+ path: ${{ matrix.python-version }}
+
+ create-issue:
+ runs-on: ubuntu-latest
+ needs: fuzz
+ if:
+ github.repository == 'psf/black' && github.event_name != 'pull_request' &&
+ failure()
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
+ with:
+ merge-multiple: true
+ path: ./output
+
+ - name: Generate issue data
+ run: |
+ output=issue-body.html
+ touch $output
+ for FILE in ./output/*; do
+ echo "**Python $(basename $FILE)**" >> $output
+ echo -e "\`\`\`py" >> $output
+ echo -e "# stdout:" >> $output
+ echo -e "$(jq .testenvs.fuzz.test[-1].output $FILE -r)\n" >> $output
+ echo -e "# stderr:" >> $output
+ echo -e "$(jq .testenvs.fuzz.test[-1].err $FILE -r)" >> $output
+ echo -e "\`\`\`\n" >> $output
+ done
+
+ - name: Get existing issue
+ id: issue
+ run: |
+ echo "ISSUE=$( gh issue list \
+ -A github-actions[bot] -l 'ci: fuzz error' \
+ --json number -q .[0].number \
+ -R $REPO )" >> $GITHUB_OUTPUT
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+
+ - name: Create new issue
+ if: steps.issue.outputs.ISSUE == ''
+ run: >
+ gh issue create -t "Fuzz test failure" -F issue-body.html -l "ci: fuzz error"
+ -R $REPO
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+
+ - name: Edit existing issue
+ if: steps.issue.outputs.ISSUE != ''
+ run: gh issue edit $ISSUE -F issue-body.html -R $REPO
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ ISSUE: ${{ steps.issue.outputs.ISSUE }}
diff --git a/.github/workflows/post_release.yml b/.github/workflows/post_release.yml
new file mode 100644
index 00000000000..a7dc3e6cea0
--- /dev/null
+++ b/.github/workflows/post_release.yml
@@ -0,0 +1,65 @@
+name: post release
+
+on:
+ release:
+ types: published
+
+permissions: {}
+
+jobs:
+ update-stable:
+ runs-on: ubuntu-latest
+ if: github.event.release.prerelease != 'true'
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout stable branch
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: stable
+ fetch-depth: 0
+ persist-credentials: true # needed for `git push` below
+
+ - name: Update stable branch to release tag & push
+ run: |
+ git reset --hard "${TAG_NAME}"
+ git push
+ env:
+ TAG_NAME: ${{ github.event.release.tag_name }}
+
+ new-changelog:
+ runs-on: ubuntu-latest
+ if: github.event.release.prerelease != 'true'
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: main
+ fetch-tags: true
+ persist-credentials: true # Needed for git-auto-commit-action
+
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
+ with:
+ python-version: "3.13"
+ pip-version: "25.3"
+
+ - run: python scripts/release.py -a
+
+ - uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
+ with:
+ commit_message: Add new changelog
+ branch: ci/new-changelog
+ create_branch: true
+
+ - name: Create PR
+ run: |
+ gh pr create \
+ -t "Add new changelog" -b "" \
+ -l "ci: skip news" -l "C: maintanance" \
+ -a $USER
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ USER: ${{ github.event.release.author.login }}
diff --git a/.github/workflows/pypi_upload.yml b/.github/workflows/pypi_upload.yml
index 1b1c5700fe5..84272633608 100644
--- a/.github/workflows/pypi_upload.yml
+++ b/.github/workflows/pypi_upload.yml
@@ -2,11 +2,10 @@ name: build and publish
on:
release:
- types: [published]
+ types: published
pull_request:
push:
- branches:
- - main
+ branches: main
permissions: {}
@@ -154,26 +153,3 @@ jobs:
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
verbose: true
-
- update-stable:
- name: update stable branch
- needs: [publish-mypyc, publish-hatch]
- runs-on: ubuntu-latest
- if: github.event_name == 'release' && github.event.release.prerelease != 'true'
- permissions:
- contents: write
-
- steps:
- - name: Checkout stable branch
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- ref: stable
- fetch-depth: 0
- persist-credentials: true # needed for `git push` below
-
- - name: Update stable branch to release tag & push
- run: |
- git reset --hard "${TAG_NAME}"
- git push
- env:
- TAG_NAME: ${{ github.event.release.tag_name }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1bb78ffff0c..68dfe767593 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,8 +11,8 @@ repos:
files: '(CHANGES\.md|source_version_control\.md)$'
additional_dependencies:
- beautifulsoup4>=4.14.2
- - commonmark==0.9.1
- - pyyaml==6.0.1
+ - commonmark>=0.9.1
+ - pyyaml>=6.0.1
- id: check-version-in-the-basics-example
name: Check black version in the basics example
@@ -21,7 +21,7 @@ repos:
files: '(CHANGES\.md|the_basics\.md)$'
additional_dependencies:
- beautifulsoup4>=4.14.2
- - commonmark==0.9.1
+ - commonmark>=0.9.1
- repo: https://github.com/pycqa/isort
rev: 7.0.0
@@ -33,7 +33,7 @@ repos:
hooks:
- id: flake8
additional_dependencies:
- - flake8-bugbear==24.2.6
+ - flake8-bugbear
- flake8-comprehensions
- flake8-simplify
exclude: ^src/blib2to3/
@@ -47,11 +47,11 @@ repos:
additional_dependencies:
# Click is intentionally out-of-sync with pyproject.toml
# v8.2 has breaking changes. We work around them at runtime, but we need the newer stubs.
- - click>=8.2.0
+ - click>=8.0.0
- packaging>=22.0
- platformdirs>=2
- - pytokens>=0.3.0
- - tomli>=1.1.0,<2.0.0
+ - pytokens~=0.4.0
+ - tomli>=1.1.0
# blackd
- aiohttp>=3.10
@@ -69,8 +69,8 @@ repos:
# version check
- beautifulsoup4>=4.14.2
- - types-commonmark
- - types-pyyaml
+ - types-commonmark>=0.9.0
+ - types-pyyaml>=6.0.0
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.8.0
@@ -83,6 +83,3 @@ repos:
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
-
-ci:
- autoupdate_schedule: weekly
diff --git a/docs/contributing/issue_triage.md b/docs/contributing/issue_triage.md
index fad25cc71e5..573ae9c98e4 100644
--- a/docs/contributing/issue_triage.md
+++ b/docs/contributing/issue_triage.md
@@ -88,15 +88,15 @@ We also have a few standalone labels:
banners for first-time visitors to the repository)
- **`help wanted`**: complex issues that need and are looking for a fair bit of work as
to progress (will also show up in various GitHub pages)
-- **`skip news`**: for PRs that are trivial and don't need a CHANGELOG entry (and skips
- the CHANGELOG entry check)
+- **`ci: skip news`**: for PRs that are trivial and don't need a CHANGELOG entry (and
+ skips the CHANGELOG entry check)
- **`ci: build all wheels`**: when a full wheel build is needed, such as to debug
platform-specific issues. Black does not build wheels for every platform on each pull
request because the full build matrix is expensive. After the label is added, the
workflow starts only when a new commit is pushed.
```{note}
-We do use labels for PRs, in particular the `skip news` label, but we aren't that
+We do use labels for PRs, in particular the `ci: skip news` label, but we aren't that
rigorous about it. Just follow your judgement on what labels make sense for the specific
PR (if any even make sense).
```
diff --git a/docs/contributing/release_process.md b/docs/contributing/release_process.md
index 22ae781f6f6..450262d3cd1 100644
--- a/docs/contributing/release_process.md
+++ b/docs/contributing/release_process.md
@@ -53,9 +53,9 @@ To cut a release:
1. Remove any empty sections for the current release
1. (_optional_) Read through and copy-edit the changelog (eg. by moving entries,
fixing typos, or rephrasing entries)
- 1. Update references to the latest version in
- {doc}`/integrations/source_version_control` and
- {doc}`/usage_and_configuration/the_basics`
+1. Update references to the latest version in
+ {doc}`/integrations/source_version_control` and
+ {doc}`/usage_and_configuration/the_basics`
- Example PR: [GH-4563]
1. Once the release PR is merged, wait until all CI passes
- If CI does not pass, **stop** and investigate the failure(s) as generally we'd want
@@ -129,14 +129,6 @@ multiple mypyc wheels jobs (hence the term "matrix") that build for a specific p
These jobs upload the built sdist and all wheels to PyPI using [Trusted
publishing][trusted-publishing].
-#### update stable branch
-
-So this job doesn't _really_ belong here, but updating the `stable` branch after the
-other PyPI jobs pass (they must pass for this job to start) makes the most sense. This
-saves us from remembering to update the branch sometime after cutting the release.
-
-- _Currently this workflow uses an API token associated with @ambv's PyPI account_
-
### publish binaries
This workflow builds native executables for multiple platforms using [PyInstaller]. This
@@ -157,6 +149,21 @@ This workflow uses the QEMU powered `buildx` feature of Docker to upload an `arm
This also runs on each push to `main`.
```
+### post release
+
+This workflow runs a few miscellaneous jobs related to repository maintenance.
+
+#### update-stable
+
+Updates the `stable` branch by force pushing it to the most recent tag. This saves us
+from remembering to update the branch sometime after cutting the release.
+
+#### new-changelog
+
+Opens a new PR to add the "Unreleased" section back to the changelog. The PR is
+intentionally not auto-merged, in case there's an issue and the release needs to be
+re-cut.
+
[black-actions]: https://github.com/psf/black/actions
[calver]: https://calver.org
[cibuildwheel]: https://cibuildwheel.readthedocs.io/
diff --git a/docs/contributing/the_basics.md b/docs/contributing/the_basics.md
index 4537ca127ce..c6e0c788a19 100644
--- a/docs/contributing/the_basics.md
+++ b/docs/contributing/the_basics.md
@@ -96,8 +96,8 @@ default. To turn it off pass `--print-tree-diff=False`.
`Black` has CI that will check for an entry corresponding to your PR in `CHANGES.md`. If
you feel this PR does not require a changelog entry please state that in a comment and a
-maintainer can add a `skip news` label to make the CI pass. Otherwise, please ensure you
-have a line in the following format added below the appropriate header:
+maintainer can add a `ci: skip news` label to make the CI pass. Otherwise, please ensure
+you have a line in the following format added below the appropriate header:
```md
- `Black` is now more awesome (#X)
diff --git a/pyproject.toml b/pyproject.toml
index 9966e76f31b..f6f721463a7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ unstable = true
# NOTE: You don't need this in your own Black configuration.
[build-system]
-requires = ["hatch-fancy-pypi-readme", "hatch-vcs", "hatchling>=1.27.0"]
+requires = ["hatch-fancy-pypi-readme", "hatch-vcs>=0.3.0", "hatchling>=1.27.0"]
build-backend = "hatchling.build"
[project]
@@ -54,25 +54,25 @@ classifiers = [
]
dependencies = [
"click>=8.0.0",
- "mypy_extensions>=0.4.3",
+ "mypy-extensions>=0.4.3",
"packaging>=22.0",
"pathspec>=1.0.0",
"platformdirs>=2",
- "pytokens>=0.3.0",
+ "pytokens~=0.4.0",
"tomli>=1.1.0; python_version<'3.11'",
- "typing_extensions>=4.0.1; python_version<'3.11'",
+ "typing-extensions>=4.0.1; python_version<'3.11'",
]
dynamic = ["readme", "version"]
[project.optional-dependencies]
colorama = ["colorama>=0.4.3"]
-uvloop = ["uvloop>=0.15.2"]
+uvloop = ["uvloop>=0.15.2; platform_system!='Windows'"]
d = ["aiohttp>=3.10"]
jupyter = ["ipython>=7.8.0", "tokenize-rt>=3.2.0"]
[dependency-groups]
-build = ["hatch==1.15.1", "hatch-fancy-pypi-readme", "hatch-vcs"]
-wheels = ["cibuildwheel==3.3.1", "pypyp==1.3.0"]
+build = ["hatch==1.15.1", "hatch-fancy-pypi-readme", "hatch-vcs>=0.3.0"]
+wheels = ["cibuildwheel==3.3.1", "pypyp"]
binary = ["pyinstaller", "wheel>=0.45.1"]
dev = [{ include-group = "cov-tests" }, { include-group = "tox" }, "pre-commit"]
@@ -85,7 +85,7 @@ docs = [
"docutils==0.21.2",
"furo==2025.12.19",
"myst-parser==4.0.1",
- "sphinx_copybutton==0.5.2",
+ "sphinx-copybutton==0.5.2",
"sphinx==8.2.3",
"sphinxcontrib-programoutput==0.18",
]
@@ -100,7 +100,7 @@ diff-shades = [
]
diff-shades-comment = ["click>=8.1.7", "packaging>=22.0", "urllib3"]
-width-table = ["wcwidth>=0.2.14"]
+width-table = ["wcwidth==0.2.14"]
[project.scripts]
black = "black:patched_main"
diff --git a/scripts/diff_shades_gha_helper.py b/scripts/diff_shades_gha_helper.py
index 3641b62b1e9..def10dbc874 100644
--- a/scripts/diff_shades_gha_helper.py
+++ b/scripts/diff_shades_gha_helper.py
@@ -187,7 +187,7 @@ def comment_body(baseline: Path, target: Path, style: str, mode: str) -> None:
""
)
else:
- body = f"--{style} style: no changes"
+ body = f"--{style} style: no changes"
filename = f".{style}{COMMENT_FILE}"
print(f"[INFO]: writing comment details to {filename}")