TonIO
TonIO is a multi-threaded async runtime for free-threaded Python, built in Rust on top of the mio crate, and inspired by tinyio, trio and tokio.
Warning: TonIO is currently a work in progress. The APIs are subject to breaking changes.
Note: TonIO is available on free-threaded Python only. Windows is supported on a best-effort basis.
TonIO supports both using yield and the more canonical async/await notations, with the latter being available as part of the tonio.colored module. Following code snippets show both the usages.
Warning: despite the fact TonIO supports
asyncandawaitnotations, it's not compatible with anyasyncioobject like futures and tasks. The TonIO-Monkey project provides patches for some popularasynciopackages.
In a nutshell
|
import tonio
def wait_and_add(x):
yield tonio.sleep(1)
return x + 1
@tonio.main
def main():
parallel = tonio.spawn(
wait_and_add(3),
wait_and_add(4)
)
six = yield wait_and_add(5)
four, five = yield parallel
return four, five, six
assert main() == (4, 5, 6)
|
import tonio.colored as tonio
async def wait_and_add(x):
await tonio.sleep(1)
return x + 1
@tonio.main
async def main():
parallel = tonio.spawn(
wait_and_add(3),
wait_and_add(4)
)
six = await wait_and_add(5)
four, five = await parallel
return four, five, six
assert main() == (4, 5, 6)
|
Usage
Entrypoint
Every TonIO program consists of an entrypoint, which should be passed to the run method:
|
import tonio
def main():
yield
print("Hello world")
tonio.run(main())
|
import tonio.colored as tonio
async def main():
await tonio.yield_now()
print("Hello world")
tonio.run(main())
|
TonIO also provides a main decorator, thus we can rewrite the previous example as:
|
import tonio
@tonio.main
def main():
yield
print("Hello world")
main()
|
import tonio.colored as tonio
@tonio.main
async def main():
await tonio.yield_now()
print("Hello world")
main()
|
Note: as you can see the
coloredmodule provides the additionalyield_nowcoroutine, a quick way to define a suspension point, given you cannot justyieldas in the non-colored notation.
Note: both
runandmaincan only be called once per program. To run the runtime multiple times in the same program, follow the section below.
Manually managing the runtime
TonIO also provides the runtime function, to manually manage the runtime lifecycle:
import tonio
def _run1():
...
async def _run2():
...
def main():
runtime = tonio.runtime()
runtime.run_until_complete(_run1())
runtime.run_until_complete(_run2())
Runtime options
The run, main and runtime methods accept options, specifically:
| option name | description | default |
|---|---|---|
context |
enable contextvars usage in coroutines |
False |
signals |
list of signals to listen to | |
threads |
Number of runtime threads | # of CPU cores |
blocking_threadpool_size |
Maximum number of blocking threads | 128 |
blocking_threadpool_idle_ttl |
Idle timeout for blocking threads (in seconds) | 30 |
Events
The core object in TonIO is Event. It's basically a wrapper around an atomic boolean flag, initialised with False. Event provides the following methods:
is_set(): return the value of the flagset(): set the flag toTrueclear(): set the flag toFalsewait(timeout=None): returns a coroutine you can yield on that unblocks when the flag is set toTrueor the timeout expires. Timeout is in seconds.
|
import tonio
@tonio.main
def main():
event = tonio.Event()
def setter():
yield tonio.sleep(1)
event.set()
tonio.spawn(setter())
yield event.wait()
|
import tonio.colored as tonio
@tonio.main
async def main():
event = tonio.Event()
async def setter():
await tonio.sleep(1)
event.set()
tonio.spawn(setter())
await event.wait()
|
Waiters
Event.wait returns a Waiter, the object the runtime actually suspends on. Waiters can be combined: w1 & w2 unblocks when both are set, w1 | w2 when either is. When the operands carry timeouts, & keeps the longest and | the shortest.
Waiters can also be built directly from events with Waiter(ev1, ev2) (all) and Waiter.any(ev1, ev2). Both accept a timeout that applies to the waiter as a whole, and expressed in microseconds.
|
import tonio
@tonio.main
def main():
ready, stop = tonio.Event(), tonio.Event()
yield ready.wait() | stop.wait()
if stop.is_set():
return
|
import tonio.colored as tonio
@tonio.main
async def main():
ready, stop = tonio.Event(), tonio.Event()
await (ready.wait() | stop.wait())
if stop.is_set():
return
|
Result
Result is a thread-safe slot to hand values across coroutines, typically paired with an Event. store(value) writes it and fetch() reads it back. Built with a size, it holds that many slots, store(value, index) fills one and fetch() returns them as a list.
Spawning tasks
TonIO provides the spawn method to schedule new coroutines onto the runtime:
|
import tonio
def doubv(v):
yield
return v * 2
@tonio.main
def main():
parallel = tonio.spawn(doubv(2), doubv(3))
v3 = yield doubv(4)
v1, v2 = yield parallel
print([v1, v2, v3])
|
import tonio.colored as tonio
async def doubv(v):
await tonio.yield_now()
return v * 2
@tonio.main
async def main():
parallel = tonio.spawn(doubv(2), doubv(3))
v3 = await doubv(4)
v1, v2 = await parallel
print([v1, v2, v3])
|
Coroutines passed to spawn get scheduled onto the runtime immediately. Using yield or await on the return value of spawn just waits for the coroutines to complete and retrieve the results.
When results are not needed, spawn.without_results waits for completion without collecting them. spawn.without_tracking schedules the coroutines and returns nothing at all, for fire-and-forget work.
Blocking tasks
TonIO provides the spawn_blocking method to schedule blocking operations onto the runtime:
|
import tonio
def read_file(path):
with open(path, "r") as f:
return f.read()
@tonio.main
def main():
file_data = yield tonio.spawn_blocking(
read_file,
"sometext.txt"
)
|
import tonio.colored as tonio
def read_file(path):
with open(path, "r") as f:
return f.read()
@tonio.main
async def main():
file_data = await tonio.spawn_blocking(
read_file,
"sometext.txt"
)
|
Running tasks from synchronous contexts
TonIO provides the block_on method to spawn coroutines from a synchronous context. It works the same way of spawn, except it accepts a single coroutine and it blocks the current thread until the coroutine is completed.
Warning: using
block_onfrom within a coroutine might produce a runtime deadlock.
Map utilities
TonIO provides the map and map_blocking utilities to spawn the same operation with an iterable of parameters:
|
import tonio
accum = []
def task(no):
yield tonio.sleep(0.5)
accum.append(no * 2)
@tonio.main
def main():
yield tonio.map(task, range(4))
|
import tonio.colored as tonio
accum = []
async def task(no):
await tonio.sleep(0.5)
accum.append(no * 2)
@tonio.main
async def main():
await tonio.map(task, range(4))
|
Completion-based iterators
TonIO provides the as_completed utility to iterate over task results based on completion order:
|
import tonio
def _sleep(v):
yield tonio.sleep(v)
return v
@tonio.main
def main():
vals = []
for task in tonio.as_completed(
_sleep(0.5),
_sleep(0.1),
_sleep(0.3),
):
vals.append(yield task)
|
import tonio.colored as tonio
async def _sleep(v):
await tonio.sleep(v)
return v
@tonio.main
async def main():
vals = []
async for val in tonio.as_completed(
_sleep(0.5),
_sleep(0.1),
_sleep(0.3),
):
vals.append(val)
|
Scopes and cancellations
TonIO provides a scope context, that lets you cancel work spawned within it:
|
import tonio
def slow_push(target, sleep):
yield tonio.sleep(sleep)
target.append(True)
@tonio.main
def main():
values = []
with tonio.scope() as scope:
scope.spawn(slow_push(values, 0.1))
scope.spawn(slow_push(values, 2))
yield tonio.sleep(0.2)
scope.cancel()
yield scope()
assert len(values) == 1
|
import tonio.colored as tonio
async def slow_push(target, sleep):
await tonio.sleep(sleep)
target.append(True)
@tonio.main
async def main():
values = []
async with tonio.scope() as scope:
scope.spawn(slow_push(values, 0.1))
scope.spawn(slow_push(values, 2))
await tonio.sleep(0.2)
scope.cancel()
assert len(values) == 1
|
When you yield on the scope, it will wait for all the spawned coroutines to end. If the scope was canceled, then all the pending coroutines will be canceled. By default, an exception in the scope context won't cancel the scope itself. If you want those exceptions to cancel the scope, you can pass cancel_on_exc=True to scope.
Note: as you can see, the colored version of
scopedoesn't require to beawaited, as it will yield when exiting the context.
Select first completing task
TonIO also provides a select utility to cancel remaining work on the first completing task:
|
import tonio
def slow_push(target, sleep):
yield tonio.sleep(sleep)
target.append(True)
@tonio.main
def main():
values = []
yield tonio.select(
slow_push(values, 0.1),
slow_push(values, 2)
)
assert len(values) == 1
|
import tonio.colored as tonio
async def slow_push(target, sleep):
await tonio.sleep(sleep)
target.append(True)
@tonio.main
async def main():
values = []
await tonio.select(
slow_push(values, 0.1),
slow_push(values, 2)
)
assert len(values) == 1
|
select also accepts waiters, so an Event.wait() can race against coroutines.
Time-related functions
tonio.time.time(): a function returning the runtime's clock (in seconds, microsecond resolution)tonio.time.sleep(delay): a coroutine you can yield on to sleep (delay is in seconds)tonio.time.timeout(coro, timeout): a coroutine you can yield on returning a tuple(output, success). If the coroutine succeeds in the given time then the pair(output, True)is returned. Otherwise this will return(None, False).
Note:
time.sleepis also exported to the maintoniomodule.
Note: all of the above functions are also present in
tonio.colored.timemodule.
Scheduling work
TonIO provides the time.interval function to create interval objects you can yield on a scheduled basis:
|
import tonio
from tonio import time
def some_task():
...
def scheduler():
interval = time.interval(1)
while True:
yield interval.tick()
tonio.spawn(some_task())
@tonio.main
def main():
tonio.spawn(scheduler())
# do some other work
|
import tonio.colored as tonio
from tonio.colored import time
async def some_task():
...
async def scheduler():
interval = time.interval(1)
while True:
await interval.tick()
tonio.spawn(some_task())
@tonio.main
async def main():
tonio.spawn(scheduler())
# do some other work
|
The interval method first argument is the interval in seconds resolution, and the method also accepts an optional at argument, to delay the first execution at a specific time (from the runtime's clock perspective):
from tonio import time
# tick every 500ms, with the first tick happening in 5 seconds from now
interval = time.interval(0.5, time.time() + 5)
Synchronization primitives
Synchronization primitives are exposed in the tonio.sync module.
Lock
Implements a classic mutex, or a non-reentrant, single-owner lock for coroutines:
|
import tonio
from tonio import sync
@tonio.main
def main():
# counter can't go above 1
counter = 0
def _count(lock):
nonlocal counter
with (yield lock()):
counter += 1
yield
counter -= 1
lock = sync.Lock()
yield tonio.spawn(*[
_count(lock)
for _ in range(10)
])
|
import tonio.colored as tonio
from tonio.colored import sync
@tonio.main
async def main():
# counter can't go above 1
counter = 0
async def _count(lock):
nonlocal counter
async with lock:
counter += 1
await tonio.yield_now()
counter -= 1
lock = sync.Lock()
await tonio.spawn(*[
_count(lock)
for _ in range(10)
])
|
The Lock object also implements an or_raise method, that will immediately fail when the lock cannot be acquired:
from tonio.exceptions import WouldBlock
try:
with lock.or_raise():
...
except WouldBlock:
...
Semaphore
A semaphore for coroutines:
|
import tonio
from tonio import sync
@tonio.main
def main():
# counter can't go above 2
counter = 0
def _count(semaphore):
nonlocal counter
with (yield semaphore()):
counter += 1
yield
counter -= 1
semaphore = sync.Semaphore(2)
yield tonio.spawn(*[
_count(semaphore)
for _ in range(10)
])
|
import tonio.colored as tonio
from tonio.colored import sync
@tonio.main
async def main():
# counter can't go above 2
counter = 0
async def _count(semaphore):
nonlocal counter
async with semaphore:
counter += 1
await tonio.yield_now()
counter -= 1
semaphore = sync.Semaphore(2)
await tonio.spawn(*[
_count(semaphore)
for _ in range(10)
])
|
As for locks, the Semaphore object also implements an or_raise method, that will immediately fail when the lock cannot be acquired:
from tonio.exceptions import WouldBlock
try:
with semaphore.or_raise():
...
except WouldBlock:
...
The Semaphore object also implements a tokens method, that returns the number of available tokens.
Barrier
A barrier for coroutines:
|
import tonio
from tonio import sync
@tonio.main
def main():
barrier = sync.Barrier(3)
count = 0
def _start_at_3():
nonlocal count
count += 1
i = yield barrier.wait()
assert count == 3
return i
yield tonio.spawn(*[
_start_at_3()
for _ in range(3)
])
|
import tonio.colored as tonio
from tonio.colored import sync
@tonio.main
async def main():
barrier = sync.Barrier(3)
count = 0
async def _start_at_3():
nonlocal count
count += 1
i = await barrier.wait()
assert count == 3
return i
await tonio.spawn(*[
_start_at_3()
for _ in range(3)
])
|
The Barrier object also implements a value method, which returns the current value of the barrier.
Channels
Multi-producer multi-consumer channels for inter-coroutine communication.
The tonio.sync.channel module provides both a channel and an unbounded constructors.
The main difference between bounded and unbounded channels, as the names suggest, is that while the first will suspend sending messages once the specified length is reached, and it will resume accepting messages once the existing buffer is consumed, the latter will always accept new messages. That's also why, the sender part of a bounded channel is async, while in the unbounded is not.
Bounded channel
|
import tonio
from tonio import sync
from tonio.sync import channel
def producer(sender, barrier, offset):
for i in range(20):
message = offset + i
yield sender.send(message)
yield barrier.wait()
def consumer(receiver):
while True:
try:
message = yield receiver.receive()
print(message)
except BrokenPipeError:
break
@tonio.main
def main():
def close(sender, barrier):
yield barrier.wait()
sender.close()
sender, receiver = channel.channel(2)
barrier = sync.Barrier(3)
yield tonio.spawn(*[
producer(sender, barrier, 100),
producer(sender, barrier, 200),
consumer(receiver),
consumer(receiver),
consumer(receiver),
consumer(receiver),
close(sender, barrier),
])
|
import tonio.colored as tonio
from tonio.colored import sync
from tonio.colored.sync import channel
async def producer(sender, barrier, offset):
for i in range(20):
message = offset + i
await sender.send(message)
await barrier.wait()
async def consumer(receiver):
while True:
try:
message = await receiver.receive()
print(message)
except BrokenPipeError:
break
@tonio.main
async def main():
async def close(sender, barrier):
await barrier.wait()
sender.close()
sender, receiver = channel.channel(2)
barrier = sync.Barrier(3)
await tonio.spawn(*[
producer(sender, barrier, 100),
producer(sender, barrier, 200),
consumer(receiver),
consumer(receiver),
consumer(receiver),
consumer(receiver),
close(sender, barrier),
])
|
Unbounded channel
|
import tonio
from tonio import sync
from tonio.sync import channel
def producer(sender, barrier, offset):
for i in range(20):
message = offset + i
sender.send(message)
yield barrier.wait()
def consumer(receiver):
while True:
try:
message = yield receiver.receive()
print(message)
except BrokenPipeError:
break
@tonio.main
def main():
def close(sender, barrier):
yield barrier.wait()
sender.close()
sender, receiver = channel.unbounded()
barrier = sync.Barrier(3)
yield tonio.spawn(*[
producer(sender, barrier, 100),
producer(sender, barrier, 200),
consumer(receiver),
consumer(receiver),
consumer(receiver),
consumer(receiver),
close(sender, barrier),
])
|
import tonio.colored as tonio
from tonio.colored import sync
from tonio.colored.sync import channel
async def producer(sender, barrier, offset):
for i in range(20):
message = offset + i
sender.send(message)
await barrier.wait()
async def consumer(receiver):
while True:
try:
message = await receiver.receive()
print(message)
except BrokenPipeError:
break
@tonio.main
async def main():
async def close(sender, barrier):
await barrier.wait()
sender.close()
sender, receiver = channel.unbounded()
barrier = sync.Barrier(3)
await tonio.spawn(*[
producer(sender, barrier, 100),
producer(sender, barrier, 200),
consumer(receiver),
consumer(receiver),
consumer(receiver),
consumer(receiver),
close(sender, barrier),
])
|
Non-blocking operations
Receivers of both channel kinds offer receive_nowait, a synchronous variant that never suspends: it returns the message or one of the Empty and Closed sentinels. Bounded senders, the only suspending ones, offer send_nowait in the same way: it returns None on success or one of the Full and Closed sentinels. The sentinels are available as attributes on the objects exposing them:
sender, receiver = channel.channel(8)
if sender.send_nowait(message) is sender.Full:
...
if (message := receiver.receive_nowait()) is receiver.Empty:
...
The same objects also expose try_send and try_receive, which raise instead: WouldBlock when the channel is full or empty, BrokenPipeError when it's closed.
Markers
The mark module provides decorator shortcuts for spawn_blocking and Semaphore:
|
import tonio
# run through `spawn_blocking`
@tonio.mark.blocking
def read_file(path):
...
# no more than 2 running at the same time
@tonio.mark.max_concurrency(2)
def fetch(url):
...
# `blocking` + `max_concurrency`
@tonio.mark.cpu_bound(4)
def resize(image):
...
|
import tonio.colored as tonio
# run through `spawn_blocking`
@tonio.mark.blocking
def read_file(path):
...
# no more than 2 running at the same time
@tonio.mark.max_concurrency(2)
async def fetch(url):
...
# `blocking` + `max_concurrency`
@tonio.mark.cpu_bound(4)
def resize(image):
...
|
Network module
Network primitives are exposed under the tonio.net module.
Streams
The high-level network primitives in TonIO are centered around the SocketStream and SocketListener objects.
The SocketListener object implements an accept coroutine which returns a SocketStream object.
The SocketStream object implements the send_all and receive_some coroutines to send and receive data, and a send_eof method to shutdown the sending side.
Both objects implement a close method to shutdown the underlying socket.
You can create and interact with the above objects using some high-level helpers in the net module, specifically:
open_tcp_stream: a coroutine to open aSocketStreamconnected to a TCP endpointopen_unix_socket: a coroutine to open aSocketStreamconnected to a Unix socketopen_tcp_listeners: a coroutine to initialiseSocketListenerobjectsopen_unix_listener: a coroutine to initialise aSocketListeneron a Unix socket pathserve_listeners: a coroutine to spawnSocketListeneraccept loops targeting a handlerserve_tcp: a coroutine that joinsopen_tcp_listenersandserve_listenersin one callserve_unix: a coroutine that joinsopen_unix_listenerandserve_listenersin one call
|
from tonio.net import open_tcp_stream, serve_tcp
def server():
yield serve_tcp(
server_handle,
host='127.0.0.1',
port=8000
)
def server_handle(stream):
# receive some data
data = yield stream.receive_some()
def client():
stream = yield open_tcp_stream(
host='127.0.0.1',
port=8000
)
# send some data
yield stream.send_all(b"message")
|
from tonio.colored.net import open_tcp_stream, serve_tcp
async def server():
await serve_tcp(
server_handle,
host='127.0.0.1',
port=8000
)
async def server_handle(stream):
# receive some data
data = await stream.receive_some()
async def client():
stream = await open_tcp_stream(
host='127.0.0.1',
port=8000
)
# send some data
await stream.send_all(b"message")
|
Unix domain sockets use the same objects, with serve_unix and open_unix_socket:
|
from tonio.net import open_unix_socket, serve_unix
def server():
yield serve_unix(
server_handle,
'/tmp/app.sock',
mode=0o600
)
def client():
stream = yield open_unix_socket(
'/tmp/app.sock'
)
yield stream.send_all(b"message")
|
from tonio.colored.net import open_unix_socket, serve_unix
async def server():
await serve_unix(
server_handle,
'/tmp/app.sock',
mode=0o600
)
async def client():
stream = await open_unix_socket(
'/tmp/app.sock'
)
await stream.send_all(b"message")
|
Readiness and non-blocking operations
SocketStream also exposes its readiness state, for code that wants to decide when to read or write rather than just block on it:
wait_readable(timeout=None)andwait_writable(timeout=None): coroutines that suspend until the socket is ready, returningFalseif the timeout (in seconds) expires firstreceive_some_nowait(max_bytes=None): a synchronous receive, returning theNotReadysentinel (available asstream.NotReady) when no data is availabletry_receive_some(max_bytes=None): same, but raisingWouldBlockinsteadwatch_readable()andwatch_writable(): context managers producing a watcher, whosewaiter()method returns aWaiter(orNonewhen ready) you can combine with others, and whoseready()method tells whether the socket is ready
|
def handler(stream, stop):
while True:
with stream.watch_readable() as watcher:
if (waiter := watcher.waiter()) is not None:
yield waiter | stop.wait()
if stop.is_set():
break
data = stream.receive_some_nowait()
if data is stream.NotReady:
continue
...
|
async def handler(stream, stop):
while True:
with stream.watch_readable() as watcher:
if (waiter := watcher.waiter()) is not None:
await (waiter | stop.wait())
if stop.is_set():
break
data = stream.receive_some_nowait()
if data is stream.NotReady:
continue
...
|
TLS streams
TonIO implement TLS wrappers around the streaming APIs through primitives in the tonio.net.tls module.
TonIO provides the TLSStream and TLSListener object wrappers and the following high-level helpers:
open_tls_over_tcp_stream: a coroutine to open aTLSStreamwrapping a TCPSocketStreamopen_tls_over_tcp_listeners: a coroutine to initialiseTLSListenerobjectsserve_tls_over_tcp: a coroutine that joinsopen_tls_over_tcp_listenersandserve_listenersin one call
Low-level sockets
The tonio.net.socket module provides TonIO's basic low-level networking API.
Generally, the API exposed by this module mirrors the standard library socket module.
TonIO socket objects are overall very similar to the standard library socket objects, with the main difference being that blocking methods become coroutines.
|
import tonio
from tonio.net import socket
def server():
sock = socket.socket()
with sock:
yield sock.bind(('127.0.0.1', 8000))
sock.listen()
while True:
client, _ = yield sock.accept()
tonio.spawn(server_handle(client))
def server_handle(connection):
with connection:
# receive some data
data = yield connection.recv(4096)
def client():
sock = socket.socket()
with sock:
yield sock.connect(('127.0.0.1', 8000))
yield sock.send(b"message")
|
import tonio.colored as tonio
from tonio.colored.net import socket
async def server():
sock = socket.socket()
with sock:
await sock.bind(('127.0.0.1', 8000))
sock.listen()
while True:
client, _ = await sock.accept()
tonio.spawn(server_handle(client))
async def server_handle(connection):
with connection:
# receive some data
data = await connection.recv(4096)
async def client():
sock = socket.socket()
with sock:
await sock.connect(('127.0.0.1', 8000))
await sock.send(b"message")
|
Filesystem module
TonIO's fs module exposes async API for filesystem operations (that are run in the blocking thread-pool).
It provides open, a Path class mirroring pathlib.Path, and wrap_file to
adopt an already-open file object.
|
import tonio
import tonio.fs as fs
def main():
f = yield fs.open('data.txt', 'w')
yield f.write('hello')
yield f.close()
path = fs.Path('data.txt')
if (yield path.exists()):
print((yield path.read_text()))
for entry in (yield fs.Path('.').iterdir()):
print(entry.name)
|
import tonio.colored as tonio
import tonio.colored.fs as fs
async def main():
async with await fs.open('data.txt', 'w') as f:
await f.write('hello')
path = fs.Path('data.txt')
if await path.exists():
print(await path.read_text())
for entry in await fs.Path('.').iterdir():
print(entry.name)
|
Operations that touch the filesystem are asynchronous; everything else stays synchronous.
Note: methods returning several paths (
iterdir,glob,rglob,walk) are resolved in a single hop and give back alist. Sincewalkis fully materialised, mutating itsdirnamesdoes not prune the traversal, unlikepathlib.Path.walk.
Reading a file line by line differs between the two flavours. The await syntax supports async for
and async with, neither of which the yield syntax can express:
|
def read_lines(path):
f = yield fs.open(path, 'r')
try:
while True:
line = yield f.readline()
if not line:
break
print(line)
finally:
yield f.close()
|
async def read_lines(path):
async with await fs.open(path, 'r') as f:
async for line in f:
print(line)
|
Subprocesses
TonIO exposes two coroutines to run child processes: run_process for the common
"run it and collect the outcome" case, and open_process for interacting with a process while it
runs. Both spawn the process on the blocking thread-pool.
run_process returns a subprocess.CompletedProcess and accepts:
stdin: bytes to feed to the child (defaults tob'', meaning "close stdin immediately"), or a file descriptor/subprocessconstantcapture_stdout/capture_stderr: when true, the relevant stream is collected and available on the resultcheck: when true (the default), a non-zero exit code raisessubprocess.CalledProcessError
Any other keyword argument is forwarded to subprocess.Popen.
|
import tonio
def main():
result = yield tonio.run_process(
['echo', 'hello'],
capture_stdout=True
)
print(result.returncode)
print(result.stdout)
|
import tonio.colored as tonio
async def main():
result = await tonio.run_process(
['echo', 'hello'],
capture_stdout=True
)
print(result.returncode)
print(result.stdout)
|
open_process returns a Process object instead, giving access to the running child. Passing
subprocess.PIPE for stdin, stdout or stderr exposes the corresponding pipe as a stream on the
process object, implementing the same send_all and receive_some coroutines of network streams:
|
import subprocess
import tonio
def main():
proc = yield tonio.open_process(
['cat'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
yield proc.stdin.send_all(b'hello')
proc.stdin.close()
data = yield proc.stdout.receive_some()
code = yield proc.wait()
|
import subprocess
import tonio.colored as tonio
async def main():
proc = await tonio.open_process(
['cat'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
await proc.stdin.send_all(b'hello')
proc.stdin.close()
data = await proc.stdout.receive_some()
code = await proc.wait()
|
The Process object exposes:
argsandpid: the command and the process identifierstdin,stdout,stderr: the piped streams, orNonewhen not pipedstdio: a(stdin, stdout)tuple, when both are pipedreturncodeandpoll(): the exit code, orNonewhile the process is still runningwait: a coroutine waiting for the process to exit, returning its exit codesend_signal,terminate,kill: synchronous methods to signal the process
Note: unlike
run_process,open_processdoes not reap the child for you: remember towaiton it — possibly after akill— otherwise the child outlives your task.
Note: processes in TonIO only communicate over unbuffered byte streams: the
universal_newlines,text,encoding,errorsandbufsizeoptions ofsubprocessare not supported.
Note: on Windows, due to the platform's lack of features, the subprocess readiness implementation falls back to the blocking thread-pool. Thus, waiting on a process or read/write operations on pipes can't be interrupted while blocked: cancellations take effect only once the OS call returns.
Driving your own I/O
The io module exposes the primitives TonIO's own sockets, pipes and processes are built on, so you can plug any file descriptor the platform poller understands into the runtime, with the same readiness model.
register puts a descriptor under the poller's watch, for both reading and writing, and returns a ScheduledIO object. The registration is edge-triggered and lasts until you close it. The descriptor itself is neither owned nor switched to non-blocking mode: that's up to you.
Readiness is consumed in user space through a small protocol: arm_r (or arm_w) returns None if the descriptor is known to be ready, otherwise a Waiter to suspend on. Once ready, you perform the actual system call. If it would block anyway, clear_r (or clear_w) drops the stale readiness, so the next arm_r suspends again.
|
import os
import tonio
def read_some(sched, fd, max_bytes=65536):
while True:
if (waiter := sched.arm_r()) is not None:
yield waiter
continue
try:
return os.read(fd, max_bytes)
except BlockingIOError:
sched.clear_r()
@tonio.main
def main():
r, w = os.pipe()
os.set_blocking(r, False)
sched = tonio.io.register(r)
os.write(w, b"hello")
print((yield read_some(sched, r)))
sched.close()
|
import os
import tonio.colored as tonio
async def read_some(sched, fd, max_bytes=65536):
while True:
if (waiter := sched.arm_r()) is not None:
await waiter
continue
try:
return os.read(fd, max_bytes)
except BlockingIOError:
sched.clear_r()
@tonio.main
async def main():
r, w = os.pipe()
os.set_blocking(r, False)
sched = tonio.io.register(r)
os.write(w, b"hello")
print(await read_some(sched, r))
sched.close()
|
arm_r and arm_w accept a timeout in seconds. An expired waiter just resumes, so a further arm_* call tells whether the descriptor got ready in the meantime. Readiness also covers hang-ups and errors: you'll be woken up when the peer goes away, and the following system call will report it.
consume_r and consume_w drain a direction's readiness and tell whether it was set, for descriptors signalling through readiness alone.
Descriptor streams
FdStream wraps a pipe-like descriptor into the same stream interface of the network module, taking ownership of it. It provides the send_all and receive_some coroutines, fileno and close, the latter also invoked when leaving a with block. This is what the pipes of Process objects are.
|
import os
import tonio
from tonio.io import FdStream
@tonio.main
def main():
r, w = os.pipe()
with FdStream(r) as reader, FdStream(w) as writer:
yield writer.send_all(b"hello")
print((yield reader.receive_some()))
|
import os
import tonio.colored as tonio
from tonio.colored.io import FdStream
@tonio.main
async def main():
r, w = os.pipe()
with FdStream(r) as reader, FdStream(w) as writer:
await writer.send_all(b"hello")
print(await reader.receive_some())
|
Concurrent operations on the same direction of a stream raise WouldBlock, and a broken pipe surfaces as ResourceBroken.
Note: on Windows
FdStreamfalls back to the blocking thread-pool, with the same limitations described for subprocesses.
Signals
TonIO provides a context manager to catch signals.
The usage of such context manager requires to first configure the runtime to listen for such signals:
|
import signal
import tonio
from tonio.time import interval
def sig_handle():
with tonio.signal_receiver(
signal.SIGHUP,
signal.SIGUSR1
) as sigs:
for ev in sigs:
sig = yield ev
if sig == signal.SIGHUP:
...
@tonio.main(
signals=[signal.SIGHUP, signal.SIGUSR1]
)
def main():
tonio.spawn(sig_handle())
ticker = interval(1)
while True:
yield ticker.tick()
|
import signal
import tonio.colored as tonio
from tonio.colored.time import interval
async def sig_handle():
with tonio.signal_receiver(
signal.SIGHUP,
signal.SIGUSR1
) as sigs:
async for sig in sigs:
if sig == signal.SIGHUP:
...
@tonio.main(
signals=[signal.SIGHUP, signal.SIGUSR1]
)
async def main():
tonio.spawn(sig_handle())
ticker = interval(1)
while True:
await ticker.tick()
|
Exceptions
The tonio.exceptions module exposes:
CancelledError: raised inside a coroutine when it gets cancelledTimeoutError: raised when a timed operation expiresWouldBlock: raised by theor_raiseandtry_*variants when the operation cannot complete immediatelyResourceBroken: raised by streams when the underlying transport is unusable
Note:
CancelledErrorandTimeoutErrorderive fromBaseException, so they pass throughexcept Exceptionclauses.
Testing
TonIO ships with a pytest plugin, which runs tests marked with the tonio marker on the runtime:
|
import pytest
import tonio
@pytest.mark.tonio
def test_sleep():
yield tonio.sleep(0.1)
|
import pytest
import tonio.colored as tonio
@pytest.mark.tonio
async def test_sleep():
await tonio.sleep(0.1)
|
The marker can also be applied at module level with pytestmark = pytest.mark.tonio, or at class level.
To avoid marking tests entirely, the plugin also provides an auto mode, in which every async test gets run on the TonIO runtime. Auto mode can be enabled with the tonio_mode option in the pytest configuration:
[tool.pytest.ini_options]
tonio_mode = 'auto'
Async fixtures
For asynchronous fixtures where setup and teardown are required, the two syntaxes differ.
await syntax
Async fixtures involved in TonIO tests run on the runtime. Coroutine fixtures simply return their value, while async generator fixtures can yield it and run teardown code after the yield:
import pytest
from tonio.colored.net import open_tcp_stream
@pytest.fixture
async def connection():
stream = await open_tcp_stream(host='127.0.0.1', port=8000)
yield stream
stream.close()
yield syntax
Given non-colored TonIO coroutines are indistinguishable from standard pytest generator fixtures, the plugin never runs generator fixtures on the runtime. This is usually not a limitation: since yield syntax tests already run as coroutines, simple setup can just happen within the test itself:
import pytest
from tonio.net import open_tcp_stream
def _connect():
stream = yield open_tcp_stream(host='127.0.0.1', port=8000)
return stream
@pytest.mark.tonio
def test_conn():
stream = yield _connect()
yield stream.send_all(b'ping')
When an actual teardown is required, the plugin provides the tonio_run fixture, which runs the given coroutine on the runtime:
@pytest.fixture
def connection(tonio_run):
stream = tonio_run(_connect())
def _disconnect():
yield stream.send_all(b'bye')
stream.close()
yield stream
tonio_run(_disconnect())
Runtime configuration in pytest
The TonIO runtime is always initialised once per test session, with context enabled and 2 threads. Runtime options can be customized overriding the session-scoped tonio_runtime_options fixture, for example in conftest.py:
import pytest
@pytest.fixture(scope='session')
def tonio_runtime_options():
return {'threads': 4}
The runtime object itself is available to tests and fixtures via the session-scoped tonio_runtime fixture.
Libraries built on TonIO
In addition to the patches provided by the TonIO-Monkey project, the following libraries target TonIO natively:
- httpunk: a low-level async HTTP library
- punkreq: a high-level async HTTP client
- punkasgi: an ASGI server built on TonIO
License
TonIO is released under the BSD License.
Metadata
Release files for tonio 0.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tonio-0.10.0.tar.gz | 106.4 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| tonio-0.10.0-cp315-cp315t-musllinux_1_1_x86_64.whl | CPython 3.15 | CPython 3.15 free-threading | Linux musl 1.1+ x86-64 | Details |
| tonio-0.10.0-cp315-cp315t-musllinux_1_1_armv7l.whl | CPython 3.15 | CPython 3.15 free-threading | Linux musl 1.1+ ARMv7l | Details |
| tonio-0.10.0-cp315-cp315t-musllinux_1_1_aarch64.whl | CPython 3.15 | CPython 3.15 free-threading | Linux musl 1.1+ ARM64 | Details |
| tonio-0.10.0-cp315-cp315t-manylinux_2_28_armv7l.whl | CPython 3.15 | CPython 3.15 free-threading | Linux glibc 2.28+ ARMv7l | Details |
| tonio-0.10.0-cp315-cp315t-manylinux_2_28_aarch64.whl | CPython 3.15 | CPython 3.15 free-threading | Linux glibc 2.28+ ARM64 | Details |
| tonio-0.10.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.15 | CPython 3.15 free-threading | Linux glibc 2.17+ x86-64 | Details |
| tonio-0.10.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl | CPython 3.15 | CPython 3.15 free-threading | Linux glibc 2.12+ x86-32 | Details |
| tonio-0.10.0-cp315-cp315t-macosx_11_0_arm64.whl | CPython 3.15 | CPython 3.15 free-threading | macOS 11.0+ ARM64 | Details |
| tonio-0.10.0-cp315-cp315t-macosx_10_12_x86_64.whl | CPython 3.15 | CPython 3.15 free-threading | macOS 10.12+ x86-64 | Details |
| tonio-0.10.0-cp314-cp314t-win_amd64.whl | CPython 3.14 | CPython 3.14 free-threading | Windows x86-64 | Details |
| tonio-0.10.0-cp314-cp314t-musllinux_1_1_x86_64.whl | CPython 3.14 | CPython 3.14 free-threading | Linux musl 1.1+ x86-64 | Details |
| tonio-0.10.0-cp314-cp314t-musllinux_1_1_armv7l.whl | CPython 3.14 | CPython 3.14 free-threading | Linux musl 1.1+ ARMv7l | Details |
| tonio-0.10.0-cp314-cp314t-musllinux_1_1_aarch64.whl | CPython 3.14 | CPython 3.14 free-threading | Linux musl 1.1+ ARM64 | Details |
| tonio-0.10.0-cp314-cp314t-manylinux_2_28_armv7l.whl | CPython 3.14 | CPython 3.14 free-threading | Linux glibc 2.28+ ARMv7l | Details |
| tonio-0.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl | CPython 3.14 | CPython 3.14 free-threading | Linux glibc 2.28+ ARM64 | Details |
| tonio-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.14 | CPython 3.14 free-threading | Linux glibc 2.17+ x86-64 | Details |
| tonio-0.10.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl | CPython 3.14 | CPython 3.14 free-threading | Linux glibc 2.12+ x86-32 | Details |
| tonio-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl | CPython 3.14 | CPython 3.14 free-threading | macOS 11.0+ ARM64 | Details |
| tonio-0.10.0-cp314-cp314t-macosx_10_12_x86_64.whl | CPython 3.14 | CPython 3.14 free-threading | macOS 10.12+ x86-64 | Details |
Total release size: 11.2 MB
Release files / tonio-0.10.0.tar.gz
| Download URL | tonio-0.10.0.tar.gz |
|---|---|
| Size | 106.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db5b87ee9f780afe38ba63e3508db4184c46dd973cf6ef6339a9526881e99ebd
|
|
BLAKE2b-256 checksum How to use checksums |
1b2f5974c936693c5bddf1cc496d6feb7344c744c7b045ead75dba1c82333c59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-musllinux_1_1_x86_64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-musllinux_1_1_x86_64.whl |
|---|---|
| Size | 715.2 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux musl 1.1+ x86-64 |
|
SHA-256 checksum How to use checksums |
23daf8d3448621473f321b610bbfe836378826bb51758de91e9d1a7ac8ae2b38
|
|
BLAKE2b-256 checksum How to use checksums |
5f674c9fbe22f25c5273d13ddc66c4371e6cfd51552e50b031d7fd808508c891
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-musllinux_1_1_armv7l.whl
| Download URL | tonio-0.10.0-cp315-cp315t-musllinux_1_1_armv7l.whl |
|---|---|
| Size | 766.3 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux musl 1.1+ ARMv7l |
|
SHA-256 checksum How to use checksums |
30ba27f4929ab7b6b158b26a9916353a9fd0d3721059cc18d66e97e159f863bd
|
|
BLAKE2b-256 checksum How to use checksums |
ce43c2ebd28452856eeda911a4d3af1793c40198d8ff9076c443ed709d63b59b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-musllinux_1_1_aarch64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-musllinux_1_1_aarch64.whl |
|---|---|
| Size | 666.4 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux musl 1.1+ ARM64 |
|
SHA-256 checksum How to use checksums |
a6cf6196aea52ec32f139dd1746d9bffe7d148368cf6f21d38e58644e8c40a12
|
|
BLAKE2b-256 checksum How to use checksums |
4426e0bd8858d013fd87ff066ae9a64c8788a2d190355767b8ed95401f1ffaca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-manylinux_2_28_armv7l.whl
| Download URL | tonio-0.10.0-cp315-cp315t-manylinux_2_28_armv7l.whl |
|---|---|
| Size | 482.2 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux glibc 2.28+ ARMv7l |
|
SHA-256 checksum How to use checksums |
884ed07500901e26704c5aae209599455259f8b00e2fa57df37afb344bc5270b
|
|
BLAKE2b-256 checksum How to use checksums |
d2b2cf4413babb7a999124221a089f7eef3524a16db93ec748e81de53f1b4518
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-manylinux_2_28_aarch64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 478.8 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
575385d7e6185674b8ecdc40234dd1fd2e2375dbbae7a781f4233e2394416933
|
|
BLAKE2b-256 checksum How to use checksums |
d8de7d7e3191b62a28111967362fbc6e081437501dd765256502387f625a08bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 597.3 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
d8fdb133233557a4c2f600236fc413148dda9f628375a29ded65cea0232750ad
|
|
BLAKE2b-256 checksum How to use checksums |
d3c24571c49737a8016b02908c9e7840f77c9d7d293ac44eda3643432d9a1ab4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl
| Download URL | tonio-0.10.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl |
|---|---|
| Size | 529.3 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading Linux glibc 2.12+ x86-32 |
|
SHA-256 checksum How to use checksums |
75d3dec70aa63c3c534e7b8960c00b912b85d5820bca61dd2d722ca1ccacfafd
|
|
BLAKE2b-256 checksum How to use checksums |
2ef9f278aaa02bf3812676a3647776b0583dd761579a2503627df80b2bab8060
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-macosx_11_0_arm64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-macosx_11_0_arm64.whl |
|---|---|
| Size | 533.1 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8324bfa289ce430dcd8718481e5e96c2c8a135d9267f75b2ed58aae90f966edc
|
|
BLAKE2b-256 checksum How to use checksums |
98dd1a7cee7996af0d8d8e58e104315e0d479890cef841f0c9d75460bc38e78a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp315-cp315t-macosx_10_12_x86_64.whl
| Download URL | tonio-0.10.0-cp315-cp315t-macosx_10_12_x86_64.whl |
|---|---|
| Size | 567.8 kB |
| Tags | CPython 3.15 CPython 3.15 free-threading macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
a4ff7453de5cae86e51216790c83fd57308018575ce5f958d2c4e14b214ad997
|
|
BLAKE2b-256 checksum How to use checksums |
78d1ba1d5db92ee52c4d7bc26247fe5420145b3ea96c84858b3e26dee42e81d9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-win_amd64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-win_amd64.whl |
|---|---|
| Size | 393.3 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Windows x86-64 |
|
SHA-256 checksum How to use checksums |
703098576df7cc7631c8893174fdd2692adfbdc681b0cdc1a34416a68a470396
|
|
BLAKE2b-256 checksum How to use checksums |
ec38f7aaffdc28eef52c70ec2affd72ba7658e448467852574f5c999dab2e0fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-musllinux_1_1_x86_64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-musllinux_1_1_x86_64.whl |
|---|---|
| Size | 715.3 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux musl 1.1+ x86-64 |
|
SHA-256 checksum How to use checksums |
577f745f5aef3c101eb3f203d9f96eed4a8ed5f59fde00af38b9546000cb5d0e
|
|
BLAKE2b-256 checksum How to use checksums |
cb70ce32fd700c06da47734e8ae91b6e44009b49fcb2c60d6445e3d5dc71b720
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-musllinux_1_1_armv7l.whl
| Download URL | tonio-0.10.0-cp314-cp314t-musllinux_1_1_armv7l.whl |
|---|---|
| Size | 766.2 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux musl 1.1+ ARMv7l |
|
SHA-256 checksum How to use checksums |
9f6ccea54f24aabf44cffb5fe7db50fe7681691cae0e5973c0e2d5be092414bc
|
|
BLAKE2b-256 checksum How to use checksums |
07e0d3a5da9abe154d880d53c739304fc2ded1ea5e1679528c1faf8bc3f942e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-musllinux_1_1_aarch64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-musllinux_1_1_aarch64.whl |
|---|---|
| Size | 666.2 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux musl 1.1+ ARM64 |
|
SHA-256 checksum How to use checksums |
86da37b8f78c4da77598b74c60ed28814015a6110895ca173359e9bcc0b689f3
|
|
BLAKE2b-256 checksum How to use checksums |
10adc1e366b340c5c18739caec2ef80fb6f6647c86ab51318958f38b0713f629
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-manylinux_2_28_armv7l.whl
| Download URL | tonio-0.10.0-cp314-cp314t-manylinux_2_28_armv7l.whl |
|---|---|
| Size | 482.1 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.28+ ARMv7l |
|
SHA-256 checksum How to use checksums |
4dc59ed60b13223e8aa644c9d416e5b4c12067094bb5cf292f9ee64ee96613cb
|
|
BLAKE2b-256 checksum How to use checksums |
0c25026084e76afad96dd34fca24d34e69d1aeeb86d6ee77ee72ccb9381e33be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 478.8 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
16858ed62d829dda3f716b8aa7d9d596a8037c67a1ba18d2ad0f1ca36c2a9269
|
|
BLAKE2b-256 checksum How to use checksums |
0d55a5f3b6dbba51a0c2519a7214972412683dc6616c142e0b3bdc828e1a42a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 596.7 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
cc648f065fd622a86f6cf8df9a8d9fefcfd5f30e962951a8cfbc19c91e2719ae
|
|
BLAKE2b-256 checksum How to use checksums |
be74b050f67458779cb2b39a7bdc4cc6a811bad0178f6d7738c6cc5a30e61f45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl
| Download URL | tonio-0.10.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl |
|---|---|
| Size | 529.1 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.12+ x86-32 |
|
SHA-256 checksum How to use checksums |
a5463aacf24d223a2427f2fcdb66795afca6fb6d9a8b8c53975feec465580e97
|
|
BLAKE2b-256 checksum How to use checksums |
b8671192bb2d15c4e79456bf011bed73d12daf3255dc9ac064807270dac6f26f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl |
|---|---|
| Size | 534.0 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9dd774ca4123af9851591346b0ea9e8380bba8859f2fea10763e393470f09a97
|
|
BLAKE2b-256 checksum How to use checksums |
739e48b427c913cd1c9b2350604151364cb04c0fe9fbdf269fdb3e7b3fa59420
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / tonio-0.10.0-cp314-cp314t-macosx_10_12_x86_64.whl
| Download URL | tonio-0.10.0-cp314-cp314t-macosx_10_12_x86_64.whl |
|---|---|
| Size | 567.7 kB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
be665ee80fd8526ed44aec936610421ab24aa1763a20e7f1c74fd4022711e460
|
|
BLAKE2b-256 checksum How to use checksums |
99cfced600fd903ff9ed98fdb4fc44218cdd7643f459981395ea2996a0592d1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency log