Skip to content

Adopt pylint, df12-python-lints, and stricter Ruff for Python sources - #542

Draft
leynos wants to merge 4 commits into
mainfrom
python-linting
Draft

Adopt pylint, df12-python-lints, and stricter Ruff for Python sources#542
leynos wants to merge 4 commits into
mainfrom
python-linting

Conversation

@leynos

@leynos leynos commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Netsuke's Python surface — the helper scripts under scripts/ and the workflow
contract tests under tests/workflow_contracts/ — was linted only in part.
make spelling-helper-test ran Ruff in isolated mode over eleven named files,
and nothing at all covered scripts/hoist_binstall_*.py or
tests/workflow_contracts/. Nothing ran Pylint, and the repository had no
pyproject.toml.

This change adopts the estate-wide Python lint stack and clears every finding it
raises.

  • A root pyproject.toml replaces ruff.toml. It carries the stricter shared
    Ruff configuration — preview mode, the ASYNC, D, and DOC rule groups,
    the NumPy docstring convention, the banned deprecated-typing-generics table,
    and the mccabe and Pylint design limits — plus the [tool.pylint.*] message
    control tables. It deliberately declares no [project] table and no
    [build-system]: Netsuke publishes a Rust crate, so nothing here may build a
    Python distribution, and uv never treats the repository as a Python project.
  • make lint gains a lint-python prerequisite that runs a repository-wide
    ruff check, Pylint on PyPy through the pylint-pypy shim, the df12 house
    lints and their separate future-annotations pass on CPython 3.14, and
    ambrleaks over tests. Every pass runs through uv tool run, so a Rust
    contributor never needs a project virtual environment.
  • make check-fmt and make fmt gain the pinned ruff format pass.
  • All 118 findings the new gates raised are fixed at source. Four rule families
    are suppressed at the site with a recorded reason, and one is disabled
    repository-wide with a recorded reason; see Notes.

Review walkthrough

Start with the configuration, then the two substantive source changes, then the
mechanical remainder.

  1. pyproject.toml
    — the new configuration. Note the header comment explaining the absent
    [project] table, and the DOC502 entry in extend-ignore with its
    rationale.
  2. Makefile
    — the RUFF, PYLINT, DF12_PYLINT, DF12_FUTURE_ANNOTATIONS, and
    AMBRLEAKS definitions, the new lint-python target, and the --no-project
    guards added to the plain uv run invocations so the new pyproject.toml
    cannot pull a virtual environment into a working tree.
  3. scripts/typos_rollout.py
    — the facade now re-exports by name rather than by assignment, marks the
    re-exports it does not itself use with redundant X as X aliases, and drops
    four re-exports (_CacheTargets, _RemoteResponse, _HttpsRedirectHandler,
    _HTTPS_OPENER) that nothing referenced.
  4. scripts/hoist_binstall_archives.py
    — the rollback path chains the combined failure group from the rollback
    failure instead of suppressing the cause outright. The group still carries
    both exceptions, the deliberate BaseException catch is unchanged, and the
    docstrings now name BaseExceptionGroup, which is what the code raises.
  5. tests/workflow_contracts/ci_lint_test.py
    — the three substring probes in the gawk-staging test are consolidated into
    one assertion over the required fragments. Coverage is unchanged, and a
    failure now names every missing fragment rather than stopping at the first.
  6. scripts/tests/test_typos_rollout_refresh.py
    and
    scripts/tests/test_typos_rollout_hardening.py
    — every assertion gains a failure message naming the contract it broke.
  7. scripts/typos_rollout_http.py
    type statements for the two callable aliases, and full Returns and Raises
    sections on _HttpsRedirectHandler.redirect_request.
  8. The remainder is mechanical: exception messages bound to locals, deprecated
    typing generics and explicit member imports replaced with module imports,
    annotation-only imports moved into type-checking blocks, a redundant ...
    removed from a Protocol body, and operator.itemgetter in place of a lambda.

Validation

Run sequentially against a shared build cache.

Command Result
make check-fmt pass — cargo fmt --check clean, 20 Python files already formatted
make lint pass — Clippy, rustdoc, Whitaker, Ruff, PyPy Pylint 10.00/10, df12 lints 10.00/10, C9112 10.00/10, ambrleaks clean
make typecheck pass
make test pass — 1700 nextest tests run, 1700 passed, 1 skipped; doctests 59 + 29 + 1 passed
make test-workflow-contracts pass — 45 passed
make markdownlint pass — spelling helpers 34 passed at 92.05% coverage, typos clean, 76 Markdown files, 0 errors

Notes

  • DOC502 is disabled repository-wide, with the reason recorded in
    pyproject.toml. The rule rejects any Raises entry the function does not
    raise with a literal raise statement, which would have forced these helpers
    to delete twenty-one accurate entries documenting the exceptions they
    propagate from the filesystem, TOML, and subprocess calls they make. That
    propagation is the contract a caller must handle. DOC501 remains enabled, so
    the stale-documentation risk is still covered from the other direction.
  • Five site-local suppressions, each carrying its reason inline as the
    suppression lint requires:
    • S404, S603, and S607 in scripts/typos_rollout_check.py and
      scripts/tests/test_typos_rollout_check.py, where the only subprocess is a
      fixed git argv with no shell. git resolves through PATH deliberately,
      so the check uses the same Git the developer or CI runner invokes.
    • S310 in scripts/typos_rollout.py, where the transport entry point is
      compared and forwarded but never opened, and in
      scripts/typos_rollout_http.py, where the HTTPS scheme audit S310 asks for
      is the guard on the preceding lines and _HttpsRedirectHandler re-applies
      it to every redirect target.
    • SIM115 in scripts/typos_rollout_cache.py, where the temporary path must
      be bound before the with block so the finally can remove it after the
      rename. The stream is closed by that with.
  • --preview added to the isolated Ruff format check in
    spelling-helper-test. The gate stays --isolated, so the spelling helpers
    remain provably self-contained, but the flag must match the preview = true
    in pyproject.toml or the two formatters disagree on how to lay out a sole
    dictionary argument.
  • The 400-line module cap bites. max-module-lines = 400 put
    scripts/typos_rollout.py and scripts/typos_rollout_http.py over the limit
    once the required documentation was added; both were within a dozen lines of
    the cap already. They were trimmed back under it rather than the cap being
    raised, but both are now close enough that the next addition to either will
    need a split.
  • C9112 is a no-op today. The df12 future-annotations pass reads
    py-version = "3.13" from pyproject.toml, and the message only fires on a
    3.14 or later baseline. It is wired in now so it starts reporting the moment
    the baseline moves.
  • .gitignore gains .venv/ and uv.lock, so a stray uv run without
    --no-project cannot leave either behind.

Summary by Sourcery

Adopt shared Python linting and formatting configuration via pyproject.toml and integrate it into the existing Makefile workflows, updating helper scripts, tests, and documentation to comply with the new lint rules.

Enhancements:

  • Add repository-wide Ruff and Pylint configuration in pyproject.toml, including stricter rule sets, NumPy docstring conventions, and deprecated-typing-generic bans.
  • Integrate Python linting and Ruff-based formatting into make lint, make fmt, make check-fmt, and spelling-helper-test targets, using uv-managed tools and pinned versions.
  • Refine hoist_binstall_archives rollback error handling to preserve and expose both the original move and rollback failures via a BaseExceptionGroup chain.
  • Clarify typos_rollout facade exports and network boundary handling, and improve type hints and protocol definitions across helper modules.
  • Tighten tests and helper code style by switching to TYPE_CHECKING-only imports, operator.itemgetter, and clearer assertions and error messages in tests and workflow contract checks.

Documentation:

  • Update design documentation to describe the new pyproject-based Python tooling configuration and its relationship to uv and the Rust-focused build.

Tests:

  • Strengthen Python tests by adding explicit assertion messages, consolidating repeated expectations, and improving workflow contract tests around gawk staging, hoist rollback, and mutation-testing workflow paths.

Chores:

  • Ignore uv virtualenv artefacts in version control and remove the legacy Ruff configuration file.

leynos added 4 commits August 7, 2026 11:14
Netsuke's Python surface — the helper scripts under `scripts/` and the
workflow contract tests — was linted only in part. `make spelling-helper-test`
ran Ruff over eleven named files in isolated mode, and nothing at all covered
`scripts/hoist_binstall_*.py` or `tests/workflow_contracts/`.

Replace the root `ruff.toml` with a `pyproject.toml` carrying the stricter
shared configuration: preview mode, the ASYNC, D, and DOC rule groups, the
NumPy docstring convention, the banned deprecated-typing-generics table, and
the mccabe and Pylint design limits. The file deliberately declares no
`[project]` table and no `[build-system]`, so nothing can build a Python
distribution from this repository and uv never treats it as a Python project.

Add three lint passes to `make lint` via a new `lint-python` target: a
repository-wide `ruff check`, Pylint on PyPy through the pylint-pypy shim, and
the df12 house lints (both the main message set and the separate
future-annotations pass) on CPython 3.14 through `uv tool run`, so no
contributor needs a project virtual environment. `ambrleaks` scans `tests` for
unredacted snapshot values. `make check-fmt` and `make fmt` gain the pinned
`ruff format` pass.

The existing isolated Ruff runs gain `--preview` so they agree with the
repository configuration, and the plain `uv run` invocations gain
`--no-project` so the new `pyproject.toml` cannot pull a virtual environment
into a Rust contributor's working tree.
Bring every helper script, workflow contract test, and the vendored Cyclopts
stub up to the newly enforced rule set.

The substantive changes are the facade in `scripts/typos_rollout.py` and the
rollback path in `scripts/hoist_binstall_archives.py`. The facade now
re-exports by name rather than by assignment, declares `__all__`, and drops
four re-exports (`_CacheTargets`, `_RemoteResponse`, `_HttpsRedirectHandler`,
and `_HTTPS_OPENER`) that nothing referenced. The rollback path chains the
combined failure group from the rollback failure instead of suppressing the
cause outright, which keeps the traceback honest and satisfies the blind-except
rule without weakening the deliberate `BaseException` catch. The documented
exception type there was `ExceptionGroup` where the code raises
`BaseExceptionGroup`; the docstrings now match.

The remainder is mechanical: exception messages bound to locals, deprecated
typing generics and explicit member imports replaced with module imports,
annotation-only imports moved into type-checking blocks, a redundant `...` in a
Protocol body removed, an `operator.itemgetter` in place of a lambda, and full
Returns and Raises sections on `_HttpsRedirectHandler.redirect_request`.

Four rules are suppressed at the site with a recorded reason: `S404`, `S603`,
and `S607` where the helpers run a fixed `git` argv without a shell; `S310`
where the transport entry point is compared or forwarded rather than opened,
and where the HTTPS guard S310 asks for sits on the line above; and `SIM115`
where the temporary path must be bound before the `with` block so the `finally`
can remove it after the rename. `DOC502` is disabled repository-wide, because
these helpers deliberately document the exceptions they propagate.
Bind the whole subject in the string branch of the archive-resolution match so
the pattern reads as a capture rather than a positional sub-pattern, and test
for an empty glob result by truthiness rather than by comparison with a list
literal.

Trim `typos_rollout.py` and `typos_rollout_http.py` back under the 400-line
module cap that the previous commit's documentation additions had pushed them
over. The facade now marks its unused re-exports with redundant `X as X`
aliases instead of carrying an `__all__` list, which says the same thing in
one line each.
Give every assertion in the spelling-rollout suites a failure message, so a
red test names the contract it broke rather than printing a bare comparison.

Consolidate the three substring probes in the gawk-staging contract test into
one assertion over the required fragments. The test still covers all three,
reports every missing fragment at once instead of stopping at the first, and
no longer reads as the repeated-substring pattern a snapshot would serve
better.

Declare the two callable aliases in `typos_rollout_http` with `type`
statements, and move the explanations for the remaining `noqa` directives onto
the suppressed lines themselves, which is where the suppression lint looks for
them.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e8fd6a67-716c-4b57-a91f-5f43bb91ca56

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adopts a unified Python linting stack (Ruff, Pylint, df12-python-lints, ambrleaks) via a new pyproject.toml and Makefile wiring, then updates Python scripts and tests to satisfy the stricter rules while making a few small behavioral and clarity improvements in typos rollout and binstall hoist logic.

Sequence diagram for hoist rollback BaseExceptionGroup chaining

sequenceDiagram
    participant Caller
    participant hoist
    participant _move_all
    participant _rollback_completed_moves

    Caller->>hoist: hoist(dist_dir, staging_config, manifest, version)
    hoist->>_move_all: _move_all(dist_dir, located)

    alt move succeeds
        _move_all-->>hoist: return
        hoist-->>Caller: return
    else move fails
        _move_all->>_move_all: failure = caught OSError
        _move_all->>_rollback_completed_moves: _rollback_completed_moves(completed)
        alt rollback succeeds
            _rollback_completed_moves-->>_move_all: return
            _move_all-->>hoist: raise failure
        else rollback fails
            _rollback_completed_moves-->>_move_all: raise rollback_failure
            _move_all->>_move_all: raise BaseExceptionGroup(msg, [failure, rollback_failure]) from rollback_failure
            _move_all-->>hoist: raise BaseExceptionGroup
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce repository-wide Python tooling configuration and wire it into the build/lint pipeline.
  • Replace ruff.toml with a root pyproject.toml configuring Ruff (preview mode, doc rules, banned typing generics, NumPy docstrings) and Pylint (design limits, message set, py-version) without declaring a Python project.
  • Pin Ruff and define Makefile variables/targets for Ruff, PyPy-backed Pylint via a shim, df12 Python lints (including future-annotations), and ambrleaks, all invoked through uv tool run.
  • Add a lint-python target, make lint depend on it, and extend fmt/check-fmt and spelling-helper-test to run Ruff format/check consistently, using --no-project where needed to avoid creating virtualenvs.
pyproject.toml
Makefile
docs/netsuke-design.md
.gitignore
ruff.toml
Refine typos rollout facade and HTTP/cache internals for clearer contracts, type usage, and security/auditing compliance.
  • Refactor scripts/typos_rollout.py to re-export cache/HTTP symbols via named imports (with explicit X as X aliases) instead of assignment, drop unused re-exports, and guard the default urllib opener with a stored callable and explicit forwarding.
  • Update typos_rollout_http to use PEP 695-style type statements for callable aliases, expand _HttpsRedirectHandler.redirect_request docstring with Returns/Raises sections, and annotate security-sensitive urllib.request.Request creation with Ruff suppressions.
  • Tighten typos_rollout_cache.RemoteResponse Protocol by removing a redundant ellipsis, and adjust atomic_write to bind the temp file path before the context manager with a documented SIM115 suppression.
  • Adjust generate_typos_config and cyclopts stub types to reflect re-exported exception names and use collections.abc.Callable for callback signatures.
scripts/typos_rollout.py
scripts/typos_rollout_http.py
scripts/typos_rollout_cache.py
scripts/generate_typos_config.py
cyclopts/__init__.pyi
scripts/tests/test_typos_rollout.py
Clarify binstall hoist error semantics and make tests compatible with new lint rules.
  • Change hoist_binstall_archives docstrings to name BaseExceptionGroup and adjust the rollback failure path to raise a BaseExceptionGroup chained from the rollback failure while preserving both exceptions.
  • Use dataclasses imported as dc in discovery code, improve pattern matching type guards, and add operator.itemgetter-based uniqueness in generated hoist tests instead of lambdas.
  • Reorder imports and add TYPE_CHECKING-only Path and pytest imports in workflow_contracts hoist tests and rollback tests to satisfy type-checking and linting rules.
scripts/hoist_binstall_archives.py
scripts/hoist_binstall_discovery.py
tests/workflow_contracts/hoist_binstall_archives_generated_test.py
tests/workflow_contracts/hoist_binstall_archives_test.py
tests/workflow_contracts/hoist_binstall_rollback_test.py
Strengthen tests and workflow contract checks with better failure messages and consolidated assertions while complying with security/static-analysis rules.
  • Augment typos rollout tests (refresh and hardening) with explicit assertion messages describing the contract being validated, adjust exception construction to bind messages to locals, and ensure temporary files are removed after failure.
  • Consolidate gawk staging assertions in ci_lint_test into a single check that reports all missing fragments, and add explicit AssertionError messages for malformed workflows.
  • Annotate subprocess usage in typos_rollout_check and its tests with security justifications, switch to typing.TYPE_CHECKING for runtime-only imports, and tweak helper functions to use cabc.Sequence types.
  • Adjust various tests to move imports under TYPE_CHECKING, reflow long paths, and ensure pytest fixtures and assertions match the new lint expectations.
scripts/tests/test_typos_rollout_refresh.py
scripts/tests/test_typos_rollout_hardening.py
tests/workflow_contracts/ci_lint_test.py
scripts/typos_rollout_check.py
scripts/tests/test_typos_rollout_check.py
scripts/tests/conftest.py
tests/workflow_contracts/mutation_testing_test.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Failed
Enforce advisory code health rules (1 file with Large Method)

Our agent can fix these. Install it.

Gates Passed
5 Quality Gates Passed

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
test_typos_rollout_refresh.py 1 advisory rule 10.00 → 9.60 Suppress

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Comment on lines +167 to +169
assert timeout == pytest.approx(30.0), (
"the network boundary was called without the 30s timeout"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Large Method
test_http_refresh_scopes_validators_and_preserves_newer_cache has 72 lines, threshold = 70

Suppress

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