Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8528607
Pin application behaviour before the complexity refactors
anth-volk Oct 14, 2026
45d9734
Name the magic numbers compared in data, client and v2 validation code
anth-volk Oct 14, 2026
c2b233a
Look up route groups by path and pass explicit database sources as on…
anth-volk Oct 14, 2026
10e79cb
Split Flask migration logging into steps and log one ServedRequest re…
anth-volk Oct 14, 2026
86b0dc0
Split the ASGI shell's request handling into context, telemetry and r…
anth-volk Oct 14, 2026
ffa151d
Pass a failed household cell to the calculation failure recorder as o…
anth-volk Oct 14, 2026
270e43b
Split household route error handling into parse, calculate and map steps
anth-volk Oct 14, 2026
a3e2df0
Share the saved-policy write configuration and failure steps
anth-volk Oct 20, 2026
305fb58
Validate the simulation identity fields in their own step
anth-volk Oct 14, 2026
f078125
Map v2 household and policy failures through ordered error tables
anth-volk Oct 14, 2026
1070ce8
Move the association create checks out of the v2 router builders
anth-volk Oct 14, 2026
8c85e02
Pass parameter-value filters as one value through the metadata layers
anth-volk Oct 14, 2026
fd40033
Split the comparison report state validator into lifecycle and compar…
anth-volk Oct 14, 2026
0b7b3dc
Split v2 household document validation into collection, record and me…
anth-volk Oct 14, 2026
8c066ca
Split v1 household translation into collection, group, membership and…
anth-volk Oct 14, 2026
922f310
Read the legacy household mapping's v1 identity from its snapshot
anth-volk Oct 14, 2026
298359f
Describe a requested page of user-household associations as one value
anth-volk Oct 14, 2026
325968a
Pass a v1 saved-policy revision and a requested association page as o…
anth-volk Oct 14, 2026
84da9d2
Pass a report run's package versions as one value
anth-volk Oct 14, 2026
3b15ef6
Split catalog extraction into verification, selection and normalizati…
anth-volk Oct 14, 2026
6dcef5c
Move the catalog staging row generators to module level
anth-volk Oct 14, 2026
f94c301
Pass a household or policy mirror's outcome to its metric logger as o…
anth-volk Oct 14, 2026
bbc8d69
Group saved-policy mirror collaborators and metric record inputs
anth-volk Oct 14, 2026
0d6e8de
Describe a new simulation or report-output run as one value
anth-volk Oct 14, 2026
57deb5f
Pass an economy report's dataset, target and options as one value
anth-volk Oct 14, 2026
5b153f3
Drop an always-set lock flag and an unused policy helper
anth-volk Oct 14, 2026
7d9c15e
Check a saved-policy mirror event's changed fields and type in their …
anth-volk Oct 14, 2026
8da31cb
Pass the gateway token provider's Auth0 settings as one value
anth-volk Oct 14, 2026
623264a
Check household entity inputs and safe JSON numbers in their own helpers
anth-volk Oct 14, 2026
ed0278f
Register the ASGI exception handlers through FastAPI's decorator
anth-volk Oct 14, 2026
63e0a47
Pin the named limits, enum normalization and log-context errors
anth-volk Oct 14, 2026
277191c
Add a changelog fragment for the application complexity refactors
anth-volk Oct 14, 2026
822da3b
Name the region filter value's length limit from #3918
anth-volk Oct 14, 2026
47bb3bf
Name the Auth0 Management status codes #3891 compares (PLR2004)
anth-volk Oct 15, 2026
dc1e1f9
Pin the user routes' failure mapping before its complexity refactor
anth-volk Oct 15, 2026
c9a689c
Map user-route failures through an ordered failure table
anth-volk Oct 15, 2026
875fe7c
Move the create and upgrade steps out of the user router builders
anth-volk Oct 15, 2026
b7f26e5
Drop the migration context's unused entity and flow overrides
anth-volk Oct 15, 2026
cb4c983
Pass the user list filters, row listings and sign-in identity as one …
anth-volk Oct 15, 2026
1a10dca
Catch expected household calculation errors by type (BLE001)
anth-volk Oct 20, 2026
3a2dcc8
Type the catalog region parameter with a private protocol (ANN401)
anth-volk Oct 20, 2026
7030d9c
Replace the policy mirror where it is defined in the user-policy pers…
anth-volk Oct 20, 2026
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
1 change: 1 addition & 0 deletions changelog.d/ruff-complexity-application.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactored the rest of the application under ruff's complexity rules (C901, PLR): the ASGI shell, Flask migration logging, household and policy routes, v1 and v2 household translation and validation, saved-policy mirrors, catalog extraction and the metadata layers are split into named steps, related arguments travel as small value objects, and magic numbers are named, with characterization tests pinning the behaviour first; responses, error messages, logs and published schemas are unchanged.
529 changes: 333 additions & 196 deletions policyengine_api/asgi_factory.py

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions policyengine_api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ def get_bundle_default_dataset_option(country_id: str) -> dict:
"economy_impact": 3,
"report_output": 3,
}
# Runtime cache versions are stored in legacy VARCHAR(10) columns, and keep at
# least this many digest characters after their scope prefix and schema version.
RUNTIME_CACHE_VERSION_LENGTH = 10
MINIMUM_RUNTIME_CACHE_DIGEST_LENGTH = 4


def _build_runtime_cache_version(
Expand All @@ -209,8 +213,8 @@ def _build_runtime_cache_version(
"""
schema_version = str(RUNTIME_CACHE_SCHEMA_VERSIONS[scope])
prefix = "e" if scope == "economy_impact" else "r"
digest_length = 10 - len(prefix) - len(schema_version)
if digest_length < 4:
digest_length = RUNTIME_CACHE_VERSION_LENGTH - len(prefix) - len(schema_version)
if digest_length < MINIMUM_RUNTIME_CACHE_DIGEST_LENGTH:
msg = f"Runtime cache version for {scope} does not fit in VARCHAR(10)"
raise ValueError(msg)

Expand Down
38 changes: 23 additions & 15 deletions policyengine_api/country.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from datetime import date, datetime
from importlib.metadata import version as get_package_version
from pathlib import Path
from typing import Any
from typing import Any, NamedTuple

import dpath
import pandas as pd
Expand Down Expand Up @@ -87,21 +87,33 @@ def _serialize_axis_result(
return entity_values


class _HouseholdCell(NamedTuple):
"""One requested household value: an entity's variable in one period."""

entity_plural: str
entity_id: str
variable_name: str
period: str


def _record_calculation_failure(
household: dict,
calculation_warnings: list[str],
cell: _HouseholdCell,
*,
has_axes: bool,
population: Population,
entity_plural: str,
entity_id: str,
variable_name: str,
period: str,
axis_population: Population | None,
error: Exception,
) -> None:
if has_axes:
"""Record a household value that could not be calculated.

``axis_population`` is the cell's population when the household has axes:
the cell then holds one null per axis point and a warning is added.
Otherwise the cell is null and the failure is only logged.
"""
entity_plural, entity_id, variable_name, period = cell
if axis_population is not None:
count_entities = len(household[entity_plural])
axis_point_count = population.count // count_entities
axis_point_count = axis_population.count // count_entities
household[entity_plural][entity_id][variable_name][period] = [
None
] * axis_point_count
Expand Down Expand Up @@ -548,12 +560,8 @@ def calculate(
_record_calculation_failure(
household,
calculation_warnings,
has_axes=has_axes,
population=population,
entity_plural=entity_plural,
entity_id=entity_id,
variable_name=variable_name,
period=period,
_HouseholdCell(entity_plural, entity_id, variable_name, period),
axis_population=population if has_axes else None,
error=error,
)

Expand Down
7 changes: 6 additions & 1 deletion policyengine_api/data/congressional_districts.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,11 @@ class CongressionalDistrictMetadataItem(BaseModel):
]


# Numbers whose last two digits are 10 through 20 always take "th" (11th, 12th).
_ALWAYS_TH_REMAINDER_MIN = 10
_ALWAYS_TH_REMAINDER_MAX = 20


def _get_ordinal_suffix(number: int) -> str:
"""Get the ordinal suffix for a number (st, nd, rd, th).

Expand All @@ -598,7 +603,7 @@ def _get_ordinal_suffix(number: int) -> str:
22 -> "nd"

"""
if 10 <= number % 100 <= 20:
if _ALWAYS_TH_REMAINDER_MIN <= number % 100 <= _ALWAYS_TH_REMAINDER_MAX:
# Special case for 11th, 12th, 13th, etc.
suffix = "th"
else:
Expand Down
4 changes: 3 additions & 1 deletion policyengine_api/data/places.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from policyengine_api.data.congressional_districts import get_valid_state_codes

PLACE_FIPS_CODE_LENGTH = 5


def parse_place_code(place_code: str) -> tuple[str, str]:
"""Parse a place code into its state abbreviation and FIPS components.
Expand Down Expand Up @@ -45,6 +47,6 @@ def validate_place_code(place_code: str) -> None:
msg = f"Invalid state in place code: '{state_abbrev}'"
raise ValueError(msg)

if not place_fips.isdigit() or len(place_fips) != 5:
if not place_fips.isdigit() or len(place_fips) != PLACE_FIPS_CODE_LENGTH:
msg = f"Invalid FIPS code in place: '{place_fips}'. Expected 5-digit FIPS code"
raise ValueError(msg)
8 changes: 6 additions & 2 deletions policyengine_api/data/v2/catalog/catalog_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)

SUPPORTED_V2_COUNTRY_IDS = frozenset({"us", "uk"})
MAXIMUM_POLICYENGINE_VERSION_LENGTH = 128


class MetadataCatalogUnavailableError(RuntimeError):
Expand Down Expand Up @@ -47,8 +48,11 @@ def validate_policyengine_version(value: str) -> str:
if not isinstance(value, str) or not value or value != value.strip():
msg = "policyengine_version must be a non-empty canonical version"
raise InvalidPolicyEngineVersionError(msg)
if len(value) > 128:
msg = "policyengine_version must be at most 128 characters"
if len(value) > MAXIMUM_POLICYENGINE_VERSION_LENGTH:
msg = (
"policyengine_version must be at most "
f"{MAXIMUM_POLICYENGINE_VERSION_LENGTH} characters"
)
raise InvalidPolicyEngineVersionError(msg)
try:
parsed = Version(value)
Expand Down
Loading
Loading