Skip to content

Enable ruff typing-only import rules (TC) - #773

Draft
anth-volk wants to merge 10 commits into
mainfrom
ruff-typing-imports
Draft

anth-volk wants to merge 10 commits into
mainfrom
ruff-typing-imports

Conversation

@anth-volk

Copy link
Copy Markdown
Contributor

Fixes #772

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 2: typing-only imports. Stacked on #765.

  • What changed: ruff.toml gains TC, and 146 fixes move annotation-only imports under TYPE_CHECKING.
  • What stays at runtime: [lint.flake8-type-checking] lists:
    • Runtime-evaluated base classes: pydantic.BaseModel, pydantic.RootModel, and every project model base class that other models inherit from: 10 across the contract, policyengine-fastapi and executor packages.
    • Runtime-evaluated decorators: the FastAPI/APIRouter route decorators. Pydantic and FastAPI evaluate these annotations at runtime.
  • Shared tool: scripts/rules is byte-identical with policyengine-api.

Testing

All seven projects' test suites pass:

Project Passed
fastapi 14
contract 145
observability 30
stage12 12
entry 224
gateway 214 (includes the OpenAPI golden)
executor 733
  • ruff check . passes with 0.15.22 and 0.16.8, and src formatting is unchanged.
  • Guards and the rules tool unittest pass.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #755 October 13, 2026 11:40
@anth-volk anth-volk mentioned this pull request Oct 13, 2026
2 tasks done
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from 18fae4c to f651387 Compare October 13, 2026 16:56
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 4 times, most recently from 6c25653 to da295a5 Compare October 14, 2026 17:29
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from da295a5 to ed7598a Compare October 14, 2026 17:56
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 3 times, most recently from ccd5b09 to fcc9970 Compare October 14, 2026 21:06
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 2 times, most recently from 0169985 to e9b8225 Compare October 14, 2026 21:52
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from e9b8225 to 123745c Compare October 15, 2026 14:06
Base automatically changed from ruff-safe-autofixes to main October 15, 2026 15:08
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from 123745c to 1b6b51a Compare October 15, 2026 15:09
anth-volk and others added 7 commits October 16, 2026 01:28
Select TC and move imports used only in annotations under TYPE_CHECKING.
Pydantic evaluates model annotations and FastAPI evaluates endpoint
annotations at runtime, so ruff.toml lists the runtime-evaluated model base
classes (every project base, since ruff only checks direct bases) and the
FastAPI/APIRouter route decorators. scripts/rules matches policyengine-api.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#789 landed on main after this stack branched. TC, which this PR enables,
flags the annotation-only imports in its new database-retry and
failure-recording modules; they now load only under TYPE_CHECKING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#791 landed on main after this stack branched. TC, which this PR enables,
flags the imports its new Stage 12 failure-cause module uses only in
annotations (Iterator, Mapping, ObservabilityRuntime, Stage and
StagePlan); they move under TYPE_CHECKING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruff treats a class as runtime-evaluated when a base, read through the
import statements of the class's own module, is listed in
runtime-evaluated-base-classes, or is a class in that same module that is.
It never follows a re-export, and only follows inheritance within a
module. Four models had bases ruff reads at unlisted paths, so ruff would
have moved imports their annotations need under TYPE_CHECKING if they
gained any:

- ArtifactBaselineSimulation and PrecomputedSimulation subclass
  policyengine.py's policyengine.core.Simulation;
- AppSettings subclasses pydantic_settings.BaseSettings;
- EconomyComparison subclassed SingleYearMacroOutput through the
  executor's simulation_macro_output re-export.

The first two paths are listed as their subclasses import them. For
EconomyComparison, compat_models now imports SingleYearMacroOutput from
policyengine_simulation_contract.macro_output, the module that defines
it, and that path is listed: every project entry then names its defining
module, and the entry also covers subclasses elsewhere that import the
class from the contract. The ruff.toml comment now describes how ruff
resolves bases instead of "direct bases only".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executor's test imports every module of the executor package and of
the project libraries installed with it; the entrypoint's imports every
module of its package. For every Pydantic model a module-level class
statement defines, they check that:

- ruff treats it as runtime-evaluated, resolving each base the way ruff
  does: through the model module's import statements as they stand at the
  class statement, reading Base[Item] as Base, following bases defined in
  the same module, and never following a re-export or an assignment. A
  failure names the model and the base path to list;
- its annotations resolve, with model_rebuild(raise_errors=True).

Unit tests pin the resolution: a listed base, a subscripted one, a
relative import, a same-module chain through Generic, an unlisted
re-export, an unlisted third-party base, an assigned alias, and a base
in a package's __init__.py, which ruff names with __init__ in its path.
The proof fixtures are compat_models.py before and after the previous
commit.

The executor's src/modal Modal entry files are not imported: they are
not part of the installed package, and importing them builds Modal
images and functions, which their tests do only with a fake Modal SDK
and a patched environment. A static test checks that every class there
derives only from builtins and the standard library, so none can be a
model. The gateway defines no Pydantic models. The entrypoint shares no
package with the executor that could hold test helpers, so it keeps a
copy of the resolution helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 3 commits October 16, 2026 01:28
…ports

sim.quality.runtime-evaluated-models-resolve is enforced by the two new
test files, which run in the executor and entrypoint jobs of the PR test
matrix. Its nodes span both projects, so they are listed from the
repository root without a project. The test files join the guard-test
discovery globs, so any test added to them must be registered.

docs/engineering/skills/typing-only-imports.md explains when ruff moves
an import, how it decides a model is runtime-evaluated, and how to list
model bases; AGENTS.md and the skills README point to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#781 landed on main after this stack branched. TC, which this PR enables,
flags the imports its contract job_progress module uses only in
annotations (Mapping and datetime), which move under TYPE_CHECKING, and
the cast in its new integration poll test, whose Client type is quoted.
The module's Pydantic models annotate their timestamps with AwareDatetime,
which stays a runtime import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#781 landed on main after this stack branched. It added
StageReportingSimulation, a policyengine.core.Simulation subclass, and
made ArtifactBaselineSimulation subclass it from another module. Ruff
reads that base through baseline_artifacts' import, which is not listed,
so it would no longer treat ArtifactBaselineSimulation as runtime-evaluated.
The sim.quality.runtime-evaluated-models-resolve guard names the path to
list; it is added beside the other project bases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from 80a1eec to 8c3125d Compare October 15, 2026 21:56

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.

1 participant