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`))ipc_fault_regressions
parent
bcb58f2af5
commit
299765508e
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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::
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ def test_child_attaches_alot():
|
|||
==
|
||||
_key
|
||||
)
|
||||
await ctx.result()
|
||||
await ctx.wait_for_result()
|
||||
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue