Skip to content

Fix apis-integ's Pyright errors and enforce every Pyright rule - #877

Draft
anth-volk wants to merge 4 commits into
pyright-enforcefrom
pyright-apis-integ
Draft

anth-volk wants to merge 4 commits into
pyright-enforcefrom
pyright-apis-integ

Conversation

@anth-volk

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

Copy link
Copy Markdown
Contributor

Fixes #876

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

Top of the Pyright stack, on top of #875. Fixes apis-integ's 4 Pyright errors without changing a request the integration tests send, then makes sim.quality.pyright-apis-integ enforced and deletes the last Pyright baseline. After this PR every package's Pyright rule is enforced with zero errors.

This PR is separate from #875 on purpose. It depends on the generated client's underscore-prefixed functions (see "Trade-off"). If that's not acceptable, dropping this PR leaves the stack consistent, with apis-integ ratcheted at 4.

The errors

The generated client types the four endpoints that declare bearer authentication (POST /simulate/economy/comparison, GET /jobs/{job_id}, POST /simulate/economy/budget-window and GET /budget-window-jobs/{batch_job_id}) as sync_detailed(*, client: AuthenticatedClient, ...). The tests' client fixture is Client | AuthenticatedClient: a plain Client, with no Authorization header, when no access token is configured, as in CI against local services.

The fix

tests/simulation/endpoint_calls.py has one function per such endpoint. Each takes either client and runs exactly the three statements of the generated sync_detailed:

request = endpoint._get_kwargs(...)
response = client.get_httpx_client().request(**request)
return endpoint._build_response(client=client, response=response)

_get_kwargs and _build_response already accept AuthenticatedClient | Client, so nothing is cast or suppressed. The call sites in conftest.py and test_calculate.py use these functions. The two polling tests that replaced sync_detailed with a stub now replace endpoint_calls.job_status.

test_endpoint_calls.py (8 tests) records each request through an httpx mock transport:

  • with an AuthenticatedClient, each function sends the same method, URL, headers and body as the endpoint's own sync_detailed;
  • with a plain Client, the same request without Authorization.

So the headers the tests send don't change, and a generator change that broke the equivalence would fail that test.

Trade-off, and the other options

_get_kwargs and _build_response are private to the generated endpoint modules. openapi-python-client's templates have emitted them for a long time, but a generator upgrade could rename them; test_endpoint_calls.py would then fail. The alternatives I considered:

Option Behaviour change
This PR: call the generated request and response helpers directly None (verified by the recording test); depends on generated private names
Always build an AuthenticatedClient, with an empty token when none is configured Sends Authorization: Bearer (empty token) to local services that run without authentication
Declare the entrypoint's bearer security optional in its OpenAPI spec, so the generator accepts either client Changes the public OpenAPI document and the published client's signatures, and misdescribes production, which requires authentication
A subclass of AuthenticatedClient that adds no header Reimplements the generated client's httpx construction; any drift changes requests silently
Leave the rule ratcheted at 4 (drop this PR) None
cast or # pyright: ignore Not allowed here

Commits (red, then green)

  1. Call the authenticated endpoints with either client in the integ tests. Red: with the baseline emptied, the check reported the 4 errors. Green: 0 errors. The apis-integ tests that need no running services pass (25), including the 8 new request-recording tests. The rest need the local services, as in test-integration.

  2. Prove the apis-integ Pyright rule as an enforced rule. Red: the rewritten proof failed against the ratchet fixtures. Green: the bad fixture is the real pre-fix report; the good fixture is a clean report recorded from the fixed tests.

  3. Enforce Pyright for apis-integ, and drop the last Pyright baseline. Red: deleting the baseline made the guard fail with baseline file ... does not exist. Green: guard, rules-tool tests and python -m scripts.rules pyright pass, with all eight packages enforced.

  4. Say that ratcheting a Pyright rule again needs a downgrade reason. Docs only, from the review.

Suppressions added

None. (test_calculate.py already used cast("Client", object()) in two unit tests; that's unchanged.)

Real bugs found

None.

Self-review

review-code on this PR's diff, run by a separate agent, found no bugs. It confirmed against the generated modules that the plain-Client path sends the request sync_detailed sent before. Findings:

  1. The tests depend on private generated names, and a generator change would surface only in test-integration. Kept, as set out above under "Trade-off". This PR exists separately so it can be dropped.
  2. The docs described ratcheting a rule again without saying what that takes. Fixed: the type-checking skill now says ratcheting an enforced rule needs a [rule.downgrade] reason, which Run Pyright in CI for every package, in its own locked environment #864's guard requires (commit 4). With no ratcheted rules, --write-baseline now says there is nothing to write (Run Pyright in CI for every package, in its own locked environment #864).

Environment variables

No CI, image or deployment changes. The integration tests read SIMULATION_INTEG_TEST_* settings as before; nothing new.

Testing

  • python -m scripts.rules pyright: all eight packages pass, all enforced.
  • apis-integ tests that need no running services: 25 passed.
  • Quality guards, rules-tool tests and ruff 0.15.22: pass.

This PR is above the bottom of the stack, so its CI runs only CI complete, which fails by design until the PRs below merge.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #867 October 21, 2026 15:50
@anth-volk
anth-volk force-pushed the pyright-apis-integ branch 2 times, most recently from 78deee9 to 66759a5 Compare October 21, 2026 16:59
@anth-volk
anth-volk force-pushed the pyright-apis-integ branch 2 times, most recently from 354d6dd to 317d690 Compare October 21, 2026 21:37
anth-volk and others added 4 commits October 22, 2026 21:15
The generated sync_detailed of each endpoint that declares bearer
authentication accepts only an AuthenticatedClient, but the integration
tests pass a plain Client against local services, which run without
authentication (4 Pyright errors).

endpoint_calls has one function per such endpoint (submit and poll, for
annual and budget-window runs). Each takes either client and sends
exactly what sync_detailed sends: the endpoint module's _get_kwargs,
through the client's own httpx client, parsed by its _build_response.
The headers the tests send don't change. A new test records the
requests through an httpx mock transport and checks it: with an
AuthenticatedClient each function sends sync_detailed's exact request,
and with a plain Client the same request without Authorization. The two
polling tests that stubbed sync_detailed now stub endpoint_calls.

apis-integ now has no Pyright errors, and its baseline is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As for the gateway and executor: the bad fixture, the integration
tests' real report from main at ceb30e1, now fails with every error
new and no baseline, and the good fixture is a clean report recorded
from the fixed tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integration tests have no Pyright errors, so
sim.quality.pyright-apis-integ moves from ratcheted to enforced and its
empty baseline goes. Every package's Pyright rule is now enforced; the
type-checking skill keeps the baseline mechanism for a future upgrade or
package that needs one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With every Pyright rule enforced, the type-checking skill describes
ratcheting one again for a future upgrade or package. It now says that
needs a [rule.downgrade] reason, which the guard requires for a move
from enforced to ratcheted.

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

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.

Fix apis-integ's 4 Pyright errors without changing the requests the tests send

1 participant