Skip to content

Move to policyengine-observability 3.0.4 and remove the scopes module - #959

Merged
anth-volk merged 3 commits into
mainfrom
observability-3-0-4
Oct 27, 2026
Merged

anth-volk merged 3 commits into
mainfrom
observability-3-0-4

Conversation

@anth-volk

Copy link
Copy Markdown
Contributor

Fixes #958

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

policyengine-observability 3.0.4 (PolicyEngine/policyengine-observability#38) annotates the __exit__ and __aexit__ methods of the scope managers that runtime.span(...) and runtime.operation(...) return as returning Literal[False] instead of bool. Pyright therefore no longer reads a scope as possibly suppressing the exception raised in its block, so policyengine_simulation_observability.scopes, which only returned those scopes retyped as AbstractContextManager[object], is no longer needed.

The history is in test-first order:

  1. Failing test (f83610cf): libs/policyengine-simulation-observability/tests/test_scope_exit_typing.py reads the return annotation of __exit__ and __aexit__ with typing.get_type_hints, as the upstream test does, for a span and an operation of a real runtime, and asserts Literal[False]. With 3.0.2 locked, all 4 cases fail (<class 'bool'> == typing.Literal[False]). It guards the removal of scopes against a downgrade.
  2. Dependency (78817f9e): the five requirements rise from >=3.0.2,<4 to >=3.0.4,<4: the observability library, the entrypoint, the gateway, and the executor's dependencies and its modal-simulation-image group. The six locks that include the package (those four packages, plus libs/policyengine-simulation-contract and libs/policyengine-stage12-persistence, which include the observability library by path) were relocked with uv lock --upgrade-package policyengine-observability. The test passes.
  3. Removal (3d9109d2):
    • the 80 calls in 15 modules (entrypoint 1, executor 44, gateway 35) call the runtime directly: scopes.span(runtime, ...) becomes runtime.span(...), and scopes.operation(runtime, ...) becomes runtime.operation(...), with the same remaining arguments; each module drops the unused import;
    • scopes.py and test_scopes.py are deleted;
    • docs/engineering/skills/observability.md and docs/engineering/skills/type-checking.md now say to open spans and operations with runtime.span(...) and runtime.operation(...), which 3.0.4 types correctly;
    • changelog_entry.yaml has a changed item.

Lock changes

Relocked with uv 0.13.6. pr.yml doesn't pin a uv version: astral-sh/setup-uv@v8.1.0 installs the latest release, which is 0.13.6 today. In every lock, only policyengine-observability moves, from 3.0.2 to 3.0.4. The other changed lines are the >=3.0.4,<4 specifiers recorded for the local path packages that declare it.

Lock Packages changed
libs/policyengine-simulation-observability/uv.lock policyengine-observability 3.0.2 → 3.0.4
libs/policyengine-simulation-contract/uv.lock policyengine-observability 3.0.2 → 3.0.4
libs/policyengine-stage12-persistence/uv.lock policyengine-observability 3.0.2 → 3.0.4
projects/policyengine-simulation-entry/uv.lock policyengine-observability 3.0.2 → 3.0.4
projects/policyengine-simulation-gateway/uv.lock policyengine-observability 3.0.2 → 3.0.4
projects/policyengine-simulation-executor/uv.lock policyengine-observability 3.0.2 → 3.0.4

3.0.4 declares the same dependencies as 3.0.2. Between the two wheels, the only code changes are the two annotations and 3.0.3's fix: when a request ends with an error outcome and the adapter passes no exception to end_request, the exception recorded earlier with record_exception stays on the request.completed record and the server span.

Tests that replace the runtime

No test patched or replaced the scopes module. The tests whose names contain "scopes" (test_segmented_national.py, test_simulation_output_region_resolution.py, test_stage12_failure_causes.py, test_stage12_segment_calculation.py) use the word as a verb, as in test_resolve_region_scopes_us_state_from_national_dataset. Every test stand-in replaces the runtime object or its span and operation methods, which the code still calls, so each one still applies. The one behaviour difference: the wrapper always passed attributes=None and remote_context=None when a caller left them out, and a direct call doesn't pass them. I checked every stand-in and assertion that could see that:

  • projects/policyengine-simulation-entry/tests/test_stage12_backend.py asserts backend._runtime.span.assert_called_once_with(..., attributes={...}); the call passes attributes, so the assertion is unchanged.
  • projects/policyengine-simulation-executor/tests/test_stage12_modal_app.py records operation's **kwargs; all four v2_app.py operations pass attributes and remote_context.
  • ScopeRecordingRuntime.operation in test_stage12_failure_causes.py asserts remote_context is None; tracked_stage now omits it, so its default is None.
  • projects/policyengine-simulation-gateway/tests/test_job_progress.py replaces runtime.span on the application's runtime, which job_status_routes.py still calls.
  • The other stand-ins accept **kwargs or give each keyword a None default, and none records which keywords were passed.

Why the dependency is needed

To confirm that removing the wrapper depends on 3.0.4, I ran Pyright on the final code with the 3.0.2 locks and requirements restored. It reports 158 errors: entrypoint 2 (reportPossiblyUnboundVariable), gateway 73 (67 reportPossiblyUnboundVariable, 6 reportReturnType), executor 83 (78 and 5). With 3.0.4 locked, Pyright reports no errors in any package.

Environment variables

None added, removed or renamed. No workflow, Dockerfile, Terraform or Modal image definition changes. The Modal images and the entrypoint image install policyengine-observability from these locks, so they get 3.0.4. The 3.0.2 to 3.0.4 source diff reads no new environment variables.

Testing

Run from the worktree with uv 0.13.6 and Python 3.13, as CI does.

Check Failing test (f83610cf) Dependency (78817f9e) Removal (3d9109d2)
Observability library tests 4 failed, 38 passed 42 passed 36 passed (the 6 test_scopes.py tests are deleted)
Pyright, six affected packages no errors no errors no errors
Contract, persistence, entrypoint, gateway tests — 322, 40, 742, 268 passed 322, 40, 742, 268 passed
Executor tests — 1876 passed, 22 skipped 1876 passed, 22 skipped

On the final commit:

  • python -m scripts.rules pyright --sync <package>: no errors for all eight packages, including libs/policyengine-fastapi and projects/policyengine-apis-integ.
  • Ruff 0.15.22: ruff format --check projects/*/src libs/*/src and ruff check . pass, and the new test is formatted.
  • python scripts/run_quality_guards.py: every guard passed, including file-length and locked-installs.
  • python -m scripts.rules guard --base-ref origin/main: passed. python -m scripts.rules render leaves no diff.
  • python -m unittest discover -s scripts/rules/tests -t .: 246 tests passed.
  • .github/scripts/check-uv-locks.sh, after ./scripts/generate-clients.sh: every lock matches its pyproject.toml.

🤖 Generated with Claude Code

anth-volk and others added 3 commits October 27, 2026 21:41
The services will open spans and operations with runtime.span(...) and
runtime.operation(...) directly, which Pyright accepts only when
policyengine-observability annotates the scope's __exit__ and __aexit__
as returning Literal[False]. With a bool annotation, Pyright reads the
scope as possibly suppressing the exception raised in its block, and
reports every variable the block assigns as possibly unbound after it.

The test reads both annotations with typing.get_type_hints, as the
upstream test does, for a span and an operation of a real runtime. It
fails while 3.0.2 is locked, which annotates them as bool, and guards the
removal of the scopes module against a downgrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.0.4 annotates the span and operation scopes' __exit__ and __aexit__
as returning Literal[False] instead of bool (PolicyEngine/policyengine-observability#38),
so Pyright no longer reads a scope as possibly suppressing the exception
raised in its block. The scope-typing test now passes.

The five requirements in the observability library, the entrypoint, the
gateway and the executor (its dependencies and its Modal image group)
rise from >=3.0.2,<4 to >=3.0.4,<4, since the services will rely on the
fix. Each of the six locks that include the package was relocked with
uv 0.13.6, the release setup-uv@v8.1.0 installs in CI, by
`uv lock --upgrade-package policyengine-observability`. In every lock
only policyengine-observability moves (3.0.2 to 3.0.4); the other
changes are the requirement specifiers recorded for the local path
packages. 3.0.4 declares the same dependencies as 3.0.2, and also brings
3.0.3's fix, which keeps the exception recorded with record_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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-observability 3.0.4 types its scopes as never suppressing
an exception, so policyengine_simulation_observability.scopes, which
returned runtime.span(...) and runtime.operation(...) unchanged but
retyped for Pyright, is no longer needed.

- The 80 calls in the entrypoint (1), the executor (44) and the gateway
  (35), in 15 modules, call the runtime directly:
  scopes.span(runtime, ...) becomes runtime.span(...) and
  scopes.operation(runtime, ...) becomes runtime.operation(...), with
  the same remaining arguments, and each module drops the unused import.
- scopes.py and its tests, test_scopes.py, are deleted. No __init__,
  registry entry or workflow path filter named them.
- No test patched or replaced the scopes module: every test stand-in
  replaces the runtime or its span and operation methods, which the
  code still calls. The wrapper passed attributes=None and
  remote_context=None when a caller left them out; no stand-in records
  or asserts on omitted keywords.
- The observability and type-checking guides now say to open spans and
  operations with runtime.span(...) and runtime.operation(...), which
  3.0.4 types correctly.

Pyright still reports no errors in any package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Move to policyengine-observability 3.0.4 and remove the scopes module

1 participant