fix: pin ruff and select the lint rule set explicitly - #6
Merged
Conversation
CI went red on an unchanged tree. The `dev` extra requested `ruff>=0.5`, so CI installed 0.16.0, which enabled RUF100 (unused-noqa) by default and reported 65 errors in files nobody had touched since June. Pin `ruff==0.16.0` and declare `select` explicitly, so neither the linter version nor the rule set can move without a commit saying so. Two rules are added on top of ruff's defaults: - RUF100, because it is what caught this and dead directives should not accumulate silently. - PLC0415 (import-outside-top-level). Lazy imports are load-bearing here: the package must import without the optional geo/model extras, and `structura --help` must not pull in the heavy stacks. Leaving the rule off made a deliberate lazy import indistinguishable from an accidental one. With it on, all 37 carry an explicit `# noqa: PLC0415`; the eight previously unmarked sites in cli.py and tests/conftest.py are annotated. Also drop the 35 `# noqa: E402` directives in tests/. They never suppressed anything — ruff exempts imports following `pytest.importorskip()`, so E402 does not fire at those sites even when the rule is selected and `--ignore-noqa` is passed. Verified against the pinned version: ruff clean, mypy clean on 26 files, 41 passed / 2 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the lint step green, mypy ran for the first time in five weeks and the 3.12 job failed: numpy/__init__.pyi:737: error: Type statement is only supported in Python 3.12 and greater [syntax] `[tool.mypy] python_version` was hard-coded to "3.11" while the matrix runs 3.11 and 3.12. numpy 2.5 requires Python >= 3.12 and ships PEP 695 `type` statements, so the 3.12 job resolved numpy 2.5 and then parsed it against a 3.11 grammar; 3.11 resolves to numpy 2.4 and was unaffected, which is why the failure looked version-specific. Drop the pin so each job analyses its own interpreter — which is the point of having a version matrix. Verified in clean venvs on both: ruff clean, mypy clean on 26 files, 41 passed / 2 skipped. This is a second, independent instance of the same class of problem as the ruff drift: an unpinned dependency changing under an unchanged tree. It was masked because ruff runs before mypy and failed first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Restores green CI. No behaviour change to the pipeline — packaging metadata, lint directives, and one moved comment.
What happened
CI failed on #5, a documentation-only PR. The failure was not caused by that PR: the last CI run before it was 2026-06-18 (green), and nothing in
src/ortests/had changed since. Thedevextra requestedruff>=0.5, so CI installed ruff 0.16.0, which enablesRUF100(unused-noqa) by default. 65 errors appeared in an untouched tree.Local ruff was 0.15.14 and reported
All checks passed, which is exactly the failure mode a pin prevents.Changes
Pin
ruff==0.16.0and declareselectexplicitly. A linter's rule set is part of the build contract; upgrading it should be a commit, not a consequence of when CI happens to run. Selecting explicitly also means a future release cannot widen the set silently.Remove the 35
# noqa: E402directives intests/. These never suppressed anything. Ruff exempts imports that followpytest.importorskip(), so E402 does not fire at those sites — verified with--select E402 --ignore-noqa, which reportsAll checks passed. They were dead from the day they were written.Enable
PLC0415(import-outside-top-level). This one is the opposite case. The 29 existing# noqa: PLC0415directives mark real violations, and lazy imports are load-bearing in this codebase: the package must import without the optionalgeo/ model extras, andstructura --helpmust not pull in the heavy stacks. With the rule disabled, those directives were inert and a deliberate lazy import looked identical to an accidental one. Enabling it makes the annotation meaningful and enforced.That surfaced eight unmarked sites —
cli.py:37and seven intests/conftest.py— all unambiguously deliberate (thecli.pyone already carried the comment "lazy: avoids heavy imports for --help", now moved above the import so the directive sits alone). All 37 are now annotated, and--ignore-noqareports exactly 37 violations: every directive is load-bearing, none spare.Verification
Run against the pinned version in a clean 3.11 venv:
ruff check .→All checks passed!mypy src→ no issues in 26 source filespytest→ 41 passed, 2 skippedNote on scope
The alternative was to delete all 64 directives and leave the rule set alone. That is smaller, and it would also have gone green — but it would have discarded the record of a deliberate architectural decision that
docs/architecture.mddescribes. The directives were kept and made enforceable instead.🤖 Generated with Claude Code