Skip to content

fix: pin ruff and select the lint rule set explicitly - #6

Merged
leiverkus merged 2 commits into
mainfrom
fix/ruff-016-rule-drift
Jul 26, 2026
Merged

fix: pin ruff and select the lint rule set explicitly#6
leiverkus merged 2 commits into
mainfrom
fix/ruff-016-rule-drift

Conversation

@leiverkus

Copy link
Copy Markdown
Owner

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/ or tests/ had changed since. The dev extra requested ruff>=0.5, so CI installed ruff 0.16.0, which enables RUF100 (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.0 and declare select explicitly. 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: E402 directives in tests/. These never suppressed anything. Ruff exempts imports that follow pytest.importorskip(), so E402 does not fire at those sites — verified with --select E402 --ignore-noqa, which reports All 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: PLC0415 directives mark real violations, and lazy imports are load-bearing in this codebase: the package must import without the optional geo / model extras, and structura --help must 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:37 and seven in tests/conftest.py — all unambiguously deliberate (the cli.py one 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-noqa reports 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 files
  • pytest → 41 passed, 2 skipped

Note 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.md describes. The directives were kept and made enforceable instead.

🤖 Generated with Claude Code

leiverkus and others added 2 commits July 26, 2026 15:08
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>
@leiverkus
leiverkus merged commit 49e49b9 into main Jul 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant