Bind named `to_actor.run()` inputs with partials
PR #481 made target inputs positional and reserved keywords for actor placement/runtime controls. PR #484 still forwarded target kwargs, so tests and examples failed local signature binding after the rebase. Deats, - bind named target inputs with `functools.partial()` - keep placement, naming and runtime controls as direct keywords - reject invalid target calls locally before actor startup - require linked one-shots to raise one direct `RemoteActorError` - doc linked context execution and per-child process reaping Prompt-IO: ai/prompt-io/opencode/20260819T184640Z_481ba003_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))drop_ria_nursery
parent
dd91195377
commit
09e78ad087
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
session: 76c5d31c-5a2f-4503-9b16-410ee7f4fab3
|
||||
timestamp: 2026-08-19T18:46:40Z
|
||||
git_ref: 481ba003
|
||||
scope: code
|
||||
substantive: true
|
||||
raw_file: 20260819T184640Z_481ba003_prompt_io.raw.md
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
Rebase PR #484 onto final PR #481, migrate every affected one-shot call
|
||||
to the new positional target API and continue through downstream tests,
|
||||
examples and documentation review.
|
||||
|
||||
## Response summary
|
||||
|
||||
Converted stale target keyword calls to target partials so previously
|
||||
named inputs remain explicit while placement/runtime controls stay
|
||||
direct. Updated error expectations for local signature validation and
|
||||
linked remote error propagation, then corrected docs which still
|
||||
described the removed one-shot implementation. Linked spawning and
|
||||
context lifecycle prose to the corresponding API methods and detailed
|
||||
context guide.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `docs/api/core.rst` - describe linked one-shot context execution.
|
||||
- `docs/guide/rpc.rst` - update placement and target call semantics.
|
||||
- `docs/guide/spawning.rst` - document positional target inputs.
|
||||
- `examples/debugging/multi_nested_subactors_error_up_through_nurseries.py` - migrate nested actor target inputs.
|
||||
- `examples/debugging/root_cancelled_but_child_is_in_tty_lock.py` - preserve named recursive target inputs with partials.
|
||||
- `tests/test_advanced_streaming.py` - migrate streaming target inputs.
|
||||
- `tests/test_cancellation.py` - migrate calls and tighten errors.
|
||||
- `tests/test_infected_asyncio.py` - bind asyncio target options.
|
||||
- `tests/test_rpc.py` - migrate RPC target argument binding.
|
||||
- `tests/test_runtime.py` - preserve named runtime target inputs.
|
||||
- `tests/test_spawning.py` - preserve named spawning target inputs.
|
||||
|
||||
## Human edits
|
||||
|
||||
The human selected the stack order and final PR #481 base, asked the
|
||||
agent to continue after each diagnostic step and required a complete
|
||||
commit plan after independently force-pushing the rebased history.
|
||||
After reviewing the migration, the human required every formerly named
|
||||
target input to remain visibly named through `functools.partial()`
|
||||
rather than becoming positional. These were human-directed agent edits;
|
||||
the human also required plain `start_actor()` and `open_context()`
|
||||
references in the spawning and RPC guides to link to their API methods
|
||||
and the detailed context guide, then clarified that `to_actor.run()`
|
||||
already uses the full context API while `Portal.run()` should share
|
||||
linked lifecycle machinery without necessarily delegating through
|
||||
`Portal.open_context()` or adding a `Started` message. The human made
|
||||
no direct source-line edits.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
timestamp: 2026-08-19T18:46:40Z
|
||||
git_ref: 481ba003
|
||||
diff_cmd: git diff HEAD~1..HEAD
|
||||
---
|
||||
|
||||
Migrate PR #484's downstream one-shot calls to PR #481's final
|
||||
`tractor.to_actor.run()` contract after the stack rebase.
|
||||
|
||||
> `git diff HEAD~1..HEAD -- docs examples tests`
|
||||
|
||||
Pass target arguments positionally and bind target keyword-only inputs
|
||||
with `functools.partial()`. Keep placement and runtime controls as
|
||||
direct `to_actor.run()` keywords. Update the invalid-target-argument
|
||||
test to expect local signature binding before actor startup and require
|
||||
direct `RemoteActorError` propagation from linked one-shots.
|
||||
|
||||
Update API and guide prose to describe positional target inputs,
|
||||
linked `Portal.open_context()` execution and per-child reaping instead
|
||||
of the removed `Portal.run()` and target-`**kwargs` conventions.
|
||||
|
||||
Verification:
|
||||
|
||||
- core and migrated runtime batches: `97 passed`
|
||||
- discovery and related lifecycle batch: `33 passed, 1 skipped`
|
||||
- changed executable examples: `9 passed`
|
||||
- mapped debugger cases: `12 passed, 6 skipped`
|
||||
- Ruff, compilation and `git diff --check`: clean
|
||||
|
|
@ -248,10 +248,12 @@ def test_dynamic_pub_sub(
|
|||
tn.start_soon(
|
||||
partial(
|
||||
tractor.to_actor.run,
|
||||
consumer,
|
||||
partial(
|
||||
consumer,
|
||||
subs=[sub],
|
||||
),
|
||||
an=an,
|
||||
name=f'consumer_{sub}',
|
||||
subs=[sub],
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -259,10 +261,12 @@ def test_dynamic_pub_sub(
|
|||
tn.start_soon(
|
||||
partial(
|
||||
tractor.to_actor.run,
|
||||
consumer,
|
||||
partial(
|
||||
consumer,
|
||||
subs=list(_registry.keys()),
|
||||
),
|
||||
an=an,
|
||||
name='consumer_dynamic',
|
||||
subs=list(_registry.keys()),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ async def do_nuthin():
|
|||
[
|
||||
# expected to be thrown in assert_err
|
||||
({}, AssertionError),
|
||||
# argument mismatch raised in _invoke()
|
||||
# argument mismatch rejected locally before spawn
|
||||
({'unexpected': 10}, TypeError)
|
||||
],
|
||||
ids=['no_args', 'unexpected_args'],
|
||||
|
|
@ -130,48 +130,34 @@ def test_remote_error(
|
|||
|
||||
# `to_actor.run()` blocks on the one-shot's result and
|
||||
# raises the remote error directly here in the caller's
|
||||
# task (a bad-arg `TypeError` likewise relays as a
|
||||
# `RemoteActorError`).
|
||||
# task. Invalid target args fail local signature binding
|
||||
# before any one-shot actor is spawned.
|
||||
try:
|
||||
await tractor.to_actor.run(
|
||||
assert_err,
|
||||
partial(assert_err, **args),
|
||||
an=an,
|
||||
name='errorer',
|
||||
**args
|
||||
)
|
||||
except tractor.RemoteActorError as err:
|
||||
assert err.boxed_type == errtype
|
||||
print("Look Maa that actor failed hard, hehh")
|
||||
raise
|
||||
|
||||
# ensure boxed errors
|
||||
# Invalid args never cross the process boundary.
|
||||
if args:
|
||||
with pytest.raises(tractor.RemoteActorError) as excinfo:
|
||||
with pytest.raises(errtype):
|
||||
trio.run(main)
|
||||
|
||||
else:
|
||||
# The linked one-shot raises the child's boxed error
|
||||
# directly in this caller task.
|
||||
with pytest.raises(
|
||||
tractor.RemoteActorError,
|
||||
) as excinfo:
|
||||
trio.run(main)
|
||||
|
||||
assert excinfo.value.boxed_type == errtype
|
||||
|
||||
else:
|
||||
# the root task will also error on the `Portal.result()`
|
||||
# call so we expect an error from there AND the child.
|
||||
# |_ tho seems like on new `trio` this doesn't always
|
||||
# happen?
|
||||
with pytest.raises((
|
||||
BaseExceptionGroup,
|
||||
tractor.RemoteActorError,
|
||||
)) as excinfo:
|
||||
trio.run(main)
|
||||
|
||||
# ensure boxed errors are `errtype`
|
||||
err: BaseException = excinfo.value
|
||||
if isinstance(err, BaseExceptionGroup):
|
||||
suberrs: list[BaseException] = err.exceptions
|
||||
else:
|
||||
suberrs: list[BaseException] = [err]
|
||||
|
||||
for exc in suberrs:
|
||||
assert exc.boxed_type == errtype
|
||||
|
||||
|
||||
def test_multierror(
|
||||
reg_addr: tuple[str, int],
|
||||
|
|
@ -391,10 +377,9 @@ async def test_some_cancels_all(
|
|||
tn.start_soon(
|
||||
partial(
|
||||
tractor.to_actor.run,
|
||||
func,
|
||||
partial(func, **kwargs),
|
||||
an=an,
|
||||
name=f'actor_{i}',
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -469,12 +454,14 @@ async def spawn_and_error(
|
|||
if depth > 0:
|
||||
|
||||
args = (
|
||||
spawn_and_error,
|
||||
partial(
|
||||
spawn_and_error,
|
||||
breadth=breadth,
|
||||
depth=depth - 1,
|
||||
),
|
||||
)
|
||||
kwargs = {
|
||||
'name': f'spawner_{i}_depth_{depth}',
|
||||
'breadth': breadth,
|
||||
'depth': depth - 1,
|
||||
}
|
||||
else:
|
||||
args = (
|
||||
|
|
@ -705,11 +692,13 @@ async def test_nested_multierrors(
|
|||
tn.start_soon(
|
||||
partial(
|
||||
tractor.to_actor.run,
|
||||
spawn_and_error,
|
||||
partial(
|
||||
spawn_and_error,
|
||||
breadth=subactor_breadth,
|
||||
depth=depth,
|
||||
),
|
||||
an=an,
|
||||
name=f'spawner_{i}',
|
||||
breadth=subactor_breadth,
|
||||
depth=depth,
|
||||
)
|
||||
)
|
||||
except (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ The hipster way to force SC onto the stdlib's "async": 'infection mode'.
|
|||
import asyncio
|
||||
import builtins
|
||||
from contextlib import ExitStack
|
||||
# from functools import partial
|
||||
from functools import partial
|
||||
import itertools
|
||||
import importlib
|
||||
import os
|
||||
|
|
@ -209,10 +209,12 @@ def test_aio_simple_error(
|
|||
debug_mode=debug_mode,
|
||||
) as an:
|
||||
await to_actor.run(
|
||||
asyncio_actor,
|
||||
partial(
|
||||
asyncio_actor,
|
||||
target='sleep_and_err',
|
||||
expect_err='AssertionError',
|
||||
),
|
||||
an=an,
|
||||
target='sleep_and_err',
|
||||
expect_err='AssertionError',
|
||||
infect_asyncio=True,
|
||||
)
|
||||
|
||||
|
|
@ -455,10 +457,14 @@ def test_aio_cancelled_from_aio_causes_trio_cancelled(
|
|||
# relays the remote error here in the caller's task.
|
||||
with trio.fail_after(1 + delay):
|
||||
await to_actor.run(
|
||||
asyncio_actor,
|
||||
partial(
|
||||
asyncio_actor,
|
||||
target='aio_cancel',
|
||||
expect_err=(
|
||||
'tractor.to_asyncio.AsyncioCancelled'
|
||||
),
|
||||
),
|
||||
an=an,
|
||||
target='aio_cancel',
|
||||
expect_err='tractor.to_asyncio.AsyncioCancelled',
|
||||
infect_asyncio=True,
|
||||
)
|
||||
|
||||
|
|
@ -663,10 +669,12 @@ def test_basic_interloop_channel_stream(
|
|||
) as an:
|
||||
# should raise RAE diectly
|
||||
await to_actor.run(
|
||||
stream_from_aio,
|
||||
partial(
|
||||
stream_from_aio,
|
||||
fan_out=fan_out,
|
||||
),
|
||||
an=an,
|
||||
infect_asyncio=True,
|
||||
fan_out=fan_out,
|
||||
)
|
||||
|
||||
trio.run(main)
|
||||
|
|
@ -682,9 +690,11 @@ def test_trio_error_cancels_intertask_chan(
|
|||
) as an:
|
||||
# should trigger remote actor error
|
||||
await to_actor.run(
|
||||
stream_from_aio,
|
||||
partial(
|
||||
stream_from_aio,
|
||||
trio_raise_err=True,
|
||||
),
|
||||
an=an,
|
||||
trio_raise_err=True,
|
||||
infect_asyncio=True,
|
||||
)
|
||||
|
||||
|
|
@ -719,9 +729,11 @@ def test_trio_closes_early_causes_aio_checkpoint_raise(
|
|||
# should raise RAE diectly
|
||||
print('waiting on final infected subactor result..')
|
||||
res: None = await to_actor.run(
|
||||
stream_from_aio,
|
||||
partial(
|
||||
stream_from_aio,
|
||||
trio_exit_early=True,
|
||||
),
|
||||
an=an,
|
||||
trio_exit_early=True,
|
||||
infect_asyncio=True,
|
||||
)
|
||||
assert res is None
|
||||
|
|
@ -770,11 +782,13 @@ def test_aio_exits_early_relays_AsyncioTaskExited(
|
|||
# should raise RAE diectly
|
||||
print('waiting on final infected subactor result..')
|
||||
res: None = await to_actor.run(
|
||||
stream_from_aio,
|
||||
partial(
|
||||
stream_from_aio,
|
||||
trio_exit_early=False,
|
||||
aio_exit_early=True,
|
||||
),
|
||||
an=an,
|
||||
infect_asyncio=True,
|
||||
trio_exit_early=False,
|
||||
aio_exit_early=True,
|
||||
)
|
||||
assert res is None
|
||||
print(f'infected subactor returned result: {res!r}\n')
|
||||
|
|
@ -809,9 +823,11 @@ def test_aio_errors_and_channel_propagates_and_closes(
|
|||
) as an:
|
||||
# should trigger RAE directly, not an eg.
|
||||
await to_actor.run(
|
||||
stream_from_aio,
|
||||
partial(
|
||||
stream_from_aio,
|
||||
aio_raise_err=True,
|
||||
),
|
||||
an=an,
|
||||
aio_raise_err=True,
|
||||
infect_asyncio=True,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ related API and error checks.
|
|||
|
||||
'''
|
||||
import itertools
|
||||
from functools import partial
|
||||
from unittest.mock import (
|
||||
AsyncMock,
|
||||
Mock,
|
||||
|
|
@ -239,19 +240,21 @@ def test_rpc_errors(
|
|||
actor = tractor.current_actor()
|
||||
assert actor.is_registrar
|
||||
await tractor.to_actor.run(
|
||||
sleep_back_actor,
|
||||
partial(
|
||||
sleep_back_actor,
|
||||
actor_name=subactor_requests_to,
|
||||
func_name=funcname,
|
||||
func_defined=bool(func_defined),
|
||||
exposed_mods=exposed_mods,
|
||||
reg_addr=reg_addr,
|
||||
),
|
||||
an=an,
|
||||
actor_name=subactor_requests_to,
|
||||
|
||||
name='subactor',
|
||||
|
||||
# function from the local exposed module space
|
||||
# the subactor will invoke when it RPCs back to this actor
|
||||
func_name=funcname,
|
||||
exposed_mods=exposed_mods,
|
||||
func_defined=True if func_defined else False,
|
||||
# Function from the local exposed module space the
|
||||
# subactor invokes when it RPCs back to this actor.
|
||||
enable_modules=subactor_exposed_mods,
|
||||
reg_addr=reg_addr,
|
||||
)
|
||||
|
||||
def run():
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Verifying internal runtime state and undocumented extras.
|
||||
|
||||
"""
|
||||
from functools import partial
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
@ -84,10 +85,12 @@ async def test_lifetime_stack_wipes_tmpfile(
|
|||
loglevel=loglevel,
|
||||
) as an:
|
||||
await tractor.to_actor.run(
|
||||
crash_and_clean_tmpdir,
|
||||
partial(
|
||||
crash_and_clean_tmpdir,
|
||||
tmp_file_path=path,
|
||||
error=error_in_child,
|
||||
),
|
||||
an=an,
|
||||
tmp_file_path=path,
|
||||
error=error_in_child,
|
||||
)
|
||||
except (
|
||||
tractor.RemoteActorError,
|
||||
|
|
|
|||
|
|
@ -51,18 +51,18 @@ async def spawn(
|
|||
# recursively spawn this same `spawn()` fn as the lone
|
||||
# task of a one-shot child subactor and get its result.
|
||||
result = await tractor.to_actor.run(
|
||||
spawn,
|
||||
partial(
|
||||
spawn,
|
||||
should_be_root=False,
|
||||
data=data_to_pass_down,
|
||||
reg_addr=reg_addr,
|
||||
),
|
||||
an=an,
|
||||
|
||||
# spawning args
|
||||
name='sub-actor',
|
||||
enable_modules=[__name__],
|
||||
|
||||
# passed to a subactor-recursive RPC invoke
|
||||
# of this same `spawn()` fn.
|
||||
should_be_root=False,
|
||||
data=data_to_pass_down,
|
||||
reg_addr=reg_addr,
|
||||
)
|
||||
assert result == 10
|
||||
return result
|
||||
|
|
@ -152,9 +152,11 @@ async def test_most_beautiful_word(
|
|||
debug_mode=debug_mode,
|
||||
) as an:
|
||||
res: Any = await tractor.to_actor.run(
|
||||
cellar_door,
|
||||
partial(
|
||||
cellar_door,
|
||||
return_value=return_value,
|
||||
),
|
||||
an=an,
|
||||
return_value=return_value,
|
||||
name='some_linguist',
|
||||
)
|
||||
assert res == return_value
|
||||
|
|
@ -204,10 +206,12 @@ def test_loglevel_propagated_to_subactor(
|
|||
|
||||
) as an:
|
||||
await tractor.to_actor.run(
|
||||
check_loglevel,
|
||||
partial(
|
||||
check_loglevel,
|
||||
level=level,
|
||||
),
|
||||
an=an,
|
||||
loglevel=level,
|
||||
level=level,
|
||||
)
|
||||
|
||||
trio.run(main)
|
||||
|
|
@ -273,19 +277,23 @@ def test_to_actor_run_can_skip_parent_main_inheritance(
|
|||
|
||||
# Default: child receives parent __main__ bootstrap data
|
||||
await tractor.to_actor.run(
|
||||
check_parent_main_inheritance,
|
||||
partial(
|
||||
check_parent_main_inheritance,
|
||||
expect_inherited=True,
|
||||
),
|
||||
an=an,
|
||||
name='replaying-parent-main',
|
||||
expect_inherited=True,
|
||||
)
|
||||
|
||||
# Opt-out: child gets no parent __main__ data
|
||||
await tractor.to_actor.run(
|
||||
check_parent_main_inheritance,
|
||||
partial(
|
||||
check_parent_main_inheritance,
|
||||
expect_inherited=False,
|
||||
),
|
||||
an=an,
|
||||
name='isolated-parent-main',
|
||||
inherit_parent_main=False,
|
||||
expect_inherited=False,
|
||||
)
|
||||
|
||||
trio.run(main)
|
||||
|
|
|
|||
Loading…
Reference in New Issue