Conversation
why: uv's global `exclude-newer = "3 days"` supply-chain cooldown hides ruff 0.16.0 (released 2026-07-23) from the resolver, so the version floor in the next commit cannot resolve. what: - Add `ruff = false` to `[tool.uv.exclude-newer-package]` Temporary. Revert before merge - the cooldown clears on its own and the `ruff>=0.16` floor is what actually holds the version.
why: ruff 0.16.0 stabilizes rules inside prefixes this workspace already selects and starts formatting Python code blocks in Markdown. Pinning a floor keeps contributors and CI on the same diagnostics instead of splitting on whatever ruff each machine resolved. what: - Raise `ruff` to `>=0.16.0` in the `dev` dependency group - Relock `uv.lock`: ruff 0.15.22 -> 0.16.0 https://astral.sh/blog/ruff-v0.16.0
why: ruff 0.16.0 stabilizes RUF036, which requires `None` to be the last member of a type union. It reads as the fallback case and matches how `Optional[X]` and typeshed order their unions. what: - Move `None` to the end of the `ScenarioInputValue` and `FrozenScenarioValue` type aliases https://docs.astral.sh/ruff/rules/none-not-at-end-of-union/
why: ruff 0.16.0 formats Python and pycon code blocks inside Markdown by default, so `ruff format . --check` now fails CI on READMEs and docs pages that were previously out of scope. The churn is mechanical - the formatter applying its existing Python rules to embedded snippets. what: - Reformat Python code blocks in the workspace and package READMEs and in the docs pages, matching `ruff format` output https://astral.sh/blog/ruff-v0.16.0
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #73 +/- ##
==========================================
- Coverage 92.80% 92.80% -0.01%
==========================================
Files 275 275
Lines 22129 22123 -6
==========================================
- Hits 20537 20531 -6
Misses 1592 1592 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
why: Contributors formatting with an older ruff will revert the Markdown code blocks this bump reflowed, so the floor belongs in the changelog. what: - Add a `### Development` entry for the `ruff>=0.16.0` floor and the new Markdown code-block formatting
why: ruff 0.16.0 published 2026-07-23T19:10Z and has now cleared uv's 3-day supply-chain cooldown, so the resolver reaches it unaided. The `ruff>=0.16.0` floor is what holds the version; leaving the exemption would permanently opt ruff out of the cooldown guard. what: - Drop `ruff = false` from `[tool.uv.exclude-newer-package]` - Relock: the setting is recorded in `uv.lock`, so removing it forces a re-resolve. ruff stays at 0.16.0 and no other package moves. This reverts commit 20572d3.
why: ruff 0.16 ships a curated default rule set as its recommended baseline. An explicit `select` replaces that set rather than extending it, so this workspace was running 351 rules and silently opting out of the rest. `extend-select` layers the project's own linters on top of the default set instead of in place of it. what: - Replace `select` with `extend-select`, same entries, one per line - Note in the file why `select` stays unset Enabled rules go from 351 to 565. https://docs.astral.sh/ruff/linter/#rule-selection
why: `url[len("git+") :]` and `url[: -len(".git")]` restate the affix
twice each and depend on the slice bounds staying in sync with the
literal. `str.removeprefix` / `str.removesuffix` say the same thing once
and carry the "only when present" guard themselves.
what:
- Replace the conditional slices with `removeprefix` / `removesuffix`
https://docs.astral.sh/ruff/rules/slice-to-remove-prefix-or-suffix/
why: `re.S` is a one-letter alias whose meaning has to be looked up. `re.DOTALL` names what it does at the call site, and the palette patterns rely on that flag to span CSS blocks across newlines. what: - Use `re.DOTALL` instead of `re.S` in the three block patterns https://docs.astral.sh/ruff/rules/regex-flag-alias/
why: Two modules kept an `if t.TYPE_CHECKING:` guard whose body was a bare `pass` — scaffolding left behind after the last type-only import moved out. It reads as though something is imported under it. what: - Delete the empty guards in `_internal/process.py` and `tests/test_gp_furo_theme.py` - Drop the now-unused `typing` import the guard was the last user of https://docs.astral.sh/ruff/rules/empty-type-checking-block/
why: The `if t.TYPE_CHECKING:` block already imports two names, so the trailing `pass` is dead — it reads like the block is empty when it is not. what: - Remove the `pass` from the TYPE_CHECKING block https://docs.astral.sh/ruff/rules/unnecessary-placeholder/
why: Chained `x.startswith(a) or x.startswith(b)` walks the string twice and repeats the receiver. `str.startswith` accepts a tuple and short-circuits internally, so the tuple form is both shorter and the one CPython optimises. what: - Collapse the paired `startswith` calls in the em-dash separator check and the how-to prose policy test https://docs.astral.sh/ruff/rules/multiple-starts-ends-with/
why: `_AsyncProcess` and `_AsyncioBus` are private, absent from `__all__`, and referenced nowhere — the comment above them claims they are re-exports for consumers, but a leading underscore makes them unimportable in that sense. The module already uses `AsyncProcess` and `AsyncioBus` directly. what: - Remove both aliases and the comment describing them - Drop the `typing` import they were the last user of https://docs.astral.sh/ruff/rules/unused-private-type-alias/
why: `from collections.abc import Set` shadows nothing but reads as the `set` builtin at every annotation site, and these are exactly the signatures where the distinction matters — Sphinx hands `merge_domaindata` and `prepare_writing` an immutable set view, not a `set`. what: - Import `Set as AbstractSet` and annotate with `AbstractSet` in the three domains, the demo builder, and the OpenGraph description parser https://docs.astral.sh/ruff/rules/unaliased-collections-abc-set-import/
why: A multi-line implicit concatenation sitting bare inside a list or tuple literal is indistinguishable from a missing comma — the reader has to count elements to tell whether two sentences are one item or two. Parentheses make the intent explicit at the site. what: - Wrap the concatenated cluster blurbs, generated-page lead paragraphs, and the expected warning format string in parentheses https://docs.astral.sh/ruff/rules/implicit-string-concatenation-in-collection-literal/
why: Every BLE001 site in this workspace guards a call into code the extension does not own — introspecting a documented project's modules, stringifying whatever annotation autodoc supplies, resolving a builder URI, importing a user's argparse parser factory, reading intersphinx's untyped inventory. Each handler degrades to a fallback or reports a docutils error, so a single malformed object cannot abort the docs build. Narrowing the catches would let exotic exceptions from foreign code escape and do exactly that. what: - Per-file-ignore BLE001 in the six modules holding those boundaries https://docs.astral.sh/ruff/rules/blind-except/
why: Three suites build throwaway modules by compiling a literal source string, because the shapes under test cannot be written as ordinary imports: deferred annotations that must not evaluate, positional-only fixture parameters, `TYPE_CHECKING`-only aliases. The source is a literal in the test file — there is no untrusted input for S102 to protect against. what: - Per-file-ignore S102 in the three suites that synthesise modules https://docs.astral.sh/ruff/rules/exec-builtin/
why: `_resolve_builtin_link` asks intersphinx for a pytest fixture URL before falling back to the static `PYTEST_BUILTIN_LINKS` map. A failure inside the probe means the inventory has no usable answer — the same outcome as a plain miss, and the next statement already handles it. The `pass` is that fallthrough, not a swallowed error, and logging it would emit a warning in every docs build that no docs author can act on. what: - Add S110 alongside BLE001 on `_store.py` https://docs.astral.sh/ruff/rules/try-except-pass/
why: `_FixtureMarker` is the return type of `_detection._get_fixture_marker`, but PYI046 only looks inside the file that defines a protocol, so one that is private to the package rather than to the module reads as dead. Renaming it public would export an internal normalisation detail purely to satisfy the check. what: - Per-file-ignore PYI046 in `_models.py` https://docs.astral.sh/ruff/rules/unused-private-protocol/
why: Both FLY002 sites join a sequence of string literals where each literal is one output line — a Python program handed to a subprocess, and the expected RST block that `_build_autofixtures_directive_text` emits line by line. The one-literal-per-line shape is what makes them readable and reviewable; collapsing them into an f-string with embedded newlines (and, in the subprocess case, nested quoting) loses that. what: - Per-file-ignore FLY002 in `package_tools.py` and `test_sphinx_pytest_fixtures.py` https://docs.astral.sh/ruff/rules/static-join-to-f-string/
why: The changelog recorded the ruff floor but not the larger change it enabled — the workspace now lints against ruff's default rule set, so contributors see diagnostics from linters this project never named. what: - Extend the ruff 0.16 development entry with the default-set adoption and the scoped per-file ignores
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ruff>=0.16.0in thedevdependency group so contributors and CI see the same diagnostics.[tool.ruff.lint] selecttoextend-select, taking the enabled-rule count from 351 to 565.pyproject.toml— for the five that flag a deliberate idiom.ruff formatnow does by default.Changes
Dependencies
pyproject.toml, uv.lock —
rufffloor raised to>=0.16.0; lockfile moves ruff 0.15.22 to 0.16.0.Lint configuration
pyproject.toml —
selectbecomesextend-select, same entries, one per line with the linter it selects named in a trailing comment.selectnow stays deliberately unset, with a comment in the file saying why: naming it would replace ruff's default set rather than layer on top of it. There is no token for "the defaults" that an explicitselectcould name.Rules fixed
none-not-at-end-of-union) —Nonemoved to the end of theScenarioInputValueandFrozenScenarioValueunions. Purely positional; the union members are unchanged.implicit-string-concatenation-in-collection-literal) — multi-line implicit concatenations sitting bare inside a list or tuple literal are indistinguishable from a missing comma. The cluster blurbs, generated-page lead paragraphs, and one expected warning format string are now parenthesized.unaliased-collections-abc-set-import) —from collections.abc import Setreads as thesetbuiltin at every annotation site, and these are exactly the signatures where it matters: Sphinx handsmerge_domaindataandprepare_writingan immutable set view. Imported asSet as AbstractSetin the three domains, the demo builder, and the OpenGraph description parser.unused-private-type-alias) —_AsyncProcessand_AsyncioBusinsphinx_vite_builder._internal.vitewere private, absent from__all__, and referenced nowhere; the comment above them claimed they were re-exports for consumers, which a leading underscore rules out. Deleted, along with thetypingimport they were the last user of.multiple-starts-ends-with) — chainedx.startswith(a) or x.startswith(b)collapsed into the tuple form.empty-type-checking-block) — twoif t.TYPE_CHECKING:guards whose body was a barepass, left behind after the last type-only import moved out.unnecessary-placeholder) — apasstrailing two live imports inside aTYPE_CHECKINGblock.slice-to-remove-prefix-or-suffix) —url[len("git+") :]andurl[: -len(".git")]replaced withremoveprefix/removesuffix, which state the affix once and carry the "only when present" guard themselves.regex-flag-alias) —re.Sspelledre.DOTALLin the palette block patterns.Ignores scoped, with reasons
blind-except) — every site guards a call into code the extension does not own: introspecting a documented project's modules, stringifying whatever annotation autodoc supplies, resolving a builder URI, importing a user's argparse parser factory, reading intersphinx's untyped inventory. Each handler degrades to a fallback or reports a docutils error, so one malformed object cannot abort the docs build. Narrowing them would let exotic exceptions from foreign code escape and do exactly that. Ignored in the six modules that hold those boundaries.exec-builtin) — three suites compile a literal source string into a throwaway module, because the shapes under test cannot be written as ordinary imports: deferred annotations that must not evaluate, positional-only fixture parameters,TYPE_CHECKING-only aliases. There is no untrusted input here.static-join-to-f-string) — both joins assemble line-oriented text, one literal per output line: a Python program handed to a subprocess, and the expected RST block a generator emits line by line. An f-string with embedded newlines loses the shape that makes them reviewable.unused-private-protocol) —_FixtureMarkeris the return type of_detection._get_fixture_marker, but the rule only looks inside the file that defines a protocol, so one private to the package rather than to the module reads as dead. Renaming it public would export an internal normalisation detail to satisfy a check.try-except-pass) — the intersphinx probe in_resolve_builtin_linkis opportunistic; a failure means the inventory has no usable answer, the same outcome as a miss, and the next statement supplies it from the static map. Logging it would emit a warning in every docs build that no docs author can act on.Formatting
README.md, docs/quickstart.md, docs/packages/sphinx-vite-builder/how-to.md, and the
gp-sphinx,sphinx-autodoc-fastmcp,sphinx-ux-badges, andsphinx-vite-builderpackage READMEs — Python snippets reformatted. Blank line after a module docstring, magic trailing comma expansion, and comment alignment. No prose or shell blocks touched, and no.pyfile needed reformatting.Docs
CHANGES — unreleased entry covering the toolchain bump, the default-set adoption, and the warning that running the formatter on an older ruff reverts the embedded Markdown snippets.
Design decisions
The 0.16.0 headline is the much larger default rule set, and an explicit
selectwas silently opting the workspace out of all of it.extend-selectlayers this project's own linters on top of the defaults instead of in place of them, which is the only way to get both.Expanding the explicit selection to whole prefixes instead was considered and rejected: it drags in all of
D,PL,S, and friends rather than the curated subset, and produces one to two orders of magnitude more findings.Every rule that fired got a decision — fixed, or ignored with the reason recorded in
pyproject.toml. Ignores are scoped to the files that need them; none is repo-wide. Each fix and each ignore is its own commit, so a reviewer can take them one at a time and revert any single decision.The Markdown formatting is the load-bearing part of the version bump itself:
ruff format . --checkruns in CI, so the embedded-snippet churn had to land or CI would go red on the next contributor's first push.The floor lives in the
devgroup only. Nothing in the published packages depends on ruff, so runtime consumers are unaffected.Test plan
uv run ruff check .— cleanuv run ruff format . --check— cleanuv run mypy .— cleanuv run pytest— full suite green