Repository navigation
Move to policyengine-observability 3.0.4 and remove the scopes module - #959
Merged
Merged
Conversation
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>
anth-volk
marked this pull request as ready for review
October 27, 2026 20:14
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #958
Same-Repository Draft PR Check
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 thatruntime.span(...)andruntime.operation(...)return as returningLiteral[False]instead ofbool. Pyright therefore no longer reads a scope as possibly suppressing the exception raised in its block, sopolicyengine_simulation_observability.scopes, which only returned those scopes retyped asAbstractContextManager[object], is no longer needed.The history is in test-first order:
f83610cf):libs/policyengine-simulation-observability/tests/test_scope_exit_typing.pyreads the return annotation of__exit__and__aexit__withtyping.get_type_hints, as the upstream test does, for a span and an operation of a real runtime, and assertsLiteral[False]. With 3.0.2 locked, all 4 cases fail (<class 'bool'> == typing.Literal[False]). It guards the removal ofscopesagainst a downgrade.78817f9e): the five requirements rise from>=3.0.2,<4to>=3.0.4,<4: the observability library, the entrypoint, the gateway, and the executor's dependencies and itsmodal-simulation-imagegroup. The six locks that include the package (those four packages, pluslibs/policyengine-simulation-contractandlibs/policyengine-stage12-persistence, which include the observability library by path) were relocked withuv lock --upgrade-package policyengine-observability. The test passes.3d9109d2):scopes.span(runtime, ...)becomesruntime.span(...), andscopes.operation(runtime, ...)becomesruntime.operation(...), with the same remaining arguments; each module drops the unused import;scopes.pyandtest_scopes.pyare deleted;docs/engineering/skills/observability.mdanddocs/engineering/skills/type-checking.mdnow say to open spans and operations withruntime.span(...)andruntime.operation(...), which 3.0.4 types correctly;changelog_entry.yamlhas achangeditem.Lock changes
Relocked with uv 0.13.6.
pr.ymldoesn't pin a uv version:astral-sh/setup-uv@v8.1.0installs the latest release, which is 0.13.6 today. In every lock, onlypolicyengine-observabilitymoves, from 3.0.2 to 3.0.4. The other changed lines are the>=3.0.4,<4specifiers recorded for the local path packages that declare it.libs/policyengine-simulation-observability/uv.locklibs/policyengine-simulation-contract/uv.locklibs/policyengine-stage12-persistence/uv.lockprojects/policyengine-simulation-entry/uv.lockprojects/policyengine-simulation-gateway/uv.lockprojects/policyengine-simulation-executor/uv.lock3.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 withrecord_exceptionstays on therequest.completedrecord and the server span.Tests that replace the runtime
No test patched or replaced the
scopesmodule. 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 intest_resolve_region_scopes_us_state_from_national_dataset. Every test stand-in replaces the runtime object or itsspanandoperationmethods, which the code still calls, so each one still applies. The one behaviour difference: the wrapper always passedattributes=Noneandremote_context=Nonewhen 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.pyassertsbackend._runtime.span.assert_called_once_with(..., attributes={...}); the call passesattributes, so the assertion is unchanged.projects/policyengine-simulation-executor/tests/test_stage12_modal_app.pyrecordsoperation's**kwargs; all fourv2_app.pyoperations passattributesandremote_context.ScopeRecordingRuntime.operationintest_stage12_failure_causes.pyassertsremote_context is None;tracked_stagenow omits it, so its default isNone.projects/policyengine-simulation-gateway/tests/test_job_progress.pyreplacesruntime.spanon the application's runtime, whichjob_status_routes.pystill calls.**kwargsor give each keyword aNonedefault, 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 (67reportPossiblyUnboundVariable, 6reportReturnType), 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-observabilityfrom 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.
f83610cf)78817f9e)3d9109d2)test_scopes.pytests are deleted)On the final commit:
python -m scripts.rules pyright --sync <package>: no errors for all eight packages, includinglibs/policyengine-fastapiandprojects/policyengine-apis-integ.ruff format --check projects/*/src libs/*/srcandruff check .pass, and the new test is formatted.python scripts/run_quality_guards.py: every guard passed, includingfile-lengthandlocked-installs.python -m scripts.rules guard --base-ref origin/main: passed.python -m scripts.rules renderleaves 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 itspyproject.toml.🤖 Generated with Claude Code