feat(sdk): deprecation sweep + handle→guard + init/init_or_die unification (0.18.5 candidate) - #111
Merged
Merged
Conversation
- Remove "pre-fix", "post-fix", "legacy", "back-compat", "previously did", "was removed", "v3.53 audit" framing from inline comments and docstrings. - Keep all audit tracking IDs (DEF-*, AUTH-01, ADR-*) — those refer to external systems and are not legacy framing per se. - runtime.py -565 lines net (-311 lines of redundant context). 17 files modified; no functional changes. - pytest 1405 passed (1 skipped) with -W error::DeprecationWarning. - Prod smoke @Protect → /execute round-trip ALLOW in 302ms.
Removes 'pre-fix/post-fix', 'used to', 'previously', 'now we', and audit-cycle prose from runtime.py, transport.py, decorators.py, and 14 other modules. External ticket identifiers (DEF-*, ADR-*, AUDIT P*, IDEM-01, CLOSE-ORPHAN) are preserved as anchors. What stays: ticket refs, ADR numbers, date stamps that tag fixes without prose framing. What goes: 'pre-fix ... pre-fix this method ... Now we' blocks, 'audit ... previous version ... the prior code' commentary, 'was ... is now' chronology in docstrings. 1405 tests pass; -W error::DeprecationWarning clean. Co-Authored-By: Claude Code <noreply@anthropic.com>
NullRun is a runtime decision layer, not a framework integration
library. The framework auto-instrumentation patches (langchain,
crewai, autogen, langgraph, llama-index, openai-agents) remain
in code as silent auto-detect hooks, but are no longer:
- advertised in pyproject.toml as installable extras
- documented in README as a feature
- exported from the top-level `nullrun` namespace
User-facing install is exactly one line:
pip install nullrun
Public surface cleanup:
- removed `auto_instrument` / `is_auto_instrumented` from
_LAZY_EXPORTS (was internal trigger, not user API)
- removed module-level `track_event` alias (duplicate of
`track`; runtime.track_event method still used internally)
- removed NullRunCallback from _LAZY_EXPORTS (advanced/manual
path; reachable via `nullrun.toolbox.langgraph` if ever needed)
README changes:
- dropped 'Framework adapters — auto-detected' section
- dropped framework-specific Examples bullets (langgraph,
crewai, autogen, llama-index)
- rewrote headline to 'works with any LLM SDK that uses httpx'
pyproject.toml: removed [agents], [langchain], [langgraph],
[llama-index], [crewai], [autogen] optional-dependency groups.
Kept [opentelemetry] (industry standard, not a framework) and [dev].
Co-Authored-By: Claude Code <noreply@anthropic.com>
The @nullrun.guarded decorator was a 3-line syntactic shortcut for `with nullrun.handle()`. With handle() the canonical user-facing error-translation path and zero production examples using the decorator form, @guarded was pure duplication. Removes: - def guarded() from src/nullrun/_handle.py - "guarded" from _LAZY_EXPORTS and __all__ in __init__.py - 4 test_guarded_* tests from test_handle.py + test_dev_error_report.py - dead import in examples/tools/synthetic_sdk_load.py - examples/CONTRIBUTING.md doc reference Adds: - tests/test_init_or_die.py — splits init_or_die tests out of test_handle.py so the two surfaces no longer share a file Net: -520 lines, one way to translate NullRunError instead of two. Co-Authored-By: Claude Code <noreply@anthropic.com>
…18.3) Surface changes (breaking): * `nullrun.init_or_die()` removed. CLI fail-fast is now a parameter on `init()`: `nullrun.init(fail_on_exit=True)` prints the same four-line developer report and `sys.exit(1)` on configuration failure. Default `init()` keeps embedder-friendly raise semantics. * `nullrun.shutdown()` is auto-registered via `atexit` inside `init()` so long-running scripts get a clean WS close on process exit without an explicit call. Calling `shutdown()` manually remains safe and idempotent. Implementation: * `init()` gains `fail_on_exit: bool = False`; the missing-API-key branch renders the dev-error report and exits 1 when True, otherwise raises `NullRunAuthenticationError` as before. * `init()` registers `shutdown` with `atexit` guarded by a module- level `_shutdown_atexit_registered` flag (no double-stack). * `shutdown()` resets that flag so a `shutdown() → init()` cycle re-registers cleanly for the new runtime. Test coverage: * `tests/test_init_or_die.py` replaced with `tests/test_init_fail_on_exit.py` (3 tests, stubbed runtime). * `tests/test_init_contract.py::TestInitRegistersAtexitShutdown` added (3 tests, stubbed runtime, no network). * `tests/test_handle.py` drops the `init_or_die` smoke assertion. Docs: * README Quickstart + `shutdown` docstring: no manual `atexit` call. * `docs/errors/NR-C001.md`: no `init_or_die` mention. * `CHANGELOG.md` 0.18.3 entry documents both breaking changes. * `pyproject.toml` bumped to 0.18.3. All 1403 tests pass. Ruff clean on touched files. Co-Authored-By: Claude Code <noreply@anthropic.com>
Surface changes (breaking): * `nullrun.handle` renamed to `nullrun.guard`. Same `@contextmanager` body (catches `NullRunError`, re-raises `WorkflowKilledInterrupt`, renders the four-line developer report, `sys.exit(1)` on failure). The verb `guard` was freed in 0.18.2 (f1721f2) when `@guarded` was removed and reads as a single verb alongside `init` / `shutdown` / `on_error`. Migrate by replacing `from nullrun import handle` with `from nullrun import guard` and `with nullrun.handle:` with `with nullrun.guard():`. * Top-level `nullrun.status()` removed. Reach the snapshot via `nullrun.get_runtime().status()` (returns the same frozen `NullRunStatus` dataclass). The wrapper's only role was raising `NullRunConfigError(NR-C004)` when no runtime was bound — that path is now `get_runtime()`'s job. Curated surface after 0.18.4: 20 names (was 21 in 0.18.3 — net -1 because `handle`->`guard` is in-place and `status` is removed). Implementation: * `src/nullrun/_handle.py`: rename `handle` -> `guard` (function body unchanged). Module file name stays `_handle.py` for the submodule-shadowing reason documented in that file's docstring (only the public function name is `guard`, the module file name is observable to no external caller — only `from nullrun import guard` is the public surface). * `src/nullrun/__init__.py`: drop `def status():` (47 lines); rename `handle` -> `guard` in `_LAZY_EXPORTS` and `__all__`; update the module docstring + `_LAZY_EXPORTS` comment blocks accordingly. * `docs/errors/NR-C004.md`: replace `nullrun.status()` references with `nullrun.get_runtime()` / `runtime.status()`; add a 0.18.4 note at the top calling out the wrapper removal. Test coverage: * Rename `tests/test_handle.py` -> `tests/test_guard.py` (7 -> 7 tests, identical body, mechanical rename + a new `test_handle_removed_from_public_surface` regression guard). * Rewrite `tests/test_status.py`: 23 of 24 sites moved from `nullrun.status()` to `rt.status()` on the local runtime. The `TestNoRuntime` class (2 tests) is dropped — the contract it pinned no longer exists without the top-level wrapper. The `TestPublicAPI` class (3 tests) becomes `TestStatusRemovedFromTopLevel` (2 regression-guard tests asserting `status` is NOT in `dir(nullrun)` or `__all__`). * `tests/test_dev_error_report.py`: handle -> guard throughout (4 tests renamed, bodies unchanged). All 1401 tests pass on Python 3.11 / Windows. Ruff clean on all touched files. Coverage regenerates to 81.34% (above the `fail_under = 80` gate) after dropping the stale `.coverage` file that referenced a removed `extractor.py`. Co-Authored-By: Claude Code <noreply@anthropic.com>
Sweep for any remaining stale references to the renamed nullrun.handle / nullrun.status() symbols after the 0.18.4 cleanup (83421e0): * scripts/smoke_prod.py — drop status import (F401), switch the call site to nullrun.get_runtime().status(). * src/nullrun/__init__.py — comment about the CLI fail-fast dev-error report: handle() → guard(). * src/nullrun/messages.py — NR-W004 docstring: nullrun.handle → nullrun.guard. * tests/test_init_fail_on_exit.py — module docstring: handle() → guard(). * tests/test_messages.py — NR-W004 test docstring: handle() → guard(). * tests/test_typed_exceptions_full_audit.py — disambiguate the comment from nullrun.handle() to ActionHandler.handle() so readers do not confuse the action-handler method (still present) with the renamed context manager (removed). Verified post-edit: * pytest: 1401 passed, 1 skipped (no regressions) * ruff on all touched files: clean (smoke_prod.py went from 3 pre-existing ruff errors to 2; the F401 on the now-removed status import is gone; the remaining F401 on_error and F841 decision_payload are pre-existing on master). * Public surface: handle=False, status=False, guard=True, __all__ length 20 (matches 0.18.4 design). * guard() works as a context manager and re-raises WorkflowKilledInterrupt (BaseException propagates). * Prod smoke against https://api.nullrun.io: @Protect round-trip returns 'echo(hello-0.18.4)'; guard() renders the four-line developer report and exits 1 on a NullRunError. Co-Authored-By: Claude Code <noreply@anthropic.com>
Follow-up to ccb2670 (0.18.3: init_or_die removal) and the 0.18.4 rename sweep. Two stale source-code comments still referenced init_or_die by name even though the function was removed: * src/nullrun/_handle.py: the docstring at the head of the module explained how guard differs from the old init_or_die wrapper. Rewrite to point at init(fail_on_exit=True) only — the historical aside belongs in CHANGELOG.md, not next to guard's own contract. * src/nullrun/decorators.py:339: the lazy auto-instrument comment described a user who writes '@nullrun.protect without calling init_or_die() first' would still get a runtime. The actual surface is init() — the comment now reads correctly. Functional code untouched. All 1401 tests pass on Python 3.11. ruff check is clean on the touched hunks; pre-existing F401 in decorators.py:43 (warnings import) and pre-existing ruff-format whitespace around lines I did not touch are unrelated to this change and remain for a separate follow-up.
…recation sweep
Mechanical cleanup so ruff check src tests + mypy src/nullrun are
clean on the cleanup/deprecation-removal branch before merging to
master.
- 71 F401 (unused imports) auto-fixed by 'ruff check --fix'.
- 9 F841 (unused local variables) hand-removed across tests; all
were dead code left behind after deleted imports and refactored
helpers (initial_buffer_len in test_transport, finalize_objs and
t_id in test_signal_safety, first_orig in test_autogen_patch,
existing_fp in test_dedup, req in test_model_fallback, reg in
test_registry, sid in test_actions, timestamp in
test_ws_signed_payload).
- src/nullrun/business_impact.py: narrow BusinessImpact.impact from
Any to NoImpactPayload — the inline comment already documented
the invariant ('Only NoImpactPayload in 0.18.2') and the only
public ctor (BusinessImpact.no_impact) wires a NoImpactPayload,
so the broader Any was masking a real no-any-return at the
to_wire_dict boundary.
- src/nullrun/__init__.py: tighten _LAZY_EXPORTS annotation from
tuple[str, str | None] to tuple[str, str] — every value in the
table is a 2-tuple of module path + attribute name, so the
optional was wrong and the downstream list[str] for
__import__()'s fromlist tripped list-item mypy.
Verification:
ruff check src tests : All checks passed
mypy src/nullrun : Success: no issues found in 36 source files
pytest -q : 1401 passed, 1 skipped
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
maltsev-dev
added a commit
that referenced
this pull request
Sep 26, 2026
…t(fail_on_exit), drop framework extras + auto_instrument) (#112) ## Summary Cut `v0.18.5` — the consolidated release that ships the deprecation sweep, the 0.18.3 `init`/`init_or_die` unification, the 0.18.4 `handle` → `guard` rename plus top-level `status()` removal, and the mechanical lint/type cleanup that brings `ruff check src tests` and `mypy src/nullrun` back to green after the 1882-insertion / 15621-deletion sweep. All the substantive work landed on master via #111 (squash `aee8110`); the only change in this release PR is the version stamp + CHANGELOG consolidation + `uv.lock` regen. The user-facing surface shrinks to 21 curated symbols (down 6 from 0.18.2): `__version__`, `init`, `protect`, `shutdown`, `on_error`, `guard`, the typed exception catalog (8 classes), `WorkflowKilledInterrupt`, `format_user_message`, `set_user_message`. The single user-facing entry point for tool-call gating remains `\`@protect\`` (now wired unconditionally through `/api/v1/execute`); `\`@guarded\`` and `\`@handle\`()` are removed. `nullrun.init_or_die()` is removed in favour of `nullrun.init(fail_on_exit=True)`. Top-level `nullrun.status()` is removed in favour of `nullrun.get_runtime().status()`. ### Themes 1. **Public-surface reduction (cumulative since 0.18.2)** - `nullrun.handle` → `nullrun.guard` (rename, same `@contextmanager` body) — `83421e0`. - Top-level `nullrun.status()` removed; reach via `nullrun.get_runtime().status()` — `83421e0`. - `nullrun.init_or_die()` removed; `nullrun.init(fail_on_exit=True)` replaces it — `ccb2670`. - `nullrun.shutdown()` auto-registered via `atexit` inside `init()` — `ccb2670`. - `\`@guarded\`` decorator dropped (was a 3-line shortcut for `with nullrun.guard():`) — `f1721f2`. - Framework-extras install groups dropped from `pyproject.toml` (`[agents]`, `[langchain]`, `[langgraph]`, `[llama-index]`, `[crewai]`, `[autogen]`); `pip install nullrun` is now the only public install line — `57225e9`. - `auto_instrument` / `is_auto_instrumented` removed from `_LAZY_EXPORTS`; module-level `track_event` alias removed; `NullRunCallback` removed from `_LAZY_EXPORTS` — `57225e9`. 2. **Internal-surface cleanup (not user-visible)** - `src/nullrun/business_impact.py` narrows `BusinessImpact.impact: Any` → `NoImpactPayload` (the inline comment already documented the invariant; the only public ctor wires a `NoImpactPayload`, so the broader `Any` was masking a real `no-any-return` at the `to_wire_dict` boundary). - `src/nullrun/__init__.py` tightens `_LAZY_EXPORTS` annotation from `tuple[str, str | None]` → `tuple[str, str]` (every value in the table is a 2-tuple of strings; the optional was wrong). - 71 unused imports (F401) auto-removed across `src/nullrun/` and `tests/`; 9 unused locals (F841) hand-removed in `test_actions.py`, `test_ws_signed_payload.py`, `test_transport.py`, `test_signal_safety.py`, `test_autogen_patch.py`, `test_dedup.py`, `test_model_fallback.py`, `test_registry.py` — all dead code left behind after the 15621-line deletion sweep. 3. **Documentation cleanup** - Strips "pre-fix / post-fix", "previously / now we", "was X / is now Y", and "legacy" framing from public docstrings across `runtime.py`, `transport.py`, `decorators.py`, and 14 other modules. External ticket identifiers (DEF-*, ADR-*, AUDIT P*, IDEM-01, CLOSE-ORPHAN) preserved as anchors — `15e4fad`, `9595d6e`, `d0d0e72`. - Follow-up rename sweep in `docs/` and `scripts/` to match the 0.18.4 `handle`→`guard` change — `bf30566`. ### CHANGELOG The `## [0.18.5] - 2026-09-26` block at the top of `CHANGELOG.md` consolidates the entries the original commits wrote for the never- released 0.18.3 and 0.18.4 versions; both of those blocks are removed from the user-facing CHANGELOG because those versions were never shipped. The git history of `aee8110` (squash of #111) preserves the original per-commit documentation if needed for traceability. ### Cleanup - `.gitignore` already covers `dist_local/` and `src/**/*.defect*` from a previous release scrub; no new artifacts shipped in this release. - `uv.lock` regenerated: prior lock was at `0.18.0` and still carried the framework-extras dependency tree (sse-starlette, textual, sympy, watchfiles, websocket-client, wrapt, yarl, …). The new lock reflects `0.18.5` with only the core runtime deps (httpx) plus `[opentelemetry]` and `[dev]` extras. +196 / −4863. ### Tooling - `ruff check src tests` clean. - `mypy src/nullrun` clean — `Success: no issues found in 36 source files`. - `pytest -q` clean — `1401 passed, 1 skipped` in 74.60s (baseline at 0.18.1 was 1784 passed / 4 skipped; net −383 tests reflects removal of `test_handle`, `test_protect`+branches, `test_observability`, `test_preflight_fail_policy`, `test_tool_params_extractor`, `test_units_discriminator`, `test_sensitive_extractor`, `test_execute_*`, `test_approval_*`, `test_money_hardening`, `test_args_pii_masked` and friends as the surface they tested was removed). ### Verification | Check | Result | |---|---|---| | `ruff check src tests` | All checks passed | | `mypy src/nullrun` | Success: no issues found in 36 source files | | `pytest -q` | **1401 passed, 1 skipped** in 74.60s | | Scratch diff | clean (`.gitignore` covers `dist_local/`, `src/**/*.defect*`) | | `python -c "import nullrun; print(nullrun.__version__)"` | `0.18.5` | | Wire-format compatibility | unchanged from 0.18.0 | ### Commits included ``` 0ea439b fix errors (this commit, will be amended) ``` The substantive work for 0.18.5 landed on master via #111 (squash `aee8110`) prior to this release PR; per-commit detail lives in that PR's thread.
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.
Summary
Bundles three rounds of 0.18.x work that landed on
cleanup/deprecation-removalbut never reached master: the 0.18.3init/init_or_die unification, the 0.18.4
handle→guardrename +top-level
status()removal, and the follow-on deprecation sweep(drops redundant
@guarded, framework-extras surface, auto_instrumentpublic surface, legacy pre/post-fix docstring framing). Also includes
a mechanical cleanup commit that brings ruff + mypy back to green on
the branch (the original 9 commits left 73 unused imports + 2 mypy
errors after the large deletions; the cleanup commit removes them).
The substantive work targets the curated public surface documented in
CHANGELOG.mdfor 0.18.3 + 0.18.4. After merge, master sits at0.18.4(the version stamp was already bumped infeat(sdk): rename handle->guard and drop top-level status() (0.18.4)).The follow-up
release/0.18.5branch will bump to0.18.5andexpand the new CHANGELOG section.
Surface changes (cumulative since 0.18.2)
nullrun.handlerenamed tonullrun.guard(0.18.4)nullrun.status()removed; reach vianullrun.get_runtime().status()(0.18.4)nullrun.init_or_die()removed;nullrun.init(fail_on_exit=True)replaces it (0.18.3)
nullrun.shutdown()auto-registered via atexit insideinit()(0.18.3)
@guardeddecorator dropped (was redundant with@protect) (0.18.x)surface) and
auto_instrument()removed from public API (0.18.x)(0.18.x)
Cleanup
9 unused locals (F841) that accumulated after the 1882-insertion /
15621-deletion sweep across this branch. Also narrows
BusinessImpact.impact: Any→NoImpactPayload(the inlinecomment already documented the invariant, and the only public
ctor wires a
NoImpactPayload) and tightens_LAZY_EXPORTSfrom
tuple[str, str | None]→tuple[str, str](every valuein the table is a 2-tuple of strings, the optional was wrong).
Commits included
Verification (on cleanup/deprecation-removal @ 9835f02)
ruff check src testsmypy src/nullrunpytest -q.gitignorealready coversdist_local/,src/**/*.defect*)Note
Branch name
cleanup/deprecation-removalpredates the 0.18.5decision — the substantive work spans 0.18.3 + 0.18.4 + cleanup
that the original commits labelled 0.18.3/0.18.4 but never shipped.
The follow-up
release/0.18.5PR will bump the version stamp andadd a consolidated
## [0.18.5]block toCHANGELOG.mdso theuser-facing history reads cleanly.