Skip to content

Enable ruff exception rules (TRY, EM, BLE) - #783

Draft
anth-volk wants to merge 16 commits into
ruff-docstringsfrom
ruff-exceptions
Draft

anth-volk wants to merge 16 commits into
ruff-docstringsfrom
ruff-exceptions

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #782

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

Phase 1 step 1.8, group 5: exceptions and messages. Stacked on #779.

  • Rules: ruff.toml gains TRY, EM and BLE. All 1,029 findings at the base are fixed, one commit per project: executor 522, contract 226, Stage 12 persistence 102, entry 70, gateway 52, repo scripts 28, integration tests 12, fastapi 11, observability 6.
  • Messages (EM and TRY003): ruff's fix builds each message in a variable before the raise (520 places). A script put every message back inline: all 74 files reproduce the base's syntax tree exactly, so the message text is unchanged. No tracked Python file used msg before.
    • Error codes renamed: where the first argument of SPMInputError or ParityMismatch is an error code, the hoisted variable is named error_code, not msg (46 places).
  • Raises inside try blocks (TRY301 9, TRY300 4): each raise moves into a small private helper, or the return moves into an else:. Each handler still receives the same exceptions: for example, contract spm.py still wraps the helpers' ValueError in the same SPMInputError.
  • Exception types (TRY004 10):
    • Changed (2): src/modal/utils/qualify_stage12_parity.py raises TypeError for its two type checks. It's an operator command-line tool that nothing calls or catches.
    • Kept (8), each with a reason: a caller catches the current type, the error crosses a Modal boundary, telemetry records the type name, or a test asserts it. None are in Pydantic validators.
  • Broad except Exception catches (BLE001 13): none narrowed, all kept with an inline reason. Telemetry must never fail a request or a Modal call; a worker failure must become a failed-job or failed-batch response; and a token decode failure must become a 403, never a 500. Ruff accepts only a traceback-logging call instead, which would raise log levels.
    • Older suppressions: seven noqa: BLE001 comments, inert until now, had no reason or used the - reason style. They now carry a reason in the standard # reason form.
  • Error logs (TRY400 9):
    • Gains a traceback (1): the gateway's auth misconfiguration log, with the same message.
    • Same output (1): observability's error log uses logger.exception and still logs the original exception's traceback.
    • Kept traceback-free (7), with a reason:
      • Four Stage 12 logs record only the error type, so request and calculation data can't leak.
      • One timeout log is fully described by its timeout_seconds field.
      • Two bad-token logs in the JWT decoder: callers control the token, so a traceback per bad token would let them flood the logs.
  • TRY201 (1): raise err becomes a bare raise in the JWT decoder.
  • Modal images: files in both the executor and gateway images change. _image_setup.py:fetch_artifacts is a build step and only its messages moved, but expect that layer and the snapshot layer after it to rebuild on the next deploy.
  • Registry: sim.quality.ruff-lint now lists BLE, EM and TRY. The shared rules tool needed no changes and is still byte-identical with the Public API (tool sha256 db83d5ad2aa4). The Public API's group 5 is Enable ruff exception rules (TRY, EM, BLE) policyengine-api#3913.

Testing

🤖 Generated with Claude Code

anth-volk and others added 16 commits October 15, 2026 00:04
Exception messages move into a msg variable before the raise (EM), with
the text unchanged. The JWT decoder re-raises with a bare raise (TRY201).
Its two JWKS lookup error logs stay traceback-free under a TRY400 noqa,
because callers control the token and a traceback per bad token would let
them flood the logs. The token decode and the GCP project-id probe keep
except Exception under a BLE001 noqa: any decode failure must become a 403,
and any probe failure falls back to GOOGLE_CLOUD_PROJECT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise (EM), with
the text unchanged; Pydantic validators keep raising ValueError, so
callers still get the same ValidationError. resolve_spm_selection and
validate_spm_result move their try bodies into private helpers so no raise
sits inside the try (TRY301, TRY300); the same ValueErrors still become
the same SPMInputError, chained from the original.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rary

Exception messages move into a msg variable before the raise (EM), with
the text unchanged. log_and_redact_exception's fallback log calls
logger.exception with the same explicit exc_info, so its output is
unchanged (TRY400). Its telemetry catch-all keeps except Exception under a
BLE001 noqa, because the helper must never raise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise, with the
text unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise (EM), with
the text unchanged. forward() returns the proxied response from an else
block (TRY300). The four Stage 12 diagnostic error logs stay
traceback-free under a TRY400 noqa: three record only the error type, so a
traceback could carry request or calculation data, and the dispatch
timeout is fully described by its timeout. The Stage 12 context capture
keeps its telemetry catch-all under a BLE001 noqa.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise (EM), with
the text unchanged. The auth misconfiguration log keeps its message and
gains a traceback through exc_info=True (TRY400). _runtime keeps
RuntimeError and _resolve_policyengine_route keeps ValueError under a
TRY004 noqa, because handlers and callers depend on those types. The
job-status and budget-window handlers keep their catch-alls under a
BLE001 noqa: call.get re-raises whatever the worker raised, and every
failure must become a failed-job response or fall back to the seed state.
The auth-bypass event keeps its catch-all too. The golden OpenAPI spec is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise (EM), with
the text unchanged. Raises inside try bodies move into private helpers
(_parse_policy_period_dates, _certified_us_spm_capability,
_require_artifact_digest, _require_local_authority_column), so the same
exceptions reach the same handlers (TRY301, TRY300); dump_package_version
narrows its try to the file read.

qualify_stage12_parity, a local operator CLI that nothing calls or
catches, now raises TypeError for a JSON value that is not an object. The
other TRY004 sites keep their types under a noqa: they cross a Modal
boundary, are documented or asserted by tests, or are recorded by type
name in telemetry and Stage 12 error summaries. The Stage 12 comparison
failure log stays traceback-free under a TRY400 noqa. Worker, cleanup and
telemetry catch-alls keep except Exception under a BLE001 noqa.

The Modal images copy src/modal and policyengine_simulation_executor, so
their layers change like any source edit; src/modal/app.py gains no
future import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exception messages move into a msg variable before the raise, with the
text unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select flake8-blind-except (BLE), flake8-errmsg (EM) and tryceratops
(TRY). The preceding commits fix every finding; deliberate catch-alls,
kept exception types and traceback-free error logs carry an inline noqa
with the reason. Tests get no exemption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enabling BLE activated suppressions that were written before the rule
was on. Seven had no reason, or kept it only in a comment above; they now
carry it inline, and the "- reason" ones use the "  # reason" form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The EM fix moves an exception's first literal argument into a variable
called msg. SPMInputError's first argument is an error code, followed by
the message, so those 20 variables are renamed error_code. The gateway
file also moves its BLE001 suppression reason to the "  # reason" form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sim.quality.ruff-lint now lists the group 5 families, and the rendered
rules page and AGENTS.md Rules block are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ParityMismatch takes a code and a schema path, not a message, so the 26
variables the EM fix named msg are renamed error_code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The colon after "(TRY, EM, BLE)" made YAML read the item as a mapping
rather than a string. The line also said error types were unchanged,
but two type checks in qualify_stage12_parity.py now raise TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#743 and #744 landed on main after this stack branched. EM and TRY003,
which this PR enables, flag 34 raises in their sentinel, routing and
package-update code that pass a literal message. Each message now moves
into a msg variable before the raise, with the text unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#789 landed on main after this stack branched. EM and TRY003, which this
PR enables, flag five raises in its new Stage 12 tests that pass a literal
message; each message now moves into a msg variable first. TRY301 flags
the raise inside the pooling test's transaction, which moves into a small
helper so the same RuntimeError still ends that transaction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant