Pyjo-Reactor-Trio
Low level event reactor with Trio support for Pyjoyment.
Pyjo.Reactor.Trio is a reactor of Pyjoyment on Trio, the async library of
structured concurrency, so that the servers of Pyjoyment, daemon, prefork
and prethread, and its user agent run along with the tasks of a program of
Trio.
Inside a run of Trio, as of trio.run, the reactor watches its handles and
timers with tasks of that run, so a task of Trio awaits a promise of
Pyjoyment, and a coroutine of Pyjoyment awaits anything of Trio. Outside of
one, Pyjo.IOLoop.start() runs a run of Trio of the reactor itself, in the
guest mode of Trio, for as long as it runs.
Installation
From PyPI, with Pyjoyment and Trio, which it depends on:
pip install Pyjo-Reactor-Trio
or, in a project managed with uv:
uv add Pyjo-Reactor-Trio
The development version is installed from the Git repository:
pip install git+https://github.com/pyjoyment/Pyjo-Reactor-Trio
Usage
Select the reactor with PYJO_REACTOR before Pyjo.IOLoop is loaded:
PYJO_REACTOR=Pyjo.Reactor.Trio python myapp.py daemon
PYJO_REACTOR=Pyjo.Reactor.Trio python myapp.py prefork
Examples
examples/hello_world_trio.py runs Pyjo.HelloWorld on the server of
Pyjoyment on Trio until the signal INT or TERM, in a with block, which
closes the listen sockets of the server at its end:
import os
# The reactor of Pyjoyment on Trio, selected before Pyjo.IOLoop is loaded
os.environ['PYJO_REACTOR'] = 'Pyjo.Reactor.Trio'
import Pyjo.HelloWorld # noqa: E402
import Pyjo.Server.Daemon # noqa: E402
# The server of Pyjoyment on Trio, until the signal INT or TERM, which closes its listen sockets at the end of the block
with Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://*:3000']) as daemon:
daemon.run()
$ python examples/hello_world_trio.py
Web application available at http://127.0.0.1:3000
$ curl http://127.0.0.1:3000/
Your Pyjo is working!
examples/trio_nursery.py is a program of Trio, whose run has the server of
Pyjoyment and two tasks of a nursery that request it, with a stream of Trio
and with the user agent of Pyjoyment, whose promise a task of Trio awaits:
import os
# The reactor of Pyjoyment on Trio, selected before Pyjo.IOLoop is loaded
os.environ['PYJO_REACTOR'] = 'Pyjo.Reactor.Trio'
import Pyjo.HelloWorld # noqa: E402
import Pyjo.Server.Daemon # noqa: E402
import Pyjo.UserAgent # noqa: E402
import trio # noqa: E402
# A task with a stream of Trio, which reads the status line of the response
async def stream(port, results):
async with await trio.open_tcp_stream('127.0.0.1', port) as stream:
await stream.send_all(b'GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n')
data = b''
while b'\r\n' not in data:
data += await stream.receive_some()
results['trio'] = data.split(b'\r\n')[0].decode()
# A task with the user agent of Pyjoyment, which awaits the promise of its request
async def user_agent(port, results):
with Pyjo.UserAgent.new() as ua:
tx = await ua.get_p(f'http://127.0.0.1:{port}/')
results['Pyjo.UserAgent'] = f'{tx.res.code} {tx.res.text}'
# The server of Pyjoyment in the run of Trio of the program, on a port that the system picks, with two tasks of a nursery
# that request it, and its listen sockets closed at the end of the block
async def main():
results = {}
with Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://127.0.0.1'], silent=True) as daemon:
port = daemon.start().ports()[0]
async with trio.open_nursery() as nursery:
nursery.start_soon(stream, port, results)
nursery.start_soon(user_agent, port, results)
for name in ('trio', 'Pyjo.UserAgent'):
print(f'{name}: {results[name]}')
trio.run(main)
$ python examples/trio_nursery.py
trio: HTTP/1.1 200 OK
Pyjo.UserAgent: 200 Your Pyjo is working!
examples/loopback_trio.py is a server of Pyjoyment and its user agent on
Trio, a blocking request, which runs the event loop until the response, and
the class of the reactor and the async library that its callbacks run in,
which Build checks with the wheel:
import os
# The reactor of Pyjoyment on Trio, selected before Pyjo.IOLoop is loaded
os.environ['PYJO_REACTOR'] = 'Pyjo.Reactor.Trio'
import Pyjo.HelloWorld # noqa: E402
import Pyjo.IOLoop # noqa: E402
import Pyjo.Server.Daemon # noqa: E402
import Pyjo.UserAgent # noqa: E402
import sniffio # noqa: E402
# The async library that a callback of the reactor runs in, as a library of Trio would detect it
libraries = []
def detect(loop):
try:
libraries.append(sniffio.current_async_library())
except sniffio.AsyncLibraryNotFoundError:
libraries.append('none')
# The server, on a port that the system picks, and the user agent, in the same event loop, which close their sockets at
# the end of the block
with (
Pyjo.Server.Daemon.new(app=Pyjo.HelloWorld.new(), listen=['http://127.0.0.1'], silent=True) as daemon,
Pyjo.UserAgent.new(ioloop=Pyjo.IOLoop.singleton) as ua,
):
# A blocking request, which runs the event loop of the server on Trio until the response
Pyjo.IOLoop.next_tick(detect)
tx = ua.get(f'http://127.0.0.1:{daemon.start().ports()[0]}/')
print(type(Pyjo.IOLoop.singleton.reactor).__name__, *libraries)
print(tx.result.code, tx.result.text)
$ python examples/loopback_trio.py
Pyjo_Reactor_Trio trio
200 Your Pyjo is working!
examples/echo_client_trio.py is the echo client of the
tutorial of Trio,
with its sender and receiver in a nursery, and an echo server of
Pyjo.IOLoop in the same run of Trio. The sender waits for the echo of each
message, and closes its side of the connection after the last one, which the
server closes the connection at:
import os
# The reactor of Pyjoyment on Trio, selected before Pyjo.IOLoop is loaded
os.environ['PYJO_REACTOR'] = 'Pyjo.Reactor.Trio'
import Pyjo.IOLoop # noqa: E402
import Pyjo.Promise # noqa: E402
from itertools import count # noqa: E402
import trio # noqa: E402
MESSAGES = [b'async can sometimes be confusing,', b'but I believe in you!']
CONNECTION_COUNTER = count()
# The echo server of Pyjoyment, which writes back what it reads, until the client closes the connection
def echo_server(closed):
def accept(loop, stream, id):
ident = next(CONNECTION_COUNTER)
print(f'echo_server {ident}: started')
@stream.on
def read(stream, data):
print(f'echo_server {ident}: received data {data!r}')
stream.write(data)
@stream.on
def close(stream):
print(f'echo_server {ident}: connection closed')
closed.resolve()
return Pyjo.IOLoop.server(accept, address='127.0.0.1')
# The echo client of the tutorial of Trio, whose sender waits for the echo of each message before the next one and
# closes its side of the connection after the last one, as the stream of Pyjoyment closes at the end of the data
async def sender(client_stream, echoed):
print('sender: started!')
for data in MESSAGES:
print(f'sender: sending {data!r}')
await client_stream.send_all(data)
await echoed.receive()
await client_stream.send_eof()
async def receiver(client_stream, echoed):
print('receiver: started!')
async for data in client_stream:
print(f'receiver: got data {data!r}')
await echoed.send(data)
print('receiver: connection closed')
# The server of Pyjoyment in the run of Trio of the program, on a port that the system picks, and the client in a
# nursery, until the server closed the connection, and the listen socket of the server removed at the end
async def parent():
closed = Pyjo.Promise.new()
id = echo_server(closed)
port = Pyjo.IOLoop.acceptor(id).port
print(f'parent: connecting to 127.0.0.1:{port}')
client_stream = await trio.open_tcp_stream('127.0.0.1', port)
send_echoed, receive_echoed = trio.open_memory_channel(0)
async with client_stream, send_echoed, receive_echoed:
async with trio.open_nursery() as nursery:
print('parent: spawning sender...')
nursery.start_soon(sender, client_stream, receive_echoed)
print('parent: spawning receiver...')
nursery.start_soon(receiver, client_stream, send_echoed)
await closed
Pyjo.IOLoop.remove(id)
trio.run(parent)
The tasks run concurrently, so the order of their lines differs between the runs:
$ python examples/echo_client_trio.py
parent: connecting to 127.0.0.1:60341
parent: spawning sender...
parent: spawning receiver...
echo_server 0: started
receiver: started!
sender: started!
sender: sending b'async can sometimes be confusing,'
echo_server 0: received data b'async can sometimes be confusing,'
receiver: got data b'async can sometimes be confusing,'
sender: sending b'but I believe in you!'
echo_server 0: received data b'but I believe in you!'
receiver: got data b'but I believe in you!'
echo_server 0: connection closed
receiver: connection closed
examples/echo_server_trio.py is the echo server of the
tutorial of Trio,
with trio.serve_tcp in a nursery, and an echo client of Pyjo.IOLoop, whose
promise a task of Trio awaits:
import os
# The reactor of Pyjoyment on Trio, selected before Pyjo.IOLoop is loaded
os.environ['PYJO_REACTOR'] = 'Pyjo.Reactor.Trio'
import Pyjo.IOLoop # noqa: E402
import Pyjo.Promise # noqa: E402
from functools import partial # noqa: E402
from itertools import count # noqa: E402
import trio # noqa: E402
MESSAGES = [b'async can sometimes be confusing,', b'but I believe in you!']
CONNECTION_COUNTER = count()
# The echo server of the tutorial of Trio, until the client closes the connection
async def echo_server(server_stream):
ident = next(CONNECTION_COUNTER)
print(f'echo_server {ident}: started')
try:
async for data in server_stream:
print(f'echo_server {ident}: received data {data!r}')
await server_stream.send_all(data)
print(f'echo_server {ident}: connection closed')
except Exception as exc:
print(f'echo_server {ident}: crashed: {exc!r}')
# The echo client of Pyjoyment, which writes each message after the echo of the one before and closes the connection
# after the last one, and its promise, which a task of Trio awaits
def echo_client(port):
promise = Pyjo.Promise.new()
messages = list(MESSAGES)
echo = b''
@Pyjo.IOLoop.client(address='127.0.0.1', port=port)
def client(loop, err, stream):
if err is not None:
return promise.reject(err)
def send():
print(f'client: sending {messages[0]!r}')
stream.write(messages[0])
@stream.on
def read(stream, data):
nonlocal echo
echo += data
if len(echo) < len(messages[0]):
return
print(f'client: got data {echo!r}')
echo = b''
messages.pop(0)
if messages:
send()
else:
stream.close_gracefully()
@stream.on
def close(stream):
print('client: connection closed')
promise.resolve()
send()
return promise
# The server of Trio in a nursery, on a port that the system picks, with its connections in a nursery of their own, so
# that its listen socket closes after the client and the echo server ends with the connection
async def main():
async with trio.open_nursery() as handler_nursery:
async with trio.open_nursery() as nursery:
listeners = await nursery.start(
partial(trio.serve_tcp, echo_server, 0, host='127.0.0.1', handler_nursery=handler_nursery)
)
await echo_client(listeners[0].socket.getsockname()[1])
nursery.cancel_scope.cancel()
trio.run(main)
$ python examples/echo_server_trio.py
echo_server 0: started
client: sending b'async can sometimes be confusing,'
echo_server 0: received data b'async can sometimes be confusing,'
client: got data b'async can sometimes be confusing,'
client: sending b'but I believe in you!'
echo_server 0: received data b'but I believe in you!'
client: got data b'but I believe in you!'
client: connection closed
echo_server 0: connection closed
Documentation
The API of the module and the examples are on Read the Docs.
Status
A reactor of Pyjoyment in a distribution of its own, as Pyjo-Reactor-Twisted, Pyjo-Reactor-Uvloop and Pyjo-Reactor-Gevent.
Metadata
Release files for Pyjo-Reactor-Trio 0.1.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 | Provenance | Actions |
|---|---|---|---|---|
| pyjo_reactor_trio-0.1.0.tar.gz | 30.7 kB | With provenance | Details for pyjo_reactor_trio-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyjo_reactor_trio-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for pyjo_reactor_trio-0.1.0-py3-none-any.whl |
Total release size: 49.0 kB
Release files / pyjo_reactor_trio-0.1.0.tar.gz
| Download URL | pyjo_reactor_trio-0.1.0.tar.gz |
|---|---|
| Size | 30.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67934600893b8fed9d2c9276400330baf1419be113235c110d3f819905444bcc
|
|
BLAKE2b-256 checksum How to use checksums |
3e55cdda39d9ea3948d74b46598a553f90b34295a768608527896b47529ba342
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 5, 2026.
Transparency logRelease files / pyjo_reactor_trio-0.1.0-py3-none-any.whl
| Download URL | pyjo_reactor_trio-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a2d2ae8ae4f85526136068aaaed5de492e3d654291b48c80f6728f81953abd93
|
|
BLAKE2b-256 checksum How to use checksums |
544613031b04c430a3670ee49672be21373760967acf075b8fee2aed9b4d15b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 5, 2026.
Transparency logSecurity for Pyjo-Reactor-Trio 0.1.0
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for pyjo_reactor_trio-0.1.0.tar.gz
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 Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.