Type the docs-visible `examples/` scripts

Type the runtime objects (`ActorNursery`, `Portal`, `Context`,
`trio.Nursery`) + fn signatures across the 16 highest-visibility,
`literalinclude`-d `examples/` scripts, matching the front-page
`we_are_processes.py` style — so the rendered guides show typed
usage throughout, not just on the landing snippet.

Spans the 3 quickstart-backing scripts + `single_func`,
`remote_error_propagation`, `multiple_streams_one_portal`,
`quick_cluster`, `service_discovery`, `service_daemon_discovery`,
`asynchronous_generators`, `nested_actor_tree`,
`concurrent_actors_primes`, `streaming_broadcast_fanout`,
`rpc_bidir_streaming`, `infected_asyncio_echo_server`,
`typed_payloads`.

Annotation-only (no renames/logic changes); each runs green and the
docs build stays warning-free. Part of the examples-typing bullet
in #472.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
wkt/big_boi_docs_472_follow_ups
Gud Boi 2026-07-02 12:32:49 -04:00
parent fc809b5275
commit 069df08529
18 changed files with 83 additions and 46 deletions

View File

@ -8,29 +8,31 @@ the_line = 'Hi my name is {}'
tractor.log.get_console_log("INFO") tractor.log.get_console_log("INFO")
async def hi(): async def hi() -> str:
return the_line.format(tractor.current_actor().name) return the_line.format(tractor.current_actor().name)
async def say_hello(other_actor): async def say_hello(other_actor: str) -> str:
portal: tractor.Portal
async with tractor.wait_for_actor(other_actor) as portal: async with tractor.wait_for_actor(other_actor) as portal:
return await portal.run(hi) return await portal.run(hi)
async def main(): async def main() -> None:
"""Main tractor entry point, the "master" process (for now """Main tractor entry point, the "master" process (for now
acts as the "director"). acts as the "director").
""" """
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
print("Alright... Action!") print("Alright... Action!")
donny = await n.run_in_actor( donny: tractor.Portal = await n.run_in_actor(
say_hello, say_hello,
name='donny', name='donny',
# arguments are always named # arguments are always named
other_actor='gretchen', other_actor='gretchen',
) )
gretchen = await n.run_in_actor( gretchen: tractor.Portal = await n.run_in_actor(
say_hello, say_hello,
name='gretchen', name='gretchen',
other_actor='donny', other_actor='donny',

View File

@ -2,17 +2,18 @@ import trio
import tractor import tractor
async def cellar_door(): async def cellar_door() -> str:
assert not tractor.is_root_process() assert not tractor.is_root_process()
return "Dang that's beautiful" return "Dang that's beautiful"
async def main(): async def main() -> None:
"""The main ``tractor`` routine. """The main ``tractor`` routine.
""" """
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
portal = await n.run_in_actor( portal: tractor.Portal = await n.run_in_actor(
cellar_door, cellar_door,
name='some_linguist', name='some_linguist',
) )

View File

@ -2,19 +2,20 @@ import trio
import tractor import tractor
async def movie_theatre_question(): async def movie_theatre_question() -> str:
"""A question asked in a dark theatre, in a tangent """A question asked in a dark theatre, in a tangent
(errr, I mean different) process. (errr, I mean different) process.
""" """
return 'have you ever seen a portal?' return 'have you ever seen a portal?'
async def main(): async def main() -> None:
"""The main ``tractor`` routine. """The main ``tractor`` routine.
""" """
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
portal = await n.start_actor( portal: tractor.Portal = await n.start_actor(
'frank', 'frank',
# enable the actor to run funcs from this current module # enable the actor to run funcs from this current module
enable_modules=[__name__], enable_modules=[__name__],

View File

@ -13,11 +13,12 @@ async def stream_forever() -> AsyncIterator[int]:
await trio.sleep(0.01) await trio.sleep(0.01)
async def main(): async def main() -> None:
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
portal = await n.start_actor( portal: tractor.Portal = await n.start_actor(
'donny', 'donny',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -25,7 +26,7 @@ async def main():
# this async for loop streams values from the above # this async for loop streams values from the above
# async generator running in a separate process # async generator running in a separate process
async with portal.open_stream_from(stream_forever) as stream: async with portal.open_stream_from(stream_forever) as stream:
count = 0 count: int = 0
async for letter in stream: async for letter in stream:
print(letter) print(letter)
count += 1 count += 1

View File

@ -9,14 +9,14 @@ from tractor import (
# this is the first 2 actors, streamer_1 and streamer_2 # this is the first 2 actors, streamer_1 and streamer_2
async def stream_data(seed): async def stream_data(seed: int):
for i in range(seed): for i in range(seed):
yield i yield i
await trio.sleep(0.0001) # trigger scheduler await trio.sleep(0.0001) # trigger scheduler
# this is the third actor; the aggregator # this is the third actor; the aggregator
async def aggregate(seed): async def aggregate(seed: int):
''' '''
Ensure that the two streams we receive match but only stream Ensure that the two streams we receive match but only stream
a single set of values to the parent. a single set of values to the parent.
@ -28,7 +28,7 @@ async def aggregate(seed):
for i in range(1, 3): for i in range(1, 3):
# fork/spawn call # fork/spawn call
portal = await an.start_actor( portal: Portal = await an.start_actor(
name=f'streamer_{i}', name=f'streamer_{i}',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -37,7 +37,7 @@ async def aggregate(seed):
send_chan, recv_chan = trio.open_memory_channel(500) send_chan, recv_chan = trio.open_memory_channel(500)
async def push_to_chan(portal, send_chan): async def push_to_chan(portal: Portal, send_chan):
# TODO: https://github.com/goodboy/tractor/issues/207 # TODO: https://github.com/goodboy/tractor/issues/207
async with send_chan: async with send_chan:
@ -49,6 +49,7 @@ async def aggregate(seed):
print(f"FINISHED ITERATING {portal.channel.uid}") print(f"FINISHED ITERATING {portal.channel.uid}")
# spawn 2 trio tasks to collect streams and push to a local queue # spawn 2 trio tasks to collect streams and push to a local queue
n: trio.Nursery
async with trio.open_nursery() as n: async with trio.open_nursery() as n:
for portal in portals: for portal in portals:

View File

@ -28,7 +28,7 @@ async def aio_echo_server(
@tractor.context @tractor.context
async def trio_to_aio_echo_server( async def trio_to_aio_echo_server(
ctx: tractor.Context, ctx: tractor.Context,
): ) -> None:
# this will block until the ``asyncio`` task sends a "first" # this will block until the ``asyncio`` task sends a "first"
# message. # message.
async with tractor.to_asyncio.open_channel_from( async with tractor.to_asyncio.open_channel_from(
@ -48,10 +48,11 @@ async def trio_to_aio_echo_server(
await stream.send(out) await stream.send(out)
async def main(): async def main() -> None:
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
p = await n.start_actor( p: tractor.Portal = await n.start_actor(
'aio_server', 'aio_server',
enable_modules=[__name__], enable_modules=[__name__],
infect_asyncio=True, infect_asyncio=True,

View File

@ -5,7 +5,7 @@ import tractor
log = tractor.log.get_logger('multiportal') log = tractor.log.get_logger('multiportal')
async def stream_data(seed=10): async def stream_data(seed: int = 10):
log.info("Starting stream task") log.info("Starting stream task")
for i in range(seed): for i in range(seed):
@ -13,7 +13,10 @@ async def stream_data(seed=10):
await trio.sleep(0) # trigger scheduler await trio.sleep(0) # trigger scheduler
async def stream_from_portal(p, consumed): async def stream_from_portal(
p: tractor.Portal,
consumed: list,
) -> None:
async with p.open_stream_from(stream_data) as stream: async with p.open_stream_from(stream_data) as stream:
async for item in stream: async for item in stream:
@ -23,14 +26,19 @@ async def stream_from_portal(p, consumed):
consumed.append(item) consumed.append(item)
async def main(): async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery(loglevel='info') as an: async with tractor.open_nursery(loglevel='info') as an:
p = await an.start_actor('stream_boi', enable_modules=[__name__]) p: tractor.Portal = await an.start_actor(
'stream_boi',
enable_modules=[__name__],
)
consumed = [] consumed: list = []
n: trio.Nursery
async with trio.open_nursery() as n: async with trio.open_nursery() as n:
for i in range(2): for i in range(2):
n.start_soon(stream_from_portal, p, consumed) n.start_soon(stream_from_portal, p, consumed)

View File

@ -41,6 +41,7 @@ async def fan_out_squares(
aggregated squares to our parent. aggregated squares to our parent.
''' '''
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
portals: list[tractor.Portal] = [] portals: list[tractor.Portal] = []
for i in (1, 2): for i in (1, 2):
@ -67,6 +68,7 @@ async def fan_out_squares(
) )
# fan out one sub-RPC per input val, concurrently. # fan out one sub-RPC per input val, concurrently.
tn: trio.Nursery
async with trio.open_nursery() as tn: async with trio.open_nursery() as tn:
for i, x in enumerate(vals): for i, x in enumerate(vals):
tn.start_soon( tn.start_soon(
@ -83,8 +85,9 @@ async def fan_out_squares(
async def main() -> None: async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
portal = await an.start_actor( portal: tractor.Portal = await an.start_actor(
'supervisor', 'supervisor',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -31,7 +31,7 @@ PRIMES = [
] ]
async def is_prime(n): async def is_prime(n: int) -> bool:
if n < 2: if n < 2:
return False return False
if n == 2: if n == 2:
@ -47,7 +47,7 @@ async def is_prime(n):
@acm @acm
async def worker_pool(workers=4): async def worker_pool(workers: int = 4):
"""Though it's a trivial special case for ``tractor``, the well """Though it's a trivial special case for ``tractor``, the well
known "worker pool" seems to be the defacto "but, I want this known "worker pool" seems to be the defacto "but, I want this
process pattern!" for most parallelism pilgrims. process pattern!" for most parallelism pilgrims.
@ -55,9 +55,10 @@ async def worker_pool(workers=4):
Yes, the workers stay alive (and ready for work) until you close Yes, the workers stay alive (and ready for work) until you close
the context. the context.
""" """
tn: tractor.ActorNursery
async with tractor.open_nursery() as tn: async with tractor.open_nursery() as tn:
portals = [] portals: list[tractor.Portal] = []
snd_chan, recv_chan = trio.open_memory_channel(len(PRIMES)) snd_chan, recv_chan = trio.open_memory_channel(len(PRIMES))
for i in range(workers): for i in range(workers):
@ -77,9 +78,14 @@ async def worker_pool(workers=4):
) -> list[bool]: ) -> list[bool]:
# define an async (local) task to collect results from workers # define an async (local) task to collect results from workers
async def send_result(func, value, portal): async def send_result(
func: Callable,
value: int,
portal: tractor.Portal,
):
await snd_chan.send((value, await portal.run(func, n=value))) await snd_chan.send((value, await portal.run(func, n=value)))
n: trio.Nursery
async with trio.open_nursery() as n: async with trio.open_nursery() as n:
for value, portal in zip(sequence, itertools.cycle(portals)): for value, portal in zip(sequence, itertools.cycle(portals)):
@ -101,7 +107,7 @@ async def worker_pool(workers=4):
await tn.cancel() await tn.cancel()
async def main(): async def main() -> None:
async with worker_pool() as actor_map: async with worker_pool() as actor_map:

View File

@ -12,7 +12,7 @@ import tractor
import trio import trio
async def burn_cpu(): async def burn_cpu() -> int:
pid = os.getpid() pid = os.getpid()
@ -23,17 +23,18 @@ async def burn_cpu():
return os.getpid() return os.getpid()
async def main(): async def main() -> None:
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
portal = await n.run_in_actor(burn_cpu) portal: tractor.Portal = await n.run_in_actor(burn_cpu)
# burn rubber in the parent too # burn rubber in the parent too
await burn_cpu() await burn_cpu()
# wait on result from target function # wait on result from target function
pid = await portal.wait_for_result() pid: int = await portal.wait_for_result()
# end of nursery block # end of nursery block
print(f"Collected subproc {pid}") print(f"Collected subproc {pid}")

View File

@ -9,12 +9,13 @@ async def sleepy_jane() -> None:
await trio.sleep_forever() await trio.sleep_forever()
async def main(): async def main() -> None:
''' '''
Spawn a flat actor cluster, with one process per detected core. Spawn a flat actor cluster, with one process per detected core.
''' '''
portal_map: dict[str, tractor.Portal] portal_map: dict[str, tractor.Portal]
tn: trio.Nursery
# look at this hip new syntax! # look at this hip new syntax!
async with ( async with (

View File

@ -2,13 +2,14 @@ import trio
import tractor import tractor
async def assert_err(): async def assert_err() -> None:
assert 0 assert 0
async def main(): async def main() -> None:
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
real_actors = [] real_actors: list[tractor.Portal] = []
for i in range(3): for i in range(3):
real_actors.append(await n.start_actor( real_actors.append(await n.start_actor(
f'actor_{i}', f'actor_{i}',

View File

@ -31,9 +31,10 @@ async def simple_rpc(
async def main() -> None: async def main() -> None:
n: tractor.ActorNursery
async with tractor.open_nursery() as n: async with tractor.open_nursery() as n:
portal = await n.start_actor( portal: tractor.Portal = await n.start_actor(
'rpc_server', 'rpc_server',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -49,13 +49,15 @@ async def client_task() -> None:
async def main() -> None: async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
portal = await an.start_actor( portal: tractor.Portal = await an.start_actor(
'quote_svc', 'quote_svc',
enable_modules=[__name__], enable_modules=[__name__],
) )
# run the client in a separate task which discovers # run the client in a separate task which discovers
# the daemon purely by its registered name. # the daemon purely by its registered name.
tn: trio.Nursery
async with trio.open_nursery() as tn: async with trio.open_nursery() as tn:
tn.start_soon(client_task) tn.start_soon(client_task)
# explicit graceful teardown of the daemon. # explicit graceful teardown of the daemon.

View File

@ -4,14 +4,17 @@ import tractor
tractor.log.get_console_log("INFO") tractor.log.get_console_log("INFO")
async def main(service_name): async def main(service_name: str) -> None:
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
await an.start_actor(service_name) await an.start_actor(service_name)
portal: tractor.Portal
async with tractor.get_registry() as portal: async with tractor.get_registry() as portal:
print(f"Registrar is listening on {portal.channel}") print(f"Registrar is listening on {portal.channel}")
sockaddr: tractor.Portal
async with tractor.wait_for_actor(service_name) as sockaddr: async with tractor.wait_for_actor(service_name) as sockaddr:
print(f"my_service is found at {sockaddr}") print(f"my_service is found at {sockaddr}")

View File

@ -54,8 +54,9 @@ async def consume(
async def main() -> None: async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
portal = await an.start_actor( portal: tractor.Portal = await an.start_actor(
'ticker', 'ticker',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -67,6 +68,7 @@ async def main() -> None:
ctx.open_stream() as stream, ctx.open_stream() as stream,
): ):
assert first == 5 assert first == 5
tn: trio.Nursery
async with trio.open_nursery() as tn: async with trio.open_nursery() as tn:
# use `.start()` so each consumer is known # use `.start()` so each consumer is known
# to be subscribed before the ticks flow. # to be subscribed before the ticks flow.

View File

@ -59,8 +59,9 @@ async def point_doubler(
async def main() -> None: async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery() as an: async with tractor.open_nursery() as an:
portal = await an.start_actor( portal: tractor.Portal = await an.start_actor(
'point_doubler', 'point_doubler',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -27,10 +27,11 @@ async def report_addr() -> str:
async def main() -> None: async def main() -> None:
an: tractor.ActorNursery
async with tractor.open_nursery( async with tractor.open_nursery(
enable_transports=['uds'], enable_transports=['uds'],
) as an: ) as an:
portal = await an.start_actor( portal: tractor.Portal = await an.start_actor(
'uds_child', 'uds_child',
enable_modules=[__name__], enable_modules=[__name__],
) )