Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Ran Stage 12 US national simulations as 20 region-group calls each, and built an independent, immutable Stage 12 cache of three annual US datasets and 60 current-law baselines before each deployment, publishing it only after a recorded determinism check and recording its release in the v2 version manifest under a new key, while the existing key keeps a copy that earlier readers accept
- Made the budget refuse a household count outside its country's plausible range (above 200 million in the US or 60 million in the UK, negative or not finite), raising ImplausibleEntityCountError instead of publishing a count like #767's
changed:
- Required policyengine-observability 3.0.4 (>=3.0.4,<4) in the observability library, the entrypoint, the gateway and the executor, and locked it in the six locks that include it, where it is the only package that moves; 3.0.4 annotates its span and operation scopes' __exit__ and __aexit__ as returning Literal[False], so the services now open spans and operations with runtime.span and runtime.operation directly, and the policyengine_simulation_observability.scopes module, which only retyped those scopes for Pyright, is removed with its tests; a new observability library test fails if an older release is installed, Pyright still reports no errors in any package, and 3.0.3's fix, which keeps the recorded exception on the request.completed record and server span of a request that ends with an error outcome but passes no exception to end_request, is included
- Enforced the file-length rule sim.quality.python-file-length, now that every tracked Python file is within its limit: its empty baseline and its downgrade section are gone, and its tier is metric rather than mechanical, because it measures a number against a threshold, with the at-the-limit and one-over fixtures as its below and above proof
- Moved the entrypoint's backend calls, response telemetry and comparison dispatch out of app.py (925 lines, now 583) into entry_runtime.py, which app.py re-exports and which still logs under the policyengine_simulation_entry.app logger; the routes stay in app.py, so FastAPI and ruff read their annotations as before; the 2,251-line deployment asset test file is split into seven test files and a support module named test_deployment_assets_support.py, the rules registry names each moved test by its new file, and its guard-test discovery now covers tests/test_deployment_assets*.py
- Moved the worker's credential setup, request year and policy parsing, and region resolution out of simulation_runtime.py (1,111 lines, now 651) into gcp_credentials.py, request_policy.py and region_resolution.py; simulation_runtime.py keeps dataset selection and loading, simulation building, local-area breakdowns and the run itself, which tests replace by name there, and re-exports the moved names, so every caller still imports them from simulation_runtime; the shared helpers _parse_year, _normalise_policy, _compile_interval_policy, _runtime_policy (now wrapper_policy), _resolve_region, _resolve_dataset_reference and _require_local_area_breakdown are now public, and the credential messages still log under the simulation_runtime logger; the 3,048-line output builder test file is split into seven test files and a module of shared fakes, all named test_simulation_output_*.py so the weighted-frame rule still checks them
Expand Down
11 changes: 5 additions & 6 deletions docs/engineering/skills/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ add `_observability_context` to it.

At the receiving function, validate the separate argument with
`normalize_observability_context` and pass the result to
`scopes.operation(runtime, ..., remote_context=context)`. The operation scope must
`runtime.operation(..., remote_context=context)`. The operation scope must
cover the worker call. Child dispatches made inside that scope call
`runtime.capture_context()` again and pass the resulting context through their
own keyword-only argument.
Expand All @@ -79,11 +79,10 @@ All span names for supported run configurations are defined in
appropriate `StagePlan` and call `plan.name(Stage.VALUE)`. Do not add span name
string literals in services or workers.

Open a span with `scopes.span(runtime, plan.name(Stage.VALUE))`, and an
operation with `scopes.operation(runtime, ...)`, from
`policyengine_simulation_observability.scopes` rather than `runtime.span(...)`
or `runtime.operation(...)`. Each returns the runtime's own scope, typed so
that Pyright knows an exception raised in the block propagates; see
Open a span with `runtime.span(plan.name(Stage.VALUE))`, and an operation with
`runtime.operation(...)`. policyengine-observability 3.0.4 types both scopes
correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so Pyright
knows an exception raised in the block propagates; see
`docs/engineering/skills/type-checking.md`.

When adding a run configuration or runtime stage:
Expand Down
14 changes: 8 additions & 6 deletions docs/engineering/skills/type-checking.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,14 @@ section is still in the registry fails the guard on every pull request.
same line: `# pyright: ignore[reportAttributeAccessIssue] # <reason>`.
- If an error shows a real bug, first write a test that demonstrates it, then
fix it.
- Open observability spans and operations with `scopes.span(runtime, ...)` and
`scopes.operation(runtime, ...)` from
`policyengine_simulation_observability.scopes`, not `runtime.span(...)` or
`runtime.operation(...)`. policyengine-observability types its scope
managers as possibly suppressing exceptions, so after `with runtime.span(...)`
Pyright reports each variable the block assigned as possibly unbound.
- Open observability spans and operations with `runtime.span(...)` and
`runtime.operation(...)`. policyengine-observability 3.0.4 types them
correctly: their `__exit__` and `__aexit__` return `Literal[False]`, so
Pyright knows they never suppress an exception and doesn't report a variable
the block assigns as possibly unbound after it. Every package that depends
on policyengine-observability requires 3.0.4 or later, and
`libs/policyengine-simulation-observability/tests/test_scope_exit_typing.py`
fails if an older release is installed.
- Before reading a simulation's output frames, narrow
`<simulation>.output_dataset.data` with the `EntityFrames` protocol in
`policyengine_simulation_executor.output_frames`, and keep reading through
Expand Down
8 changes: 4 additions & 4 deletions libs/policyengine-simulation-contract/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/policyengine-simulation-observability/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ requires-python = ">=3.13"
dependencies = [
"fastapi>=0.115.0",
"pydantic>=2.0",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.2,<4",
"policyengine-observability[fastapi,google,otlp-grpc]>=3.0.4,<4",
]

[project.optional-dependencies]
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""The installed policyengine-observability types its scopes as never suppressing.

The services open spans and operations with ``runtime.span(...)`` and
``runtime.operation(...)``. A type checker reads an ``__exit__`` or
``__aexit__`` annotated as returning ``bool`` as "this context manager may
suppress the exception raised in its block", so Pyright would then report every
variable such a block assigns as possibly unbound after it, and every function
that returns from inside the block as possibly not returning.
policyengine-observability 3.0.4 annotates both methods as returning
``Literal[False]``. These tests fail if an older release is installed.
"""

from typing import Literal, get_type_hints

import pytest
from policyengine_simulation_observability.observability import build_runtime


@pytest.fixture
def runtime(monkeypatch):
monkeypatch.setenv("OTEL_SDK_DISABLED", "true")
real_runtime = build_runtime(
service_name="policyengine-simulation-scope-typing-test",
service_role="test",
platform="local",
environment="test",
)
yield real_runtime
real_runtime.shutdown()


@pytest.mark.parametrize("exit_method_name", ["__exit__", "__aexit__"])
@pytest.mark.parametrize(
"open_scope",
[
pytest.param(lambda runtime: runtime.span("scope_typing"), id="span"),
pytest.param(lambda runtime: runtime.operation("scope_typing"), id="operation"),
],
)
def test_scope_exit_is_typed_as_never_suppressing_an_exception(
runtime, open_scope, exit_method_name
):
scope_manager = open_scope(runtime)
exit_method = getattr(type(scope_manager), exit_method_name)

assert get_type_hints(exit_method)["return"] == Literal[False]
103 changes: 0 additions & 103 deletions libs/policyengine-simulation-observability/tests/test_scopes.py

This file was deleted.

8 changes: 4 additions & 4 deletions libs/policyengine-simulation-observability/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions libs/policyengine-stage12-persistence/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading