Skip to content

Refactor within ruff's complexity limits, then select every ruff rule (ALL) - #804

Merged
anth-volk merged 93 commits into
mainfrom
ruff-complexity
Oct 20, 2026
Merged

anth-volk merged 93 commits into
mainfrom
ruff-complexity

Conversation

@anth-volk

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

Copy link
Copy Markdown
Contributor

Fixes #803

Same-Repository Draft PR Check

  • This PR is a draft.
  • This PR is from a branch in PolicyEngine/policyengine-sim-api, not a personal fork.

Summary

Phase 1 step 1.8, group 8b, the last group: complexity refactors, then select = ["ALL"]. Stacked on #802. This completes step 1.8.

Every function over ruff's complexity limits is refactored, not suppressed. That includes PLR0913 (too many arguments): this PR adds no suppression. Each refactor is preceded by a commit that adds characterization tests pinning the current behaviour. Every commit passes ruff and the quality guards.

Suppression counts

Inline suppression comments Rule codes in them
#802 (85f690c, the base) 221 225
This PR (eef2427) 211 215
  • Moved, not new: the suppression comments this PR adds are seven that the refactors moved unchanged:
    • BLE001 in stage12_runtime/coordination.py (3) and stage12_runtime/simulation.py (1);
    • BLE001 and TRY400 (2) in the entry app's Stage 12 dispatch.
  • Removed (10 more): ten in-function import suppressions (PLC0415).
    • Seven are in src/modal/_image_setup.py, three in src/modal/v2_app.py. All of them exist on main.
    • The refactor had also added eight more in those two files before this update. None remains.
  • Still added: none.

Changes

  • Contract and persistence libraries:

    • ComparisonReportRecord.validate_state keeps its Pydantic signature and is split into report and comparison checks.
    • The live column-type check is split into enum and plain-type checks.
    • URI, version and name-length limits are named constants.
    • create_initial_batch_state takes the batch's version and Modal app name as one frozen BatchRoute(version, app_name), and has five arguments.
    • resolve_spm_selection takes the route's policyengine version, model version and route provenance as one frozen SPMRouteVersions, and has four arguments.
    • All their callers are in this repository: the gateway, the executor's batch scheduler and SPM normalization, and the tests. The libraries are path dependencies, and the only package this repository publishes is the generated API client. Their results are unchanged.
  • Observability library: init_simulation_observability drops service_version. None of its four callers or the tests passed it, and build_runtime falls back to the package version. Modal workers still pass a version through init_process_observability.

  • Entry:

    • create_app (complexity 52, 144 statements) keeps every route's name, decorators, parameters and registration order. The shared behaviour moves into _EntryRuntime, and Stage 12 error logs still record only the error type.
    • Settings validation is split into caller-auth, old-gateway and Stage 12 checks.
    • The Stage 12 adapter is split into eligibility checks and a report builder.
    • Stage 12 dispatch takes a prepared report, an origin and a worker version.
  • Gateway: active-state route resolution is split into pinned, latest and requested-version steps.

  • Executor: long argument lists become small frozen groups, including:

    • artifact key inputs;
    • baseline identity requests;
    • precompute function handles;
    • national-reduce inputs;
    • fan-out poll backoff;
    • qualification runners and tolerances;
    • Stage 12 coordinator invocation and dependencies;
    • child spawn requests;
    • worker seams.

    Stage 12 coordination (complexity over 10, 11 arguments, 109 statements) becomes a _ReportCoordination class with claim, plan, dispatch, wait and aggregate phases. The Stage 12 parity checks are split by entity, column and report value. The image artifact fetch is split into freshness, credential and download steps.

  • Executor imports:

    • src/modal/v2_app.py already imports policyengine_simulation_executor.stage12_runtime at module level, and that package's __init__ imports its submodules eagerly.
      • So the four in-function imports of the same package loaded nothing new. Their reason, "loaded in the worker container, not at deploy time", did not hold.
      • The Modal function bodies now call stage12_runtime.<name>. Tests that patch stage12_runtime.run_single_simulation or stage12_runtime.coordinate_report still take effect, because the attribute is looked up at call time.
    • src/modal/_image_setup.py imports json, logging, os, pathlib.Path and typing.TYPE_CHECKING at module level.
      • Modal runs these image-build functions from the function's mounted source package; they are not serialized.
      • stage12_cache_image.py, which also runs before add_local_python_source, already imports standard-library modules (json, os, pathlib, typing) at module level.
      • test_image_setup_module_stays_self_contained now allows only standard-library modules at module level (sys.stdlib_module_names), and still rejects any import of the executor package.
    • google.oauth2 and policyengine_uk join banned-module-level-imports, so their in-function imports need no suppression.
      • stage12_cache_image.py was the only application module importing google.oauth2 at module level; the import moves into _storage_client, which is the one function that uses it.
      • Its test patches google.oauth2.service_account.Credentials directly.
  • Code from Expose the certified dataset SHA-256 and assert the served identity at deploy #743, Gate releases on a numerical sentinel and make them reversible #744 and Release Stage 12 database sessions and keep original failures visible #789, which merged while this was in progress, is refactored the same way:

    • release_sentinel.check_deployment (JobPolling, a _Deployment with per-case and job-wait steps, HTTPStatus.OK).
    • The routing publisher's main: one command function per mode, with the same precedence. Its --help output is byte-identical.
    • restore_routing_plans.
    • The package updater's bundle_versions and verify_dispatch.
    • open_connection_with_retry (RetryTiming).
    • The workflow-expression and routing-command test helpers.

    Release Stage 12 database sessions and keep original failures visible #789's original-failure reporting and session release are kept inside the new coordination phases.

  • Rules tool: takes the Public API's group 8b refactor (Refactor tests, scripts and the rules tool, then select every ruff rule (ALL) policyengine-api#3934). Each repository owns its copy; this one is currently identical. Its tests went from 80 to 115.

  • ALL:

    • ruff.toml selects every rule, and ignores only the two that conflict with ruff format (COM812, ISC001) and one of each mutually exclusive docstring pair (D203, D213).
    • The per-file exemptions are unchanged.
    • ALL found nothing that wasn't already fixed.
    • sim.quality.ruff-lint lists ALL.
    • The rules-registry skill doc says every rule applies, and that a ruff upgrade PR fixes any new rule's violations.

Noticed, not changed: in release_sentinel, a job that fails but still returns an object result has its values compared anyway. The new tests pin that existing behaviour. It may be worth confirming it's intended.

Commits added on 2026-10-20 (eef2427)

Rebase. Rebased onto #802 at 85f690c. Two commits had conflicts with #802's new commits:

New commits:

  1. Drop init_simulation_observability's never-passed service_version.
  2. Pass create_initial_batch_state its route as one BatchRoute.
  3. Pass resolve_spm_selection the route's versions as one SPMRouteVersions.
  4. Call Stage 12 runtime functions through v2_app's module-level import.
  5. Import the image setup module's standard-library modules at module level.
  6. List google.oauth2 and policyengine_uk as lazily loaded packages.
  7. Update the changelog entry for the removed PLR0913 suppressions.

Testing

At eef2427, each project in an environment synced with uv sync --frozen --extra test --python 3.13:

Project Result
executor 1,540 passed, 22 skipped, 3 deselected
entry 618 passed
gateway 260 passed, 1 deselected
contract 249 passed
Stage 12 persistence 40 passed
observability 32 passed
fastapi 14 passed
  • Earlier work, still valid:
    • The characterization tests added by the refactors are 381 in all.
    • The grouping renamed 7 parametrized test IDs in the entry's deploy-script tests, and 34 in the gateway's SPM route tests.
    • The gateway golden OpenAPI test passes, with tests/golden unchanged. The entry, executor and gateway OpenAPI dumps were byte-identical to the base when last compared, before this update; this update changes no route or schema.
  • Lint: ruff 0.15.22 check --extend-select RUF100 . (with ALL) gives 0 findings, and format --check projects/*/src libs/*/src passes.
  • Rules tool and guards:
    • The rules tool's 115 unittests pass on Python 3.13.
    • scripts/run_quality_guards.py, with the base ruff-idioms-arguments, passes.
    • Rendering the rules leaves no diff.
  • Modal images: files in the executor and gateway images change, all inside function bodies, module helpers and module-level standard-library imports. src/modal/app.py is untouched.
  • CI/CD: no workflow changed, and no environment-variable read was added or changed; some reads only moved within changed files. The variables read in changed deployment code:
    • The image artifact fetch (_image_setup.py):
      • POLICYENGINE_VERSION, POLICYENGINE_US_VERSION or POLICYENGINE_UK_VERSION, POLICYENGINE_BUNDLE_RECEIPT and POLICYENGINE_DATA_FOLDER come from the image environment: .env(VERSION_ENV) in src/modal/app.py, applied before the fetch layer.
      • GOOGLE_APPLICATION_CREDENTIALS_JSON comes from the gcp-credentials Modal secret (environment main) attached to that layer.
    • The image-build cache fetch (stage12_cache_image.py):
      • GOOGLE_APPLICATION_CREDENTIALS_JSON comes from the stage12-evaluation-gcp-credentials Modal secret attached to the cache layer.
      • STAGE12_CACHE_MANIFEST_DIGEST comes from the deploy workflow's stage12_cache step. STAGE12_BUNDLE_MANIFEST_SHA256 and POLICYENGINE_DATA_FOLDER come from the image environment.
    • The v2 Modal functions read MODAL_ENVIRONMENT, unchanged, falling back to local. The repository doesn't set it for these functions, so its provider is unverified: it is expected from the Modal runtime.
    • Other changed functions: the release sentinel's SIMULATION_TEST_AUTH_* values come from the SIMULATION_ENTRYPOINT_* secrets through secrets: inherit. RELEASE_PAYLOAD is set from the check-policyengine-updates.yml step env:.
    • Checked: both Modal secrets exist (gcp-credentials in main and staging; stage12-evaluation-gcp-credentials in main, staging and testing). Which keys they hold wasn't checked.
  • CI: above the bottom of its stack, so only CI complete runs until the PRs below merge.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #755 October 14, 2026 18:50
@anth-volk anth-volk changed the title ruff complexity Refactor within ruff's complexity limits, then select every ruff rule (ALL) Oct 14, 2026
@anth-volk
anth-volk force-pushed the ruff-complexity branch 3 times, most recently from 96b676c to 358d65a Compare October 14, 2026 21:06
@anth-volk
anth-volk force-pushed the ruff-complexity branch 3 times, most recently from 257e32d to c0bea9c Compare October 15, 2026 14:06
@anth-volk
anth-volk force-pushed the ruff-complexity branch 5 times, most recently from ed9692c to 78a8e6c Compare October 16, 2026 05:12
@anth-volk
anth-volk force-pushed the ruff-complexity branch 2 times, most recently from ce079c6 to 0b1028a Compare October 16, 2026 06:06
@anth-volk
anth-volk force-pushed the ruff-complexity branch 3 times, most recently from 611b6e1 to 8e88dd1 Compare October 16, 2026 06:53
anth-volk and others added 25 commits October 20, 2026 18:08
… as one group

#781 landed on main after this stack branched. PLR0913, which this PR
enables, flags the segmented national runner's initializer once #781's
job progress reporter sits beside its runtime. The two now travel as one
SegmentedRunReporting, whose progress defaults to reporting nothing as
before; run_segmented_national_impl and dispatch_run_simulation keep
their signatures, and the previous commit's pins keep their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#712 landed on main after this stack branched. PLR2004, which this PR
enables, flags five magic values in its code; each is now a named
constant, with no behaviour change:

- the date-range period's two parts (_DATE_RANGE_PART_COUNT, the name
  simulation_runtime already uses);
- the segment poll's consecutive-error limit (CONSECUTIVE_POLL_ERROR_LIMIT,
  as in segmented_national);
- the national partition's 20 region groups
  (NATIONAL_PARTITION_GROUP_COUNT, next to US_REGION_GROUPS);
- worker validation's cache years, artifact count and probe year, from
  the cache precompute's PRECOMPUTE_YEARS: one dataset per year and one
  baseline per US region group per year make the 63 artifacts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… method

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags its eleven-argument _observe_simulations. It becomes
_ReportCoordination._observe_children, which takes the calls and
descriptors and reads the child plans, store, runtime, progress, stage
plan, clock, sleep and wait window from the coordination it belongs to.
The polling, failure and timeout logic is unchanged, and #712's runtime
tests cover every path through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one site

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags its seven-argument report_failure. The telemetry scope,
default error code, attributes and segment index that say where a
failure was caught now travel as one FailureSite; the error, runtime and
progress stay separate. The coordinator's three calls, the worker's and
the segment runner's pass the same values as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e group

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags Stage12SegmentRunner's nine-argument constructor. The
Modal module, region groups, sleep and the two poll intervals that tests
replace now travel as one SegmentRunnerSeams, whose defaults are the
deployed ones; the simulation, app name, runtime and cancellation check
stay separate. run_segmented_simulation passes nothing new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s one group

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags its eight-argument build_stage12_simulation. The country
module, region, dataset selection and loaded dataset the worker resolves
in its own stages now travel as one ResolvedSimulationInputs. The builder
reads the scoping strategy from the region, as _build_simulation already
does, so it no longer takes the region code it only deleted. The SPM
parameters, normalised policy and execution input stay separate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e group

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags the cache precompute's seven-argument run_precompute. Its
five Modal function handles now travel as one CachePrecomputeFunctions,
as the baseline precompute's PrecomputeFunctions already do; the bucket
and echo stay separate. The Modal app's entrypoint passes the same five
functions, and its secrets and environment are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ript

#712 landed on main after this stack branched. PLR0913, which this PR
enables, flags the six-argument ScriptedChildCall its runtime tests use.
The result, wait error, cancel error and during-wait hook that script a
call's outcome now travel as one ChildCallScript; the call ID and clock
stay separate. Both constructions script the same outcomes as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#712 landed on main after this stack branched. Before its eight-return
qualifying_cache_identity is split, pin the ineligible cases its tests
did not reach: no or a non-region-group scoping strategy, a missing,
empty or non-list region group, a non-state or non-text region code, and
a scope without a cache key. Each gets no cache identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and scope checks

PLR0911 flags the eight returns of qualifying_cache_identity. It now
asks three questions: is this a current-law baseline on the default
dataset, what is its group of state regions, and what is the
region-group scope's cache key. Each check is pure, so asking them in
this order gives the same result; the previous commit's pins and
#712's identity tests keep their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… added

#712 landed on main after this stack branched. Before its C901
verify_determinism_impl is split, pin what its tests did not reach: every
kind of frame difference (an entity missing on one side, a column on only
one side, a dtype change and a value change) in its sorted order, the
rejection of a probe that is not a planned baseline, and that the stored
baseline is downloaded only when it is not already in the data folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C901 flags verify_determinism_impl. The comparison of cached and fresh
frames moves into _frame_differences, which lists each difference in the
same sorted order; the probe check, download, cache-hit check and pass
record stay in verify_determinism_impl. The previous commit's pins keep
their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#712 landed on main after this stack branched. Before its C901
merge_segment_results is split, pin the checks its tests did not reach:
a result set that omits a segment, a result naming another region group,
segments with different entities or column dtypes, and an entity without
its identifier. The serializer refuses frames without an identifier, so
that pin decodes every segment to one directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… merge steps

C901 and PLR0912 flag merge_segment_results. It now indexes the results
(rejecting an incomplete set or a duplicate index), decodes each planned
segment in partition order (rejecting an omitted segment or one naming
another role or region group), and merges each entity's frames
(rejecting different entities, dtypes, a missing identifier or duplicate
IDs), each in its own function. The checks and their order are
unchanged, and the previous commit's pins keep their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#712 landed on main after this stack branched and extended
run_single_simulation, which C901 and PLR0915 now flag. Before it is
split, pin the paths its tests did not reach: a simulation that is no
longer active is refused, a claim another writer won stops the run as
cancelled, and a failed status read in the cancellation check is logged
by error type only and treated as not cancelled, so the run completes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ord steps

C901 and PLR0915 flag run_single_simulation, which #712 extended with
segmented calculation and cancellation. It now reads as its phases: claim
the simulation (refusing one that is no longer active or that another
writer claimed), build the cancellation check, write the input,
calculate (segmented, standard or injected, always as a
SimulationCalculation), write the artifact, and record success or
failure, each phase but the writes in its own function. The order of
reads, writes and reports is unchanged, and the previous commit's pins
keep their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#712 landed on main after this stack branched. Before its C901, PLR0912
and PLR0915 fetch_stage12_cache is split, pin what its two tests did not
reach: each manifest rejection (no bucket or manifest, schema, bundle,
year set, artifact list, artifact count and deployment digest), each
artifact rejection (a non-object entry, a filename with a directory, a
duplicate filename, a non-integer size and a same-size file with another
digest, which leaves no partial file), and the storage client built with
no credentials, a credentials object or credentials encoded as a string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d file steps

C901, PLR0912 and PLR0915 flag fetch_stage12_cache. It now validates the
whole manifest first (schema, installed bundle and years; the artifact
list and its counts; then the deployment digest), builds the storage
client from the deployment's credentials only when none is given, and
fetches each artifact through a partial file. The checks, their order
and the environment variables it reads are unchanged; the module stays
self-contained for the image build. The previous commit's pins keep
their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
None of the four callers (the entry app, the gateway app and its test
factory, and the executor's FastAPI app) or the tests passes
service_version, and build_runtime falls back to the package version when
it is absent. Without it the function takes five arguments, so its
PLR0913 suppression goes. Modal workers still pass service_version
through init_process_observability.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The batch's reported version and its Modal app name always travel
together: the gateway takes both from its route resolution, and the batch
scheduler from its batch context. create_initial_batch_state now takes
them as one frozen BatchRoute(version, app_name), so it has five
arguments and needs no PLR0913 suppression. The stored batch state is
unchanged. The gateway, the scheduler and the tests pass a BatchRoute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The policyengine.py version, the model version and the route provenance
are read only together, to decide whether a route with no SPM capability
predates canonical SPM. resolve_spm_selection now takes them as one
frozen SPMRouteVersions, so it has four arguments and needs no PLR0913
suppression. Its decisions are unchanged. The gateway, the executor and
the tests pass an SPMRouteVersions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2_app.py already imports policyengine_simulation_executor.stage12_runtime
at module level, and that package's __init__ imports its submodules
eagerly, so the four in-function imports of the same package loaded
nothing new and each needed a PLC0415 suppression. The Modal function
bodies now call stage12_runtime.<name> instead. Attribute lookups happen
at call time, so tests that patch stage12_runtime.run_single_simulation
or stage12_runtime.coordinate_report still take effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Splitting fetch_artifacts into six functions gave each one its own
in-function imports of json, logging, os and pathlib.Path, raising the
module's PLC0415 suppressions from 7 to 14. Those modules are always
importable in the image-build step: Modal imports this module from the
function's mounted source package (the function is not serialized), and
stage12_cache_image.py, which also runs before add_local_python_source,
already imports json, os and pathlib at module level. The module now
imports them, and typing.TYPE_CHECKING, at module level, which removes
twelve suppressions.

test_image_setup_module_stays_self_contained now allows module-level
imports of standard-library modules only (sys.stdlib_module_names), and
still rejects any import of the executor package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add both to banned-module-level-imports, so ruff keeps them out of
module-level imports (TID253) and accepts their in-function imports
without a PLC0415 suppression. That removes the two remaining
suppressions in _image_setup.py.

stage12_cache_image.py was the only application module that imported
google.oauth2 at module level; the import moves into _storage_client,
the one function that uses it. The storage-client test now patches
google.oauth2.service_account.Credentials directly, as the image setup
tests already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk marked this pull request as ready for review October 20, 2026 16:37
@anth-volk
anth-volk requested a review from a team as a code owner October 20, 2026 16:37
@anth-volk
anth-volk merged commit 21158d4 into main Oct 20, 2026
14 checks passed
@anth-volk
anth-volk deleted the ruff-complexity branch October 20, 2026 16:38
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.

Refactor within ruff's complexity limits, then select every ruff rule (ALL)

1 participant