Skip to content

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

Merged
anth-volk merged 3 commits into
masterfrom
ruff-typing-imports
Oct 15, 2026
Merged

anth-volk merged 3 commits into
masterfrom
ruff-typing-imports

Conversation

@anth-volk

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

Copy link
Copy Markdown
Collaborator

Fixes #3901

Summary

Phase 1 step 1.8, group 2: typing-only imports. Stacked on #3898.

  • What changed: select gains TC, and its fixes move imports used only in annotations under TYPE_CHECKING (236 findings on today's master: 103 standard-library, 90 first-party and 36 third-party imports, plus 7 cast() calls whose type is now quoted). This removes import-time work and import cycles, without changing runtime behaviour.

  • What stays at runtime: Pydantic and SQLModel models, SQLAlchemy declarative models and FastAPI endpoints all evaluate annotations at runtime, so their imports must stay. [tool.ruff.lint.flake8-type-checking] lists:

    • Runtime-evaluated base classes: pydantic.BaseModel, sqlmodel.SQLModel, sqlalchemy.orm.DeclarativeBase, V1Base, and the 33 other project model classes that other models inherit from. Ruff checks direct bases only, so each must be listed. A model base missing from the list fails the guard described below.
    • Runtime-evaluated decorators: the APIRouter route decorators.
  • Found during testing: the first attempt broke V1Base models, because SQLAlchemy resolves Mapped[...] at class creation. Hence DeclarativeBase and V1Base are on the list.

  • Rules tool: the scripts/rules changes here are this repository's own; the copy in the Simulation API no longer has to match.

  • Guard for the runtime list: tests/unit/test_runtime_evaluated_annotations.py (api.quality.runtime-evaluated-models-resolve) imports the package, apart from the two app-assembly modules, which define no classes. It then checks that:

    • every package model class that other models inherit from is listed in runtime-evaluated-base-classes;
    • every Pydantic model resolves its annotations (model_rebuild(raise_errors=True));
    • every SQLAlchemy mapper configures.

    Without it, a model base left off the list would let ruff ask for an import its subclasses need to be moved, and the break would surface only when that model was first used. The rationale is in a new skill, docs/engineering/skills/typing-only-imports.md, with a pointer in AGENTS.md.

Testing

  • pytest tests/unit: 2,342 passed, including the 4 new guard tests. Removing TimestampedModel from the list fails the first. Moving UUID under TYPE_CHECKING in services/v2/policies/types.py fails the second, naming the five models that need it.
  • mypy is clean. ruff check . passes with 0.15.12 and 0.16.8.
  • Rules tool unittest passes on Python 3.11, 3.12 and 3.14. All guards pass.

🤖 Generated with Claude Code

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

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 26.54867% with 249 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.71%. Comparing base (172bbed) to head (018b355).

Files with missing lines Patch % Lines
...cyengine_api/services/v2/user_policies/services.py 14.28% 5 Missing and 1 partial ⚠️
...olicyengine_api/services/v2/households/services.py 16.66% 4 Missing and 1 partial ⚠️
policyengine_api/services/v2/policies/services.py 16.66% 4 Missing and 1 partial ⚠️
...engine_api/services/v2/policies/transformations.py 16.66% 4 Missing and 1 partial ⚠️
...engine_api/services/v2/user_households/services.py 16.66% 4 Missing and 1 partial ⚠️
policyengine_api/asgi_factory.py 20.00% 3 Missing and 1 partial ⚠️
policyengine_api/data/v2/catalog/records.py 20.00% 3 Missing and 1 partial ⚠️
policyengine_api/data/v2/report_runs.py 20.00% 3 Missing and 1 partial ⚠️
policyengine_api/fastapi_routes/dependencies.py 0.00% 4 Missing ⚠️
...icyengine_api/fastapi_routes/v2/metadata/common.py 20.00% 3 Missing and 1 partial ⚠️
... and 79 more
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #3902       +/-   ##
===========================================
+ Coverage   45.27%   85.71%   +40.44%     
===========================================
  Files         183      198       +15     
  Lines       11084    12355     +1271     
  Branches     1907     2247      +340     
===========================================
+ Hits         5018    10590     +5572     
+ Misses       5644     1083     -4561     
- Partials      422      682      +260     

☔ 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 force-pushed the ruff-typing-imports branch from b71530a to 5d3461e Compare October 13, 2026 16:56
@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-typing-imports branch from 5d3461e to e950a77 Compare October 14, 2026 13:52
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 2 times, most recently from a59bb82 to c354d5f Compare October 14, 2026 16:59
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 3 times, most recently from e7aff0c to 6b8a90a Compare October 14, 2026 20:04
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch 4 times, most recently from 5035c82 to cd21647 Compare October 14, 2026 22:29
Base automatically changed from ruff-safe-autofixes to master October 14, 2026 22:55
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from cd21647 to e0a9e49 Compare October 14, 2026 22:56
anth-volk and others added 2 commits October 15, 2026 04:05
Select TC and move imports used only in annotations under TYPE_CHECKING.
Pydantic, SQLModel and SQLAlchemy evaluate model annotations at runtime,
and FastAPI evaluates endpoint annotations, so those imports stay at
runtime: pyproject.toml lists the runtime-evaluated base classes (every
project model base, since ruff only checks direct bases) and the
APIRouter decorators. scripts/rules changes are mirrored in
policyengine-sim-api.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the ruff-typing-imports branch from e0a9e49 to 11c3b1f Compare October 15, 2026 00:06
@anth-volk
anth-volk marked this pull request as ready for review October 15, 2026 12:51
@anth-volk
anth-volk requested a review from a team as a code owner October 15, 2026 12:51
Ruff's TC rules keep a class's annotation imports at runtime only when
one of its direct bases is listed in runtime-evaluated-base-classes.
Pydantic, SQLModel and SQLAlchemy read model annotations at runtime, so
a model base missing from that list lets ruff ask for an import its
subclasses need to be moved under TYPE_CHECKING; moving it breaks those
models the first time they are used.

tests/unit/test_runtime_evaluated_annotations.py imports the package
(except the two app-assembly modules, which define no classes) and
checks that:

- every package model class that other models inherit from is listed;
- every Pydantic model resolves its annotations (model_rebuild with
  raise_errors);
- every SQLAlchemy mapper configures.

Registered as api.quality.runtime-evaluated-models-resolve, with the
rationale in a new typing-only-imports skill and a pointer in AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk merged commit eb2b4b6 into master Oct 15, 2026
14 of 15 checks passed
@anth-volk
anth-volk deleted the ruff-typing-imports branch October 15, 2026 15:07

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 typing-only import rules (TC)

1 participant