readie
readie is the Python client for Readie, a service that runs Python functions
in gVisor sandboxes restored from checkpoints. Decorate a function with
@remote, and calls to it run on a Readie worker instead of in your process.
pip install readie
The client requires Python 3.12. See Python version.
For guides, concepts, and the SDK reference, see the documentation at readie.org.
Quick example
from readie import remote
import asyncio
@remote
def add(a, b):
return a + b
print(add(1, 2)) # blocking
async def add_async(a, b):
return await add.aio(a, b)
print(asyncio.run(add_async(1, 2))) # from an event loop
The client serializes the decorated function with cloudpickle, and a Python
interpreter inside a gVisor sandbox runs it. The function must therefore be
picklable, and its imports must exist in the worker's image or be declared with
packages (see Packages).
Python version
The client and the sandbox must run the same Python minor version, because a
pickled function is portable only within one minor version. The sandbox runs
Python 3.12, so readie.Client() raises IncompatiblePythonError unless the
local interpreter is Python 3.12.
Authentication
If the service requires a bearer token, set the READIE_AUTH_TOKEN environment
variable before creating the client. If the variable is unset, the client sends
no token.
Resource budgets
Declare how much memory a function needs on the decorator. Each budget takes a
byte count or a size string such as "512Mi" or "4Gi". An unset budget falls
back to the cluster default.
@remote(gpu=True, memory="2Gi", max_memory="8Gi")
def train(rows): ...
memory is the container's initial limit, and max_memory is the ceiling that
the worker can expand to when the function needs more. gpu_memory and
max_gpu_memory set the same limits for GPU device memory. gpu=True sends the
call to a GPU worker, and a gpu_memory budget implies it.
The client also extracts the function's imports statically and forwards them, so that the router can pick a checkpoint that already imported them. This is automatic and needs no configuration.
Packages
Declare the PyPI requirements that a function needs beyond what the checkpoint's
root filesystem already contains. The executor installs them with uv before
every call:
@remote(packages=["numpy==1.26.0", "requests"])
def fetch(url): ...
Each entry is a requirement string, either a bare name or a full spec such as
"numpy==1.26.0". The client normalizes the entries and removes duplicates by
distribution name when the function is decorated, so "Requests" and
"requests" collapse to one entry.
Installation is unconditional. It runs on every call, even when the package appears to be present, because each call restores a fresh, isolated container and there is nothing to verify the claim against.
Client options
The module-level client is created on first use. To change its options, call
readie.configure before the first remote call:
import readie
readie.configure(timeout=120.0, chunk_size=64 * 1024, stream_logs=True)
| Option | Description |
|---|---|
timeout |
Deadline for a whole call, in seconds. The default is no deadline. |
chunk_size |
Payload bytes per stream message. The default is 1 MiB. |
stream_logs |
If true, print the function's output as it arrives instead of only on failure. The default is true. |
When a remote function raises
If the remote function raises, the client raises readie.RemoteExecutionError
with the traceback from the process that raised:
try:
train()
except readie.RemoteExecutionError as error:
print(error.remote_type) # "ValueError"
print(error.remote_traceback) # the frames inside the sandbox
print(error.worker_id, error.container_id)
The client does not reconstruct the exception object. Unpickling it would
require its class to be importable locally, and for a library that exists only in
the worker image, the resulting ImportError would replace the real error with a
misleading one.
EmptyResultError indicates a different failure: the worker returned nothing at
all, which means the executor died before it could report an outcome. A function
that returns None returns a value and does not raise EmptyResultError.
Sessions
By default, every call is independent and gets a fresh container. To reuse a warm container and the Python state it holds, open a session:
with readie.default_client().session() as s:
load_data.bind(s)() # cold start
train.bind(s)() # resumes the same interpreter
The router serializes calls within one session. A session maps to one container that runs one interpreter behind one socket, so its calls cannot run at the same time. Unrelated sessions run in parallel.
client.session() is a convenience wrapper. Session is a plain class that you
can construct directly, so the context-manager form is optional:
s = readie.Session()
load_data.bind(s)()
train.bind(s)()
# ... later, possibly in another function
s.close()
A call can run inside a session in three equivalent ways: at decoration time, through a bound copy, or per call.
@remote(session=s) # every call through this name uses s
def f(): ...
g = train.bind(s) # a copy of an existing @remote bound to s; the original
# is module-level and shared, so it is left unchanged
readie.default_client().call(train.func, session=s) # one call, through the client directly
Session also accepts an explicit id, for example Session(session_id="sess-...").
This lets you reconstruct a session in another process, or after the current
process restarts, as long as the session's container still exists.
Closing and expiry
Closing a session is a local operation. It stops this client from using the id, and no message reaches the router. The router never removes a session on a timer or a cap of its own. It retires a session id only after the container behind the session is gone, typically because the worker removed the container after its idle timeout.
A session can therefore outlive close(). The following two cases differ:
- A
session_idthat the router has never seen, including the id of a newSession(), is not an error. The call cold-starts, exactly like a call with no session. - A
session_idwhose container the router has already reclaimed raisesreadie.SessionExpiredError. The router raises the error instead of cold-starting under an id that appears to carry warm state. Open a newSessioninstead of retrying the expired one.
Metadata
Release files for readie 0.3.1
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 | |
|---|---|---|---|---|
| readie-0.3.1.tar.gz | 64.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| readie-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.4 kB
Release files / readie-0.3.1.tar.gz
| Download URL | readie-0.3.1.tar.gz |
|---|---|
| Size | 64.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd22b794a578a53d0b899cef0b506edad3f44c5186f46d66dd189dcc42e5198d
|
|
BLAKE2b-256 checksum How to use checksums |
477724de11a82e250e41e1caf44620194281b1f9475d5a898e5646e1f5ac8670
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
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 Oct 3, 2026.
Transparency logRelease files / readie-0.3.1-py3-none-any.whl
| Download URL | readie-0.3.1-py3-none-any.whl |
|---|---|
| Size | 40.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4a370ff21c756212b804279e21fd47a0c629c30dca689031f4112e0df39effc
|
|
BLAKE2b-256 checksum How to use checksums |
0f34ea681b2f2e902ac9c9ab2bb132c67c30094c5f2797ca3c4766cb2e958af4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
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 Oct 3, 2026.
Transparency logSecurity for readie 0.3.1
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
All 2 files in this release have been uploaded with provenance metadata.
Attestation for readie-0.3.1.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 Oct 3, 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.