Skip to content

Enable ruff annotation rules (ANN) - #3905

Draft
anth-volk wants to merge 12 commits into
masterfrom
ruff-annotations
Draft

anth-volk wants to merge 12 commits into
masterfrom
ruff-annotations

Conversation

@anth-volk

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

Copy link
Copy Markdown
Collaborator

Fixes #3904

Summary

Phase 1 step 1.8, group 3: annotations. Stacked on #3902.

  • Annotations: select gains ANN, and every function outside tests is now annotated: 252 findings in policyengine_api and the scripts, plus 16 in .github/.

  • Any (ANN401): replaced by a real type wherever one exists (56 of 63). Types used include object for opaque pass-through values, the existing JSONValue alias, CachedReformImpact, Starlette Scope, ArrayLike, and two new Protocols (CachePipeline, SPMCapabilitySource).

  • Remaining suppressions: each keeps an inline # noqa: ANN401 with a reason:

    • exact-type() JSON validators mypy can't narrow;
    • Session.exec overloads that reject core selects;
    • dataclasses.replace(**changes);
    • the TOML value from Enforcer.get;
    • incompatible Flask/a2wsgi WSGI stubs;
    • pass-through JSON values;
    • Pydantic model-class maps.

    One noqa: ANN201 is on a Pydantic wrap serializer, because annotating it would change the published OpenAPI schema.

  • Test-file exemptions: **/tests/** ignores S101, D1, ANN and PLR2004, as agreed for step 1.8. This is registered as api.quality.tests-ruff-exemptions.

  • Annotations checked against the code: I ran mypy over the whole package (beyond the configured v2 scope) and compared it with the base branch. Annotations that didn't match the code were corrected in the commit "Correct annotations that whole-package mypy shows were wrong". Whole-package mypy errors fell from 259 to 182.

  • Eight new whole-package errors remain:

    • Library typing: policyengine_core has no py.typed, and Connector.connect and numpy arithmetic return Any.
    • Two real code gaps, now visible because the functions are annotated:
      • Flask's view_args is optional, but the route indexes it.
      • get_execution_result accepts None, but its success path needs an execution.

    Fixing either needs a code change, which is out of scope for an annotation PR.

  • Runtime-annotation guard: the guard Enable ruff typing-only import rules (TC) #3902 added now resolves each model's bases the way ruff does: through the import in the model's own module, following bases defined in the same module. Ruff matches that import path, not where a base is defined, so a listed base imported through a re-export doesn't count. The old guard compared definition paths and would have missed that case. A new test pins the resolution, and an experiment against ruff 0.15.12 confirmed each case. No model in the package is affected today. The rule statement and docs/engineering/skills/typing-only-imports.md now describe the matching precisely.

  • PolicyEngine bundle 6.4.1 (replaces Update PolicyEngine bundle to 6.4.1 #3939): the Simulation API now runs 6.4.1 (chore(deps): update policyengine to 6.4.1 policyengine-sim-api#809). On 6.3.4, the Public API's "Ensure PolicyEngine bundle is supported by simulation API" check fails with "The us route resolves to a different worker application", which also blocks master's staging deploy. The bundle moves policyengine-uk from 2.126.12 to 2.130.1; US stays at 2.2.1.

    • Catalog counts reviewed and updated in test_v2_catalog_installed.py. UK adds 37 variables and removes one, and adds parameters for ESA and JSA remunerative work, the tax credits Pension Credit passport and the Universal Credit company-owner rules.
    • Removed input: policyengine-uk 2.129.0 removed uc_has_limited_capability_for_work in favour of uc_limited_capability_for_work. It joins DEPRECATED_VARIABLES, so a partner still sending it gets a warning naming the replacement instead of a failed calculation.

Testing

  • pytest tests/unit tests/contract: 2,358 passed. to_refactor plus dedupe: 53 passed.
  • Configured mypy is clean. ruff check/format are clean repo-wide.
  • All 234 Pydantic model schemas and the OpenAPI spec are identical to the base branch.
  • Guards and the rules tool unittest pass. scripts/rules is byte-identical with sim.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #3888 October 13, 2026 12:35
@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.86364% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.48%. Comparing base (e76065e) to head (34af5fd).

Files with missing lines Patch % Lines
policyengine_api/runtime_cache/core.py 22.22% 0 Missing and 14 partials ⚠️
policyengine_api/spm.py 66.66% 4 Missing and 1 partial ⚠️
policyengine_api/asgi_factory.py 62.50% 3 Missing ⚠️
policyengine_api/specification.py 57.14% 2 Missing and 1 partial ⚠️
...e_api/utils/payload_validators/validate_country.py 66.66% 2 Missing and 1 partial ⚠️
policyengine_api/worker_spm.py 57.14% 1 Missing and 2 partials ⚠️
policyengine_api/data/orm.py 50.00% 1 Missing and 1 partial ⚠️
policyengine_api/data/v2/catalog/extraction.py 60.00% 1 Missing and 1 partial ⚠️
...pi/fastapi_routes/v2/households/document_models.py 60.00% 1 Missing and 1 partial ⚠️
policyengine_api/libs/gateway_auth.py 60.00% 1 Missing and 1 partial ⚠️
... and 5 more
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #3905       +/-   ##
===========================================
+ Coverage   43.80%   85.48%   +41.68%     
===========================================
  Files         183      198       +15     
  Lines       11219    12412     +1193     
  Branches     1993     2266      +273     
===========================================
+ Hits         4915    10611     +5696     
+ Misses       5812     1100     -4712     
- Partials      492      701      +209     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@anth-volk
anth-volk removed this pull request from stack #3888 October 13, 2026 19:26
@anth-volk
anth-volk added this pull request to stack #3917 October 13, 2026 19:26
@anth-volk
anth-volk force-pushed the ruff-annotations branch 7 times, most recently from cd4765b to 4c0a7c6 Compare October 14, 2026 21:13
@anth-volk
anth-volk force-pushed the ruff-annotations branch 5 times, most recently from 1403bc2 to 28faa7d Compare October 15, 2026 00:06
Base automatically changed from ruff-typing-imports to master October 15, 2026 15:07
anth-volk and others added 8 commits October 15, 2026 19:20
Type the cache backend protocol with what callers rely on (string
envelopes in, opaque write acknowledgements out) and add a CachePipeline
protocol for the queued writes. Decoded envelope payloads are object,
because every consumer validates them. A SPMCapabilitySource protocol
covers both the HTTP entrypoint client and the release registry adapter
that validate_worker_spm accepts. ReformImpactCache.update keeps Any for
the keyword changes it forwards unchanged to dataclasses.replace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reform-impact service parameters take the cache's scalar types, with the
options hash optional on the write paths that receive it from
EconomicImpactSetupOptions. Policy JSON read from the v1 table is a
JSONValue. SPMSelection.serialize_selection stays without a return
annotation (noqa ANN201) because Pydantic would build the model's
serialization JSON schema from it and change the published OpenAPI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before-validators take and return object. The parameter-tree SQL helpers
take mapped columns and return typed column expressions. Three helpers
keep Any with a reason: the JSON validators and canonical encoders
dispatch on exact type() checks mypy cannot narrow, and read_rows passes
core SQLAlchemy selects that Session.exec's overloads do not declare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View decorators are generic over the wrapped view's result, so a view
returning dict | Response keeps that type plus the error Response.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/rules stays standard-library only. Enforcer.get keeps Any for
the raw TOML value that callers read by its shape. scripts/rules changes
need mirroring in policyengine-sim-api.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select ANN everywhere except tests, which need no annotations. The
annotation commits before this one fix every finding outside .github/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bump_version.py, find-api-model-versions.py and scripts/check_updates.py
(with a ChangelogEntry TypedDict). Annotations only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
api.quality.ruff-lint now includes ANN, and the **/tests/** per-file
ignore list (S101, D1, ANN, PLR2004) is registered as
api.quality.tests-ruff-exemptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruff treats a class as runtime-evaluated when a base resolves, through
the import in the class's own module, to a listed name, or when a base
defined in the same module is itself runtime-evaluated. It matches the
import path, not where the base is defined, so a listed base imported
through a re-export is not recognised. The guard compared definition
paths, so it would have passed such a subclass while ruff asked for its
annotation imports to be moved.

The guard now parses each model's module, resolves its bases through
the module's imports (including relative imports, attribute access and
subscripted generics), follows same-module bases, and fails with the
path to add. test_base_resolution_matches_ruff pins the resolution:
listed imports, module attributes, same-module chains, generics and
relative imports count; re-exports and unlisted third-party bases do
not. An experiment against ruff 0.15.12 confirmed each case. No model
in the package is affected today. The rule statement and the
typing-only-imports skill describe the matching precisely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot and others added 2 commits October 15, 2026 21:04
The 6.4.1 bundle moves policyengine-uk from 2.126.12 to 2.130.1; US
stays at 2.2.1. The installed-catalog compatibility test pins exact
entity counts so package changes get an explicit review:

- variables 7,294 -> 7,330: UK adds 37 and removes one;
- parameters 104,158 -> 104,171, parameter values 1,194,256 ->
  1,194,357 and parameter nodes 29,300 -> 29,306: UK adds parameters
  for ESA exempt work and remunerative work, JSA remunerative work,
  the tax credits Pension Credit passport and the Universal Credit
  company-owner rules.

policyengine-uk 2.129.0 removed the input
uc_has_limited_capability_for_work in favour of
uc_limited_capability_for_work. A partner still sending it would now
fail, so it joins DEPRECATED_VARIABLES: it is dropped with a warning
naming the replacement, as for medical_out_of_pocket_expenses.

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

This branch was successfully deployed

1 active deployment
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 annotation rules (ANN)

1 participant