Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
- `api.quality.environment-variables-load`: Required environment variables and credentials load in a deployed-like environment. (blocking: CI job `test_env_vars`)
- `api.quality.mypy-v2`: Shared query models, FastAPI dependencies, and v2 routes and services pass mypy with untyped definitions disallowed. (blocking: mypy)
- `api.quality.ruff-format`: All Python files are formatted with ruff format. (blocking: ruff format)
- `api.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W))
- `api.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W))
- `api.quality.tests-pass`: The unit, contract and to-refactor test suites pass. (blocking: CI job `test`)

### Observability
Expand Down
1 change: 1 addition & 0 deletions changelog.d/ruff-typing-imports.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enabled ruff's typing-only import rules (TC) and moved annotation-only imports under TYPE_CHECKING, keeping runtime imports for Pydantic, SQLModel, SQLAlchemy and FastAPI.
8 changes: 4 additions & 4 deletions docs/generated/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ flowchart LR
| `api.quality.environment-variables-load` | Required environment variables and credentials load in a deployed-like environment. | mechanical | enforced | yes | CI job `test_env_vars` | `pr.yml:test_env_vars` | — |
| `api.quality.mypy-v2` | Shared query models, FastAPI dependencies, and v2 routes and services pass mypy with untyped definitions disallowed. | mechanical | enforced | yes | mypy | `pr.yml:typecheck-v2` | — |
| `api.quality.ruff-format` | All Python files are formatted with ruff format. | mechanical | enforced | yes | ruff format | `pr.yml:lint` | — |
| `api.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W) | `pr.yml:lint` | — |
| `api.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W) | `pr.yml:lint` | — |
| `api.quality.tests-pass` | The unit, contract and to-refactor test suites pass. | mechanical | enforced | yes | CI job `test` | `pr.yml:test` | — |

```mermaid
Expand All @@ -286,7 +286,7 @@ flowchart LR
e_ruff_format["ruff format"]
j_CI_pr_yml_lint["CI pr.yml:lint"]
r_api_quality_ruff_lint("api.quality.ruff-lint (enforced)")
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_["ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, UP, W)"]
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_["ruff check (E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W)"]
r_api_quality_tests_pass("api.quality.tests-pass (enforced)")
e_CI_job_test["CI job test"]
j_CI_pr_yml_test["CI pr.yml:test"]
Expand All @@ -298,8 +298,8 @@ flowchart LR
e_mypy --> j_CI_pr_yml_typecheck_v2
r_api_quality_ruff_format --> e_ruff_format
e_ruff_format --> j_CI_pr_yml_lint
r_api_quality_ruff_lint --> e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__UP__W_ --> j_CI_pr_yml_lint
r_api_quality_ruff_lint --> e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_
e_ruff_check__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_ --> j_CI_pr_yml_lint
r_api_quality_tests_pass --> e_CI_job_test
e_CI_job_test --> j_CI_pr_yml_test
```
Expand Down
10 changes: 7 additions & 3 deletions policyengine_api/asgi_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
from __future__ import annotations

import time
from collections.abc import Callable
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING

from a2wsgi import WSGIMiddleware
from fastapi import FastAPI, Request
from fastapi.exception_handlers import request_validation_exception_handler
from fastapi.exceptions import RequestValidationError
from policyengine_observability import ObservabilityRuntime
from starlette.datastructures import MutableHeaders
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.responses import PlainTextResponse, Response
from starlette.routing import Match, Mount
from starlette.types import ASGIApp

from policyengine_api.constants import VERSION
from policyengine_api.fastapi_routes.dependencies import NativeRouteDependencies
Expand Down Expand Up @@ -52,6 +50,12 @@
generate_request_id,
)

if TYPE_CHECKING:
from collections.abc import Callable

from policyengine_observability import ObservabilityRuntime
from starlette.types import ASGIApp


def _apply_request_id_header(
response: Response,
Expand Down
8 changes: 6 additions & 2 deletions policyengine_api/data/v2/catalog/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import json
import sys
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING

from sqlalchemy import Engine, create_engine
from sqlalchemy.pool import NullPool
Expand All @@ -18,13 +18,17 @@
PublicationEvidence,
publish_catalog,
)
from policyengine_api.data.v2.catalog.records import NormalizedCatalog
from policyengine_api.data.v2.settings import (
V2ConfigurationError,
V2DatabaseSettings,
load_v2_data_write_database_settings,
)

if TYPE_CHECKING:
from collections.abc import Callable, Mapping

from policyengine_api.data.v2.catalog.records import NormalizedCatalog


def build_data_write_engine(settings: V2DatabaseSettings) -> Engine:
"""Build an isolated, unpooled engine for one initialization attempt."""
Expand Down
8 changes: 6 additions & 2 deletions policyengine_api/data/v2/catalog/publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
import time
from collections.abc import Callable
from typing import TYPE_CHECKING

import sqlalchemy as sa
from sqlalchemy import Connection, Engine
Expand All @@ -23,7 +23,6 @@
CatalogPublicationError,
PublicationEvidence,
)
from policyengine_api.data.v2.catalog.records import NormalizedCatalog
from policyengine_api.data.v2.migration_target import V2_ALEMBIC_HEAD_REVISION
from policyengine_api.data.v2.models import (
DatasetVersion,
Expand All @@ -32,6 +31,11 @@
Simulation,
)

if TYPE_CHECKING:
from collections.abc import Callable

from policyengine_api.data.v2.catalog.records import NormalizedCatalog

# Stable application-defined PostgreSQL lock ID shared by all v2 catalog publishers.
PUBLICATION_ADVISORY_LOCK_KEY = 8_629_020_026_090_001

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from typing import TYPE_CHECKING

import sqlalchemy as sa
from sqlalchemy import Connection, Select
from sqlalchemy.dialects.postgresql import JSONB
Expand All @@ -19,7 +21,6 @@
from policyengine_api.data.v2.catalog.publication_types import (
CatalogPublicationError,
)
from policyengine_api.data.v2.catalog.records import CountryCatalog
from policyengine_api.data.v2.models import (
Dataset,
Parameter,
Expand All @@ -31,6 +32,9 @@
Variable,
)

if TYPE_CHECKING:
from policyengine_api.data.v2.catalog.records import CountryCatalog

DATASETS = Dataset.__table__
MODELS = TaxBenefitModel.__table__
MODEL_VERSIONS = TaxBenefitModelVersion.__table__
Expand Down
5 changes: 4 additions & 1 deletion policyengine_api/data/v2/catalog/publication_staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from collections.abc import Callable, Iterable, Iterator, Sequence
from typing import TYPE_CHECKING

import sqlalchemy as sa
from psycopg import sql
Expand All @@ -19,6 +19,9 @@
iter_batches,
)

if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Iterator, Sequence

COPY_BATCH_SIZE = 10_000

STAGING_METADATA = MetaData()
Expand Down
10 changes: 6 additions & 4 deletions policyengine_api/data/v2/catalog/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

from __future__ import annotations

from collections.abc import Iterator, Sequence
from dataclasses import dataclass, fields
from datetime import datetime
from typing import Any, TypeVar
from uuid import UUID
from typing import TYPE_CHECKING, Any, TypeVar

if TYPE_CHECKING:
from collections.abc import Iterator, Sequence
from datetime import datetime
from uuid import UUID


@dataclass(frozen=True, slots=True)
Expand Down
5 changes: 4 additions & 1 deletion policyengine_api/data/v2/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import atexit
import os
from threading import Lock
from typing import TYPE_CHECKING

from sqlalchemy import Engine
from sqlalchemy.orm import sessionmaker
from sqlmodel import Session, create_engine

Expand All @@ -16,6 +16,9 @@
load_v2_runtime_database_settings,
)

if TYPE_CHECKING:
from sqlalchemy import Engine

DATABASE_POOL_RECYCLE_SECONDS = 1800
DATABASE_POOL_SIZE = 5
DATABASE_POOL_MAX_OVERFLOW = 5
Expand Down
6 changes: 4 additions & 2 deletions policyengine_api/data/v2/household_migration_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

import json
import sys
from collections.abc import Callable, Collection, Mapping
from dataclasses import dataclass
from typing import Protocol
from typing import TYPE_CHECKING, Protocol

from alembic.config import Config
from alembic.migration import MigrationContext
Expand All @@ -29,6 +28,9 @@
load_v2_migration_database_settings,
)

if TYPE_CHECKING:
from collections.abc import Callable, Collection, Mapping

HOUSEHOLD_MIGRATION_REVISION = "724b1b11a33e"
V2_ALEMBIC_CONFIG = "alembic-v2.ini"

Expand Down
5 changes: 4 additions & 1 deletion policyengine_api/data/v2/migration_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from __future__ import annotations

import os
from collections.abc import Mapping
from dataclasses import dataclass
from datetime import date
from typing import TYPE_CHECKING

from sqlalchemy import Connection, MetaData, inspect, text
from sqlalchemy.engine import URL, make_url
Expand All @@ -20,6 +20,9 @@
validate_supabase_database_identity,
)

if TYPE_CHECKING:
from collections.abc import Mapping

V2_ALEMBIC_DISPOSABLE_TEST = "V2_ALEMBIC_DISPOSABLE_TEST"
V2_ALEMBIC_HEAD_REVISION = "91af4fc8d8c1"
DISPOSABLE_DATABASE_NAME = "policyengine_v2_alembic_test"
Expand Down
6 changes: 4 additions & 2 deletions policyengine_api/data/v2/policy_migration_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

import json
import sys
from collections.abc import Callable, Collection, Mapping
from dataclasses import dataclass
from typing import Protocol
from typing import TYPE_CHECKING, Protocol

from alembic.config import Config
from alembic.migration import MigrationContext
Expand All @@ -23,6 +22,9 @@
load_v2_migration_database_settings,
)

if TYPE_CHECKING:
from collections.abc import Callable, Collection, Mapping

POLICY_MIGRATION_REVISION = "711ec2f0a5a5"
V2_ALEMBIC_CONFIG = "alembic-v2.ini"

Expand Down
9 changes: 6 additions & 3 deletions policyengine_api/data/v2/report_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

from __future__ import annotations

from collections.abc import Mapping
from datetime import datetime
from uuid import UUID
from typing import TYPE_CHECKING

from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
Expand All @@ -18,6 +16,11 @@
)
from policyengine_api.data.v2.models.base import utc_now

if TYPE_CHECKING:
from collections.abc import Mapping
from datetime import datetime
from uuid import UUID


class ReportNotFoundError(LookupError):
"""Raised when a requested durable report does not exist."""
Expand Down
5 changes: 4 additions & 1 deletion policyengine_api/data/v2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

import os
import re
from collections.abc import Callable, Mapping
from dataclasses import dataclass, field
from functools import cache
from typing import TYPE_CHECKING

from sqlalchemy.engine import URL, make_url
from sqlalchemy.exc import ArgumentError

if TYPE_CHECKING:
from collections.abc import Callable, Mapping

V2_RUNTIME_DATABASE_URL = "V2_RUNTIME_DATABASE_URL"
V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE = "V2_RUNTIME_DATABASE_URL_SECRET_RESOURCE"
V2_MIGRATION_DATABASE_URL = "V2_MIGRATION_DATABASE_URL"
Expand Down
10 changes: 5 additions & 5 deletions policyengine_api/fastapi_routes/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from functools import lru_cache
from importlib import metadata as importlib_metadata
from typing import TYPE_CHECKING, Protocol
from uuid import UUID

from policyengine_api.json_types import JSONObject

if TYPE_CHECKING:
from collections.abc import Callable
from datetime import datetime
from uuid import UUID

from policyengine_api.json_types import JSONObject
from policyengine_api.services.v2.households.types import (
HouseholdPage,
HouseholdRead,
Expand Down
5 changes: 3 additions & 2 deletions policyengine_api/fastapi_routes/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

from __future__ import annotations

from typing import Literal
from typing import TYPE_CHECKING, Literal

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from starlette.responses import PlainTextResponse

from policyengine_api.fastapi_routes.dependencies import NativeRouteDependencies
if TYPE_CHECKING:
from policyengine_api.fastapi_routes.dependencies import NativeRouteDependencies


class HealthResponse(BaseModel):
Expand Down
8 changes: 5 additions & 3 deletions policyengine_api/fastapi_routes/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Literal, TypedDict
from typing import TYPE_CHECKING, Literal, TypedDict

from fastapi import APIRouter
from starlette.responses import Response, StreamingResponse
Expand All @@ -11,17 +11,19 @@
InvalidCountryError,
ensure_supported_country,
)
from policyengine_api.fastapi_routes.dependencies import NativeRouteDependencies
from policyengine_api.fastapi_routes.responses import (
LegacyJSONResponse,
render_legacy_json,
)
from policyengine_api.json_types import JSONObject
from policyengine_api.utils.streaming_json import (
iter_body_chunks,
should_stream_body,
)

if TYPE_CHECKING:
from policyengine_api.fastapi_routes.dependencies import NativeRouteDependencies
from policyengine_api.json_types import JSONObject


class MetadataSuccessPayload(TypedDict):
status: Literal["ok"]
Expand Down
Loading
Loading