Skip to content

Refactor the application package within ruff's complexity limits - #3933

Draft
anth-volk wants to merge 39 commits into
ruff-complexity-economyfrom
ruff-complexity-application
Draft

anth-volk wants to merge 39 commits into
ruff-complexity-economyfrom
ruff-complexity-application

Conversation

@anth-volk

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

Copy link
Copy Markdown
Collaborator

Fixes #3930

Summary

Phase 1 step 1.8, group 8b, part 2 of 3: the rest of policyengine_api/, refactored within ruff's complexity limits. Stacked on #3932.

  • All 89 findings in scope are fixed by refactoring, with no complexity or argument suppressions. Before each refactor, a commit adds characterization tests that pin current behaviour; 249 in all. A new tests/fixtures/recording_session.py records every statement, compiled for PostgreSQL, and every ORM write, in order.
  • ASGI shell (asgi_factory, complexity 34 and 113 statements):
    • Lifespan, exception handlers and router wiring move to module level.
    • Per-request work moves into named steps: request context, telemetry, the route call and the response.
    • A new test pins the exact order of runtime and logger calls.
  • Flask routes:
    • Household route errors are mapped by helpers that keep the old except precedence.
    • Saved-policy writes share configuration, mirror and failure steps.
    • Simulation identity checks move into their own step.
    • View signatures are unchanged.
  • v2 services and routes:
    • Household and policy failures map through ordered error tables (run_v2_operation).
    • Household document validation and v1 household translation are split into collection, record and membership steps.
    • ComparisonReportRecord.validate_state keeps its Pydantic signature, with its checks split into helpers.
    • The parameter-value listing route takes one frozen ParameterValueListingQuery dependency; its OpenAPI is byte-identical.
  • Data layer:
    • Catalog extraction splits into verification, selection and normalization steps, called in the same order.
    • Staging row generators move to module level.
    • Legacy user-policy persistence takes one revision value; a new test pins every statement and write in order.
    • Report and simulation runs take NewReportOutputRun, NewSimulationRun and ReportRunPackageVersions.
  • Migration logging and flags:
    • Route groups come from a path table.
    • A served request is logged from one ServedRequest value.
    • Explicit database sources are one value.
  • Mirroring and other services:
    • Mirror outcome loggers take one outcome value, and saved-policy mirror collaborators are grouped.
    • GatewayAuthTokenProvider takes GatewayAuthCredentials.
    • Two dead pieces are removed: policy_service._create_new_policy, which had no callers, and an always-true lock flag.
  • Magic numbers (25): each becomes a named constant or an HTTPStatus member, with messages unchanged.
  • Reconciled with Update PolicyEngine bundle to 6.3.4 #3918 (merged while this was in progress):

Testing

  • The make test set at this branch's tip: 2,848 passed, which is the base plus part 1's 166 and this PR's 249.
  • Behaviour comparisons against the base:
    • Parameter-value queries are identical.
    • 4,800 household translation and normalization outcomes are identical.
    • 63 catalog extraction mutations are identical.
  • make typecheck-v2 is clean. mypy on the 28 changed application files outside that gate gives identical output to the base.
  • ruff check, ruff format --check and the quality guards (migration-contracts, rules-registry, ci-scripts) pass.
  • The FastAPI OpenAPI, the /v2/openapi.json preview, the legacy spec and every existing model and adapter schema are byte-identical, with and without OLD_SIMULATION_GATEWAY_URL set.
  • Not run locally: tests/integration/test_v1_user_policy_dual_write.py needs MySQL and PostgreSQL; its 6 call sites were updated.
  • 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 #3917 October 14, 2026 18:47
@anth-volk anth-volk changed the title ruff complexity application Refactor the application package within ruff's complexity limits Oct 14, 2026
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch 2 times, most recently from 984feb4 to e599f21 Compare October 14, 2026 20:04
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch 6 times, most recently from 44ad18f to 30cb394 Compare October 15, 2026 00:06
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch from 30cb394 to 1cf7ff3 Compare October 15, 2026 15:21
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch 2 times, most recently from 1a1e093 to a92ce80 Compare October 15, 2026 17:20
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch from a92ce80 to 86ca615 Compare October 16, 2026 05:40
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch from 86ca615 to f9166fb Compare October 16, 2026 06:00
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch 3 times, most recently from fab7882 to 4a2abbc Compare October 16, 2026 06:59
Add characterization tests for the application functions that exceed
ruff's complexity and argument limits, covering the branches the existing
suite did not reach: the ASGI request telemetry order, migration request
records and route groups, the v1 household, saved-policy and simulation
route error paths, mirror metric log records, saved-policy event decoding,
comparison report state rules, household document and v1 translation
failures, catalog extraction failures and staging rows, report-run
creation, and the statements that map legacy households and saved
policies.

A small recording session double records each statement (compiled for
PostgreSQL) and ORM write in order, so the mapping tests pin the exact
queries and writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 29 commits October 16, 2026 11:05
The user-household and user-policy create routes now call a module helper
that checks the body and query countries agree and runs the creation, so
the router builders stay within the complexity limit. Route registration,
order and signatures are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parameter-value listing route reads its query into a frozen
ParameterValueListingQuery dependency, whose fields keep the route's
OpenAPI parameters byte-identical. The reader protocol and V2MetadataService
take a ParameterValueFilter (parameter, current, now) instead of three
keyword arguments, the service resolves it with parameter_value_selection,
and read_parameter_values takes the resolved ParameterValueSelection.
read_parameter_children reads the dialect from the session it is given, so
MetadataDatabaseSession no longer exposes dialect_name.

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

ComparisonReportRecord.validate_state keeps its signature and delegates to
module helpers: the report lifecycle check (with the successful-report
rules in their own step) and the comparison check, which dispatches on
comparison_status to the incomplete, completed or failed rules. The rules
run in the same order and raise the same messages.

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

normalize_household_document now checks the supported collections, the
saved SPM selection, each collection's records (identity, then contents)
and the people's memberships in separate helpers, in the same order and
with the same messages and key order. The two require_json_value
validators check a container's members in their own helper.

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

translate_legacy_household now checks the source collections, translates
each collection's groups (their values and member memberships) and the
people in separate helpers that share a _LegacyTranslation holding the
person ids, memberships and values read so far. Groups and people are read
in the same order and raise the same errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create_legacy_household_mapping takes the LegacyHouseholdSnapshot, which
supplies the v1 country, household id and API version, instead of those
three values as separate arguments. The insert statement is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read_user_household_page and read_user_household_rows take a
UserHouseholdListing (country, user, optional household, offset and limit)
instead of five keyword arguments; V2UserHouseholdService.list_user_households
keeps its signature and builds the listing. The query is unchanged.

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

LegacyUserPolicyRevision carries a v1 revision's country, saved-policy id,
reform label, fingerprint, source revision and changed fields through
persist_legacy_user_policy_mapping, apply_existing_legacy_user_policy_mapping,
validate_existing_legacy_user_policy_mapping and the mapping insert and
update connectors, which also take the projected association instead of its
user and policy ids. UserPolicyListing describes a requested page of
associations for read_user_policy_page and read_user_policy_rows;
list_user_policies keeps its signature. The statements, writes and their
order are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create_report_run takes a ReportRunPackageVersions (country package and
PolicyEngine.py versions) instead of two separate arguments; it validates
them in the same order and stores the same run.

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

normalize_json_value now dispatches through an ordered table of type
normalizers (float, enum, date, mapping, sequence) and unwraps scalars in
its own helper. _normalize_country checks the model identity, release
manifest, default dataset and region registry, normalizes parameter nodes,
selects each region's dataset and normalizes each region in separate
helpers, called in the same order so the same error is raised first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_catalog_rows now maps each staging table to a module-level generator
(model, variables, parameter nodes, parameters, parameter values, datasets
and regions) instead of seven nested closures. Each generator stays lazy
and yields the same rows.

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

_log_copy_operation and _log_mirror_event take the outcome, write sources,
destination result and failure category as one frozen outcome value
instead of five or six keyword arguments. The logged records are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mirror_pending_user_policy_events_after_commit takes its event service,
reform snapshot loader and mirror factory as one optional
UserPolicyMirrorCollaborators value; any collaborator left None still
takes its default. _log_mirror_operation takes the mirrored revision and
the mirror outcome as two values instead of nine keyword arguments. The
policy routes and the tests pass the new values; the logged records are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create_simulation_run and create_report_output_run take an optional
NewSimulationRun or NewReportOutputRun holding the run's status, trigger,
output, error, source run, spec snapshot, version manifest and fixed ID,
with the same defaults, instead of up to ten keyword arguments. The report
output service and the tests build the new values; the stored rows are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_report_spec takes an optional EconomyReportSettings instead of three
keyword arguments, with the same defaults; the spec check builds it from
the stored economy spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The simulation lookup was only ever called with for_update=True, so it is
renamed _lock_existing_simulation and always locks. PolicyService's
_create_new_policy had no callers and is removed.

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

_decode_mirror_event now validates the changed fields and the event type
against them in two module helpers, in the same order and with the same
messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GatewayAuthTokenProvider takes an optional GatewayAuthCredentials (issuer,
audience, client ID, client secret and secret resource) instead of five
positional arguments; any setting left None is still read from its
environment variable. The tests build the credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
find_unrecognized_household_inputs hands each known entity group to
_find_unrecognized_entity_inputs, which reports each variable through
_unrecognized_variable; findings keep their order. get_safe_json writes
infinities through _safe_json_number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calling app.exception_handler(...) on the module-level handlers, as the
nested decorators did, keeps their types acceptable to mypy; the handlers
are registered the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover the paths the refactor touched that no test reached: the 128
character catalog version limit, the one-date-range legacy period rule,
the metadata page bounds, the five-digit place FIPS code, enum values that
are also tuples or dates, and invalid migration flags in the log context.
The new limit and enum tests pass on the pre-refactor code too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3918 added _serialize_region_filter_value, which compares against the
literal 255 (PLR2004). Name it MAX_REGION_FILTER_VALUE_LENGTH, the size of
the catalog's filter_value column; the error message text is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Management API client compares the token response with HTTPStatus.OK
and retries a deletion after HTTPStatus.UNAUTHORIZED, as the gateway
token provider does. HTTPStatus is an IntEnum, so the comparisons are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
user_resource_operation maps each typed service failure #3891 added to
a status, message and headers, and logs persistence and unexpected
failures by event name only. The new characterization test pins all
eighteen cases, including subclasses that must match their base's
clause and an IntegrityError that must win over the SQLAlchemyError
clause, and passes on the current code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
user_resource_operation now runs the service call and hands any failure
to _user_failure_response, which answers with the first
UserResourceFailure in USER_RESOURCE_FAILURES that the error matches:
its status, the error's own or a fixed message, the 401's
WWW-Authenticate challenge, and a logged event for persistence
failures. The entries follow the order of the old except clauses, and
an unmatched failure is still logged and answered with the generic 500,
so each failure gets the same response and log record; the pinned
failure-mapping test passes unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user-report and user-simulation create routes call a module helper
that checks the body and query countries agree and runs the creation,
as the user-household and user-policy routes do. The /v2/me router
builds its signed-in-only dependency through _signed_in_user_dependency
and runs the upgrade through _upgrade_account. The three builders #3891
added stay within the complexity limit (C901); route registration,
order, signatures and the OpenAPI documents are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3891 added include_compute to get_migration_context, giving it six
arguments (PLR0913). Its db_entity and sim_flow overrides were never
passed: every caller, including the request logging and the tests,
lets the route group's configuration name them. The overrides go, so
the context takes the entity and flow from the route group and, with
include_compute=False, records no flow; every logged context is
unchanged.

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

The PLR0913 findings in the code #3891 added:

- The user-household and user-policy services, their route Protocols and
  routes take an optional UserHouseholdFilter or UserPolicyFilter (the
  v2 and v1 ids to narrow a listing to) instead of two keyword
  arguments, as the metadata layers take a ParameterValueFilter. The
  routes build the filter from the same query fields.
- read_user_report_rows and read_user_simulation_rows take a
  UserReportListing or UserSimulationListing, as the household and
  policy row readers take their listings.
- create_user_identity takes the UserPrincipal whose issuer, subject and
  email it stores, instead of the three fields.

The statements, their parameters and the OpenAPI documents are
unchanged. The route and service tests, and the concurrent sign-in
test's stand-in, use the new arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the ruff-complexity-application branch from 4a2abbc to 103c752 Compare October 16, 2026 07:05

This branch was successfully 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.

Refactor the application package within ruff's complexity limits

1 participant