Skip to content

Enable ruff structure and import rules (INP, PLC, SLF, N, A, FAST, E) - #3920

Draft
anth-volk wants to merge 10 commits into
ruff-security-loggingfrom
ruff-structure-imports
Draft

anth-volk wants to merge 10 commits into
ruff-security-loggingfrom
ruff-structure-imports

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #3919

Summary

Phase 1 step 1.8, group 7: structure and imports. Stacked on #3915.

  • Test exemption (decided for this group): test files now also ignore SLF001, INP001 and PLC0415, alongside S101, D1, ANN and PLR2004. Tests deliberately reach into private members, pytest test directories aren't packages, and tests often import inside a test after patching the environment. Application code still gets every rule. api.quality.tests-ruff-exemptions is updated to match.
  • Rules: select gains A, E, FAST, INP, N, PLC and SLF. All 289 findings left after the exemption are fixed or kept with an inline reason.
  • FastAPI dependencies (FAST002, 21): ruff's own fix would break the v2 routes, which use from __future__ import annotations: FastAPI resolves string annotations against module globals, and the query dependencies were local closures. Each dependency is now a module-level alias instead, such as HouseholdCreateQueryDependency = Annotated[HouseholdCreateQuery, Depends(query_dependency(HouseholdCreateQuery))]. The OpenAPI is byte-identical.
  • Imports inside functions (PLC0415, 51):
    • Moved (8): imports that add no cycle, side effect or third-party load. A fresh-interpreter check of all 220 policyengine_api modules shows that policyengine_api.asgi, policyengine_api.api and 211 others load exactly the same modules. Seven route and dependency modules now also load the small, dependency-free readiness and fastapi_routes.dependencies modules.
    • Kept (43), each with a reason: heavy imports (PolicyEngine.py, country models, spm_calculator), Google clients, the v2 persistence stack (data.v2.database registers an exit hook at import), import-time I/O (simulation_entrypoint, specification, constants), one cycle, and two places where tests patch the name per call.
  • Namespace packages (INP001, 45): policyengine_api has no __init__.py on purpose, and .github, dashboard/experiments, migrations/v1, migrations/v2 and rules/fixtures aren't packages. They're declared in ruff's top-level namespace-packages setting, with a comment. Compared with the full ruff check output before the change, the only other difference is a hyphenated .github script name (N999), kept with a reason because a shell script runs it by that path. No __init__.py was added, and the built wheel and sdist contain the same files.
  • Names (N, A, SLF001): internal names are renamed: two constants in economy_comparison.py, the rules tool's BaseUnavailableError and assert_violation, SQLAlchemy's TimeoutError imported as PoolTimeoutError in tests, and 13 test function names that alone overflowed the line. Kept with a reason: country.py's class reform, since policyengine-core uses the class name as the reform key; two gRPC-stub Export methods in a test; and three private accesses with no public equivalent that tests patch.
  • Line length (E501, 149): wrapped. Every split string has identical text, which a syntax-tree comparison confirms. The two published Field descriptions that were split are unchanged. Kept with a reason (6 findings, 4 suppressions): a dotted module path, two GPT-4 prompts in the dashboard experiment, and a docstring line that is a script's --help text.
  • Registry: api.quality.ruff-lint now lists the seven families. The shared rules tool changed (wrapped lines and the renames above), so its hash is now f7e9b6462761; sim takes the same copy in Enable ruff structure and import rules (INP, PLC, SLF, N, A, FAST, E) policyengine-sim-api#799.

Testing

  • pytest tests/unit tests/contract, to_refactor and the dedupe integration test (the make test set): 2,420 passed, the same as the base. That run was before the stack was rebased onto the release commit; this branch differs from it only in that commit's CHANGELOG.md, pyproject.toml version, uv.lock and changelog fragment.
  • make typecheck-v2 is clean. mypy on the 65 changed files outside that gate gives the same output as the base.
  • ruff check and ruff format --check (0.15.12) are clean repo-wide.
  • Every Pydantic model schema, the FastAPI OpenAPI with every native router enabled, the /v2/openapi.json preview and the legacy spec are byte-identical to the base, with and without OLD_SIMULATION_GATEWAY_URL set.
  • The quality guards, the rules guard and the rules tool unittest (75 tests) pass.
  • CI/CD: no workflow changed. .github/find-api-model-versions.py gained only a suppression comment.
  • CI: this PR is above the bottom of its stack, so it runs only CI complete, which fails and says why. Its full run starts once the PRs below it merge.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #3917 October 14, 2026 13:55
@anth-volk anth-volk changed the title ruff structure imports Enable ruff structure and import rules (INP, PLC, SLF, N, A, FAST, E) Oct 14, 2026
@anth-volk
anth-volk force-pushed the ruff-structure-imports branch 3 times, most recently from a346195 to 0f717e8 Compare October 14, 2026 18:08
@anth-volk
anth-volk force-pushed the ruff-structure-imports branch 7 times, most recently from 74e5329 to 5b47c96 Compare October 14, 2026 22:56
anth-volk and others added 10 commits October 15, 2026 04:05
Tests deliberately reach into private members, pytest test directories
aren't packages, and tests often import inside a test after patching the
environment. Application code still gets every rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route modules use `from __future__ import annotations`, and FastAPI
resolves string annotations against module globals only, so the query
dependencies move from per-router closures to module-level Annotated
aliases. The OpenAPI documents are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename the purely internal names: the outcome bounds and labels in the
decile impact helpers, the rules tool's BaseUnavailableError and its
tests' assert_violation helper, a test's list of uncalculated variables,
the PostgreSQL UUID import, and the tests' SQLAlchemy TimeoutError
imports (now PoolTimeoutError, so they no longer shadow the builtin).

Keep, with reasons, the Reform subclass name that policyengine-core uses
as the reform key, the gRPC stubs' Export methods, and the three private
accesses that have no public accessor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine_api is a namespace package (its routes and libs directories
included), and .github, dashboard/experiments, the two Alembic
environments and rules/fixtures hold scripts and fixtures that are never
imported as packages. Listing them in ruff's namespace-packages changes
no other rule's result, except N999 on the hyphenated
.github/find-api-model-versions.py, which keeps its name because
update-policyengine-package.sh runs it by that path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move four imports out of functions: request_context now reads
get_runtime through the observability package it already imports, the
simulation entrypoint imports SPMValidationError at the top (spm was
already loaded), the four v2 resource routers import their default
service factories, and the route dependencies import is_ready. None
creates a cycle or loads a third-party or standard-library module that
was not already loaded; the ASGI and Flask entry points import exactly
the same modules as before.

The remaining deferred imports keep an inline reason: they load country
models, PolicyEngine.py, spm_calculator, Google clients, the v2
persistence stack (whose database module registers an atexit hook) or
the bundle manifest, break an import cycle, need configuration at
import, or are looked up per call so tests can replace them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Long strings are split by implicit concatenation, so every log message,
error message and published Field description keeps its exact text;
long comments are rewrapped or moved above the line they describe. Only
four docstrings change, none of them published: a Flask route's, two
internal functions' and a rule proof fixture's.

Three lines keep a suppression: a dotted module path that cannot be
wrapped, and the GPT-4 prompts and a script's --help summary, whose text
would change if wrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every split string keeps its exact text (the parsed code is unchanged),
so the rendered rules page and the AGENTS.md block are identical apart
from the tool hash. Two test docstrings are reworded to fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Split long strings (same text), rewrap comments and reword over-long
test docstrings. Thirteen test functions whose names alone overflowed
the line get shorter names; nothing references them. The economy
service tests reach the long-named reform-impact lookup mock through a
small hash_prefix_lookup helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select A, E, FAST, INP, N, PLC and SLF. The earlier commits in this
branch fix or explain every finding, so ruff reports none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
api.quality.ruff-lint now lists A, E, FAST, INP, N, PLC and SLF. The
rendered rules page also picks up the rules tool's new hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the ruff-structure-imports branch from 5b47c96 to b5f847b Compare October 15, 2026 00:06

This branch has not been deployed

No deployments
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.

Enable ruff structure and import rules (INP, PLC, SLF, N, A, FAST, E)

1 participant