From 299765508ebda28956c1613d9c00dd2967595b6f Mon Sep 17 00:00:00 2001 From: goodboy Date: Wed, 23 Sep 2026 17:59:46 -0400 Subject: [PATCH] Drop deprecated `Context.result()` wrapper Use `Context.wait_for_result()` directly at the nine remaining call sites, including the debugger's root stdio-lock handshake. Preserve `hide_tb=False` in the payload-spec test and keep the outcome-renaming TODO beside the supported API. Update the API removal note, ctx-specific comments and drain diagnostics, and include the API-removal Prompt-IO record. Prompt-IO: ai/prompt-io/opencode/20260922_context_result_removal.md (this patch was generated in some part by `opencode` using `gpt-6-astra` (`openai`)) --- .../20260922_context_result_removal.md | 99 +++++++++++++++++++ .../20260922_context_result_removal.raw.md | 66 +++++++++++++ docs/api/context.rst | 5 +- tests/msg/test_pldrx_limiting.py | 4 +- tests/test_context_stream_semantics.py | 21 ++-- tests/test_infected_asyncio.py | 2 +- tests/test_inter_peer_cancellation.py | 19 ++-- tests/test_oob_cancellation.py | 2 +- tests/test_shm.py | 2 +- tractor/_context.py | 22 +---- tractor/_exceptions.py | 8 +- tractor/_streaming.py | 2 +- tractor/devx/debug/_tty_lock.py | 4 +- tractor/msg/_ops.py | 3 +- tractor/msg/types.py | 2 +- tractor/to_asyncio.py | 2 +- 16 files changed, 216 insertions(+), 47 deletions(-) create mode 100644 ai/prompt-io/opencode/20260922_context_result_removal.md create mode 100644 ai/prompt-io/opencode/20260922_context_result_removal.raw.md diff --git a/ai/prompt-io/opencode/20260922_context_result_removal.md b/ai/prompt-io/opencode/20260922_context_result_removal.md new file mode 100644 index 00000000..c710033f --- /dev/null +++ b/ai/prompt-io/opencode/20260922_context_result_removal.md @@ -0,0 +1,99 @@ +--- +model: openai/gpt-6-astra +service: opencode +session: unavailable +timestamp: 2026-09-22T15:43:25Z +git_ref: 19a61e6ea14ec70b0e0f9a24d207ea8d88bd4c4c +scope: code +substantive: true +raw_file: 20260922_context_result_removal.raw.md +--- + +## Prompt + +The human approved descriptor removal and requested Context.result +removal as a distinct small followup. Work only in the Tractor +context_fault_status worktree. First save the exact simplified status +boundary, including intended tests and provenance, using a private +index. Exclude .gitignore and .ai. Preserve the user-owned real index +containing the original descriptor-based implementation. Save tree +OIDs and binary patches under ignored .claude runtime state. + +Remove only the deprecated wrapper, migrate the nine identified calls, +preserve hide_tb=False, and update stale Context.result references and +API documentation. Preserve Portal/future result APIs, maintained +notes and TODOs, and the warnings import. Verify affected suites with +the supplied interpreter and PYTHONPATH, keeping reaping disabled and +reporting fixture blockers. Downstream changes belong to the parent. + +## Response summary + +Saved the pre-removal status tree before changing working source, then +removed Context.result and migrated all nine calls. wait_for_result +source is byte-for-byte unchanged. Updated documentation, diagnostic +strings and Context-specific comments, including the payload message +API reference. Moved the still-relevant outcome-renaming TODO beside +wait_for_result without changing its checkbox state. + +Existing public-result tests cover falsey payloads and repeated waits. +No additional mechanical API-presence regression was needed. + +## Files changed + +- tractor/_context.py: wrapper removal and preserved TODO relocation. +- tractor/devx/debug/_tty_lock.py: debugger context-result await. +- tests/test_inter_peer_cancellation.py: one call and references. +- tests/msg/test_pldrx_limiting.py: one call, hide_tb=False retained. +- tests/test_shm.py: one call. +- tests/test_infected_asyncio.py: one call. +- tests/test_context_stream_semantics.py: four calls and references. +- docs/api/context.rst: removal note and replacement. +- tractor/_exceptions.py, tractor/_streaming.py: comments/docstrings. +- tractor/msg/_ops.py: drain diagnostic names the supported API. +- tractor/msg/types.py: payload API reference. +- tractor/to_asyncio.py: Context API comment. +- tests/test_oob_cancellation.py: Context-specific TODO reference. +- This new provenance pair records only the separate API removal. + +## Verification + +All commands ran from the context_fault_status worktree, using: + +```xsh +$PYTHONPATH = '/home/goodboy/repos/tractor/wkts/context_fault_status:/home/goodboy/repos/tractor/wkts/ib_disconnect_cleanup/py313/lib/python3.13/site-packages:/home/goodboy/repos/piker/py313/lib/python3.13/site-packages' +py = '/home/goodboy/repos/tractor/wkts/ib_disconnect_cleanup/py313/bin/python' +@(py) -m pytest -p no:tractor._testing._reap tests/test_context_status.py tests/test_context_stream_semantics.py tests/test_ipc_eof.py -q +@(py) -m pytest -p no:tractor._testing._reap tests/msg/test_pldrx_limiting.py -q +@(py) -m pytest -p no:tractor._testing._reap tests/test_shm.py -q +``` + +Results: 90, 8 and 7 passed respectively; 105 passed total. +No failures or skips in those runs. Tractor import resolved inside +this worktree. TRACTOR_SPAWN_METHOD and TRACTOR_LOGLEVEL were unset; +ambient VIRTUAL_ENV pointed to piker/py313, but the explicit interpreter +above was used throughout. + +Not run: inter-peer and infected-asyncio suites require +track_orphaned_uds_per_test from the disabled reaper plugin; +infected-asyncio also requests reap_subactors_per_test. This is an +explicit coverage limitation, not a passing or skipped test result. +Debugger integration was not separately executed. + +A private AST audit confirms exactly nine migrated calls and no other +executable structural changes beyond deleting the wrapper (diagnostic +strings are normalized for that comparison). An independent source +comparison confirms wait_for_result itself is unchanged. Added Python +lines fit 69 columns; git diff --check passes. Final wrapping edits +after the test runs changed only formatting, confirmed by the audit. + +## Human edits + +The human explicitly selected API removal as a separate followup, +identified the nine call sites and downstream ownership, constrained +the implementation to wrapper removal, and required private snapshots, +real-index preservation, fixture-blocker reporting and provenance. +These are human-directed agent edits. No direct manual source edits +were observed during this followup. Acceptance remains human-owned. +The earlier provenance pair, including its original raw record, was +preserved unchanged; its result-method retention describes the prior +status boundary, superseded only by this separate followup. diff --git a/ai/prompt-io/opencode/20260922_context_result_removal.raw.md b/ai/prompt-io/opencode/20260922_context_result_removal.raw.md new file mode 100644 index 00000000..192282a9 --- /dev/null +++ b/ai/prompt-io/opencode/20260922_context_result_removal.raw.md @@ -0,0 +1,66 @@ +--- +model: openai/gpt-6-astra +service: opencode +timestamp: 2026-09-22T15:43:25Z +git_ref: 19a61e6ea14ec70b0e0f9a24d207ea8d88bd4c4c +diff_cmd: git diff HEAD~1..HEAD +--- + +## Generated response + +Remove the deprecated Context.result wrapper as a separate followup +to the simplified ContextStatus migration. Migrate nine calls across +six files to wait_for_result, retaining hide_tb=False. Document the +removal and update Context-specific comments and diagnostic strings. +Keep the outcome-renaming TODO with wait_for_result. + +> `git diff HEAD~1..HEAD -- tractor/_context.py` + +Delete only the wrapper; wait_for_result source is byte-for-byte +unchanged. Keep the warnings import used by send_yield. + +> `git diff HEAD~1..HEAD -- tractor/devx/debug/_tty_lock.py` +> `git diff HEAD~1..HEAD -- tests/test_inter_peer_cancellation.py` +> `git diff HEAD~1..HEAD -- tests/msg/test_pldrx_limiting.py` +> `git diff HEAD~1..HEAD -- tests/test_shm.py` +> `git diff HEAD~1..HEAD -- tests/test_infected_asyncio.py` +> `git diff HEAD~1..HEAD -- tests/test_context_stream_semantics.py` + +Migrate the debugger lock call and eight test calls, including four +context-stream calls. Preserve arguments and surrounding behavior. + +> `git diff HEAD~1..HEAD -- docs/api/context.rst` +> `git diff HEAD~1..HEAD -- tractor/_exceptions.py` +> `git diff HEAD~1..HEAD -- tractor/_streaming.py` +> `git diff HEAD~1..HEAD -- tractor/msg/_ops.py` +> `git diff HEAD~1..HEAD -- tractor/msg/types.py` +> `git diff HEAD~1..HEAD -- tractor/to_asyncio.py` +> `git diff HEAD~1..HEAD -- tests/test_oob_cancellation.py` + +Replace stale Context.result references with the supported method. +The API page explicitly records removal and its replacement. + +## Verification output + +Context-status, context-stream and IPC EOF: 90 passed in 76.04s. +Payload limiting, separate process: 8 passed in 3.64s. +Shared memory, separate process: 7 passed in 5.17s. +Total: 105 passed, no failures or skips in these runs. + +Inter-peer and infected-asyncio suites were not run: their module +fixture requirements depend on the disabled reaper plugin. No cleanup +plugin was enabled. Debugger integration was not separately executed. + +9 call migrations; wait_for_result source unchanged; AST OK +All added Python lines fit 69 columns + +git diff --check passed. Existing public-result tests already cover +falsey outcomes and repeated waits; no new presence-only test was added. +Original staged index SHA-256 remained unchanged through verification. + +## Diff boundary + +The eventual HEAD~1..HEAD pointers assume this pair accompanies only +the API-removal followup. Before committing, use the private snapshot +patches under .claude/wkts/context-status-boundary instead. The +pre-removal tree is 94858f83875d9809b67d517f2b0bc52ccbd09ded. diff --git a/docs/api/context.rst b/docs/api/context.rst index ff024bd9..80257687 100644 --- a/docs/api/context.rst +++ b/docs/api/context.rst @@ -51,9 +51,10 @@ The ``@context`` decorator maybe_error, outcome -.. deprecated:: 0.1.0a6 +.. note:: - ``Context.result()`` warns; use :meth:`Context.wait_for_result`. + ``Context.result()`` has been removed; replace calls with + :meth:`Context.wait_for_result`. .. note:: diff --git a/tests/msg/test_pldrx_limiting.py b/tests/msg/test_pldrx_limiting.py index b04111b0..579b15f6 100644 --- a/tests/msg/test_pldrx_limiting.py +++ b/tests/msg/test_pldrx_limiting.py @@ -359,7 +359,9 @@ def test_basic_payload_spec( assert first.field == 'yo' try: - res: None|PldMsg = await ctx.result(hide_tb=False) + res: None|PldMsg = await ctx.wait_for_result( + hide_tb=False, + ) assert res is None except MsgTypeError as mte: maybe_mte = mte diff --git a/tests/test_context_stream_semantics.py b/tests/test_context_stream_semantics.py index be67dd0c..e22cb5b3 100644 --- a/tests/test_context_stream_semantics.py +++ b/tests/test_context_stream_semantics.py @@ -55,7 +55,7 @@ from ._helpers import ( # call ``Context.started()`` to unblock this entry on the parent side. # the child task executes until complete and returns a final value # which is delivered to the parent side and retreived via -# ``Context.result()``. +# ``Context.wait_for_result()``. # - cancel termination: # context can be cancelled on either side where either end's task can @@ -514,7 +514,9 @@ def test_simple_context( if child_blocks_forever: await portal.run(assert_state, value=True) else: - assert await ctx.result() == 'yo' + assert ( + await ctx.wait_for_result() == 'yo' + ) if pointlessly_open_stream: async with ctx.open_stream(): @@ -593,7 +595,7 @@ def test_parent_cancels( ''' Verify that when the opening side of a context (aka the parent) cancels that context, the ctx does not raise a cancelled when - either calling `.result()` or on context exit. + either calling `.wait_for_result()` or on context exit. ''' async def check_canceller( @@ -608,7 +610,7 @@ def test_parent_cancels( and not child_returns_early ): try: - res = await ctx.result() + res = await ctx.wait_for_result() assert 0, 'Portal cancel should raise!' except ContextCancelled as ctxc: @@ -624,7 +626,7 @@ def test_parent_cancels( # silently! else: try: - res = await ctx.result() + res = await ctx.wait_for_result() except ContextCancelled as ctxc: pytest.fail(f'should not have raised ctxc\n{ctxc}') @@ -726,8 +728,9 @@ def test_parent_cancels( # # WHY: the `.cancel_actor()` case (cancel_method='portal') # will cause both: - # * the `ctx.result()` inside `.open_context().__aexit__()` - # * AND the `ctx.result()` inside `check_canceller()` + # * `ctx.wait_for_result()` inside + # `.open_context().__aexit__()` + # * AND `ctx.wait_for_result()` in `check_canceller()` # to raise ctxc. # # which should in turn cause `ctx._scope` to @@ -1504,7 +1507,9 @@ def test_maybe_allow_overruns_stream( print('Requesting `ctx.cancel()` in parent!') await ctx.cancel() - res: str|ContextCancelled = await ctx.result() + res: str|ContextCancelled = ( + await ctx.wait_for_result() + ) if cancel_ctx: assert isinstance(res, ContextCancelled) diff --git a/tests/test_infected_asyncio.py b/tests/test_infected_asyncio.py index ae907cdd..4a87f1fa 100644 --- a/tests/test_infected_asyncio.py +++ b/tests/test_infected_asyncio.py @@ -400,7 +400,7 @@ def test_context_spawns_aio_task_that_errors( async with expect_ctxc( yay=parent_cancels == 'actor', ): - await ctx.result() + await ctx.wait_for_result() if parent_cancels == 'context': # to tear down sub-acor diff --git a/tests/test_inter_peer_cancellation.py b/tests/test_inter_peer_cancellation.py index 3a43b1e7..d9a86faf 100644 --- a/tests/test_inter_peer_cancellation.py +++ b/tests/test_inter_peer_cancellation.py @@ -468,12 +468,13 @@ def test_peer_canceller( try: print('PRE CONTEXT RESULT') - res = await sleeper_ctx.result() + res = await sleeper_ctx.wait_for_result() assert res # should never get here pytest.fail( - 'Context.result() did not raise ctx-cancelled?' + 'Context.wait_for_result() did not ' + 'raise ctx-cancelled?' ) # should always raise since this root task does @@ -650,7 +651,8 @@ def test_peer_canceller( # propagated # else: - # since sleeper_ctx.result() IS called above + # since sleeper_ctx.wait_for_result() IS called + # above # we should have (silently) absorbed the # corresponding `ContextCancelled` for it and # `._scope.cancel()` should never have been @@ -694,7 +696,7 @@ def test_peer_canceller( # the recorded local erro should always be # the same as the one raised by the - # `sleeper_ctx.result()` call + # `sleeper_ctx.wait_for_result()` call assert ( le and @@ -715,7 +717,8 @@ def test_peer_canceller( assert not ctx.cancel_called assert not ctx.cancel_acked - # since sleeper_ctx.result() IS called + # since sleeper_ctx.wait_for_result() + # IS called # above we should have (silently) # absorbed the corresponding # `ContextCancelled` for it and thus @@ -736,7 +739,8 @@ def test_peer_canceller( # a `Portal.cancel_actor()` to the # sleeper and thus never should call # `ctx.cancel()` per say UNLESS the - # sleeper's `.result()` call above + # sleeper's `.wait_for_result()` call + # above # ctxc exception results in the # canceller's # `.open_context().__aexit__()` error @@ -1176,7 +1180,8 @@ def test_peer_spawns_and_cancels_service_subactor( await client_ctx.cancel() print( '-> root cancelling client,\n' - '-> root checking `client_ctx.result()`,\n' + '-> root checking ' + '`client_ctx.wait_for_result()`,\n' f'-> checking that sub-spawn {peer_name} is down\n' ) diff --git a/tests/test_oob_cancellation.py b/tests/test_oob_cancellation.py index 58d12d2b..df3840fc 100644 --- a/tests/test_oob_cancellation.py +++ b/tests/test_oob_cancellation.py @@ -40,7 +40,7 @@ from tractor import ( # typing # `raise scope_error from ctxc`? # # -[ ] a self-cancelled context, if not allowed to block on -# `ctx.result()` at some point will hang since the `ctx._scope` +# `ctx.wait_for_result()` will hang since the `ctx._scope` # is never `.cancel_called`; cases for this include, # - an `open_ctx()` which never starteds before being OoB actor # cancelled. diff --git a/tests/test_shm.py b/tests/test_shm.py index e63aef07..2737fe9e 100644 --- a/tests/test_shm.py +++ b/tests/test_shm.py @@ -80,7 +80,7 @@ def test_child_attaches_alot(): == _key ) - await ctx.result() + await ctx.wait_for_result() await portal.cancel_actor() diff --git a/tractor/_context.py b/tractor/_context.py index f41744f5..6a4023c7 100644 --- a/tractor/_context.py +++ b/tractor/_context.py @@ -1473,6 +1473,10 @@ class Context: raise remote_error + # TODO: consider renaming this API! + # -[ ] should be named `.wait_for_outcome()` and instead do + # a `.outcome.Outcome.unwrap()` ? + # async def wait_for_result( self, hide_tb: bool = True, @@ -1579,24 +1583,6 @@ class Context: # `self.outcome.unwrap()` here? return self.outcome - # TODO: switch this with above! - # -[ ] should be named `.wait_for_outcome()` and instead do - # a `.outcome.Outcome.unwrap()` ? - # - async def result( - self, - *args, - **kwargs, - ) -> Any|Exception: - log.warning( - '`Context.result()` is DEPRECATED!\n' - 'Use `Context.[no]wait_for_result()` instead!\n' - ) - return await self.wait_for_result( - *args, - **kwargs, - ) - @property def maybe_error(self) -> BaseException|None: ''' diff --git a/tractor/_exceptions.py b/tractor/_exceptions.py index 9434676d..0de1b55e 100644 --- a/tractor/_exceptions.py +++ b/tractor/_exceptions.py @@ -1376,7 +1376,7 @@ def _raise_from_unexpected_msg( and raised locally as -> `trio.EndOfChannel` - All other mis-keyed msgss (like say a "final result" - 'return' msg, normally delivered from `Context.result()`) + 'return' msg, normally from `Context.wait_for_result()`) are re-boxed inside a `MessagingError` with an explicit exc content describing the missing IPC-msg-key. @@ -1433,7 +1433,8 @@ def _raise_from_unexpected_msg( log.debug(message) # TODO: if the a local task is already blocking on - # a `Context.result()` and thus a `.receive()` on the + # a `Context.wait_for_result()` and thus `.receive()` + # on the # rx-chan, we close the chan and set state ensuring that # an eoc is raised! @@ -1450,7 +1451,8 @@ def _raise_from_unexpected_msg( # call (likely by another task using a broadcast receiver) # doesn't accidentally pull the `return` message # value out of the underlying feed mem chan which is - # destined for the `Context.result()` call during ctx-exit! + # destined for `Context.wait_for_result()` during + # ctx-exit! stream._eoc: Exception = eoc # in case there already is some underlying remote error diff --git a/tractor/_streaming.py b/tractor/_streaming.py index 6c122eb8..db72bb0b 100644 --- a/tractor/_streaming.py +++ b/tractor/_streaming.py @@ -516,7 +516,7 @@ class MsgStream(trio.abc.Channel): # is arguably more confusing since we lose detection of the # runtime's closure of ``rx_chan`` in the case where we may # still need to consume msgs that are "in transit" from the far - # end (eg. for ``Context.result()``). + # end (eg. for ``Context.wait_for_result()``). # self._closed = True return drained diff --git a/tractor/devx/debug/_tty_lock.py b/tractor/devx/debug/_tty_lock.py index 14d551fd..eae8c533 100644 --- a/tractor/devx/debug/_tty_lock.py +++ b/tractor/devx/debug/_tty_lock.py @@ -1102,7 +1102,9 @@ async def request_root_stdio_lock( # sync with child-side root locker task # completion - status: LockStatus = await req_ctx.result() + status: LockStatus = ( + await req_ctx.wait_for_result() + ) assert not status.locked DebugStatus.lock_status = status diff --git a/tractor/msg/_ops.py b/tractor/msg/_ops.py index 86e5e649..6e3931a8 100644 --- a/tractor/msg/_ops.py +++ b/tractor/msg/_ops.py @@ -725,7 +725,8 @@ async def drain_to_final_msg( # a final result or error/ctxc. else: report: str = ( - 'Ignoring "yield" msg during `ctx.result()` drain..\n' + 'Ignoring "yield" msg during ' + '`ctx.wait_for_result()` drain..\n' f'<= {ctx.chan.aid.uid}\n' f' |_{ctx._nsf}()\n\n' f'=> {ctx._task}\n' diff --git a/tractor/msg/types.py b/tractor/msg/types.py index 686df200..171eed3b 100644 --- a/tractor/msg/types.py +++ b/tractor/msg/types.py @@ -82,7 +82,7 @@ class PayloadMsg( (i.e. any values passed by `tractor` application code using any of |_ `._streaming.MsgStream.send/receive()` - |_ `._context.Context.started/result()` + |_ `._context.Context.started/wait_for_result()` |_ `._ipc.Channel.send/recv()` aka our "IPC primitive APIs") diff --git a/tractor/to_asyncio.py b/tractor/to_asyncio.py index f8bd6729..5a2941df 100644 --- a/tractor/to_asyncio.py +++ b/tractor/to_asyncio.py @@ -679,7 +679,7 @@ def _run_asyncio_task( # and then set any final `res` from above as a field # that can optionally be read by the trio-paren-task as # needed (just like in our - # `Context.wait_for_result()/.result` API yah? + # `Context.wait_for_result()` API yah? # # if provide_channels: