Python API¶
This page is generated from the package's docstrings and signatures by uv run python tests/harness/api.py --write, and a check fails when it differs from the live surface. Edit the docstrings, not this page.
What is promised¶
The public API is the names below: what wasi_python and wasi_python.tools export (each module's __all__), the wasi-python command (see Command line), and the environment variables WASI_PYTHON_ARTIFACTS and WASI_PYTHON_CACHE. Everything else is internal and may change in any release: every other name, and the modules runner, mounts and cli. The project's tests record this surface and fail on any change to it that was not made on purpose.
The promise starts at 0.1.0. Versions are 0.MINOR.PATCH. A patch release changes no public name, signature, field, default or command option. A minor release may add names, keyword-only arguments whose default keeps today's behaviour, dataclass fields placed last with a default (or keyword-only), Errno members, stats() keys, command options and JSON keys. It removes or changes something only after a deprecation: a DeprecationWarning, a docstring that begins "Deprecated since 0.N: use X instead." and a CHANGELOG line, all at once. A thing deprecated in 0.N still works in 0.N and 0.N+1; 0.N+2 is the first release that may remove it. Two exceptions need no deprecation: a tighter default limit, in a minor release, and a security fix, in any release; each is named in the CHANGELOG. Exceptions are promised by type, never by message. Not promised: messages, reprs, help text, wasmtime's trap names and error types, timings, fuel counts, the .cwasm cache, and the guest's package versions. The tool protocol is versioned by PROTOCOL. At 1.0 the same rules hold with "major" in place of "minor".
Reference¶
wasi_python¶
wasi_python.Artifacts¶
@dataclass(frozen=True)
class Artifacts:
snapshot: Path
plain: Path
stdlib_zip: Path
manifest: Path
The artifact set's four files, made by artifacts(), or by you for run(artifacts=…).
Attributes:
snapshot: The snapshot binary, python-snapshot.wasm: the reactor with the stdlib's bootstrap imported (no package).plain: The plain reactor, python.wasm, without the snapshot.stdlib_zip: The stdlib zip, python-stdlib.zip, which every guest sees read-only at /usr/local/lib.manifest: The MANIFEST: every pinned source and recipe hash the build used.
wasi_python.ArtifactsMissing¶
class ArtifactsMissing(RuntimeError):
...
Raised when there is no usable artifact set; the message says how to get one.
wasi_python.Completed¶
@dataclass(frozen=True)
class Completed:
exit_code: int | None
stdout: bytes
stderr: bytes
trapped: str | None
fuel_used: int | None
truncated: tuple[str, ...] = ()
files: FileRecord | None = None
tools: ToolRecord | None = None
How one program ended: what run returns.
Attributes:
exit_code: The program's exit status: its return value, or the code it passed to exit.Nonewhen it trapped.stdout: What the guest wrote to standard output, up to the output limit.stderr: What the guest wrote to standard error, up to the output limit.trapped:Nonewhen the program ended by itself. Otherwise one of four fixed strings, "out of fuel", "deadline", "cancelled" or "out of file calls", or wasmtime's words: a trap's plain name ("stack overflow"), or an error's first line. The four strings are promised; wasmtime's words are not.fuel_used: The guest's instructions spent, when afuelcap was given and no deadline hit; elseNone.truncated: The streams ("stdout", "stderr") whose output passed the limit.files: For a call with mounts, its provider calls, faults and refused writes; elseNone.tools: For a call with tools, every frame counted and the first 1,000 listed; elseNone.
wasi_python.Defaults¶
@dataclass(frozen=True)
class Defaults:
memory_bytes: int | None
deadline_s: float | None
output_bytes: int | None
async_workers: int = <factory>
file_calls: int | None = 10000
tool_calls: int | None = 1000
tool_request_bytes: int | None = 16777216
open_files: int | None = 1024
session_idle_s: float | None = 900.0
session_lifetime_s: float | None = 3600.0
session_shutdown_s: float = 5.0
The limits a call has when it names none: defaults() returns the ones in force, and set_defaults() replaces them.
Attributes:
memory_bytes: The most linear memory a guest may hold, in bytes.deadline_s: The wall-clock seconds a call may run, enforced by epoch interruption, which costs nothing per instruction.output_bytes: The most bytes kept per stream; bytes past it are dropped and the stream is named intruncated.async_workers: How many guestsrun_async's own pool runs at once; by default the CPU count, at most 8.file_calls: The most provider calls a call with mounts makes; then it ends "out of file calls".tool_calls: The most tool-protocol frames a call with tools answers; past it the guest getsToolBudgetExceeded.tool_request_bytes: The most bytes of tool-protocol requests a call with tools reads, counted from each frame's length before it is read; a frame that would pass it is refused unread, and the guest getsToolBudgetExceeded.open_files: The most files and folders a guest holds open at once, in grants, the root and mounts alike; past it an open answersEMFILEand the guest runs on.session_idle_s: The longest aSessionmay rest between turns before it ends "idle".session_lifetime_s: How long after its opening aSession's turns may run; then it ends "lifetime".session_shutdown_s: The seconds aSession's ending has to run Python's shutdown (atexithandlers, then finalization); past them the session is simply stopped. 0 skips the shutdown; it cannot beNone.
wasi_python.Errno¶
class Errno(IntEnum):
EACCES = 2
EBUSY = 10
ECANCELED = 11
EDQUOT = 19
EEXIST = 20
EFBIG = 22
EINVAL = 28
EIO = 29
EISDIR = 31
ENAMETOOLONG = 37
ENOENT = 44
ENOSPC = 51
ENOSYS = 52
ENOTDIR = 54
ENOTEMPTY = 55
ENOTSUP = 58
EPERM = 63
EROFS = 69
ESTALE = 72
ETIMEDOUT = 73
EXDEV = 75
An answer a provider gives instead of a value: WASI preview1's numbers, which the guest sees unchanged, each as the OSError of that errno. The set is closed: no success, nothing that makes the guest's C library retry, and nothing that describes the guest's own arguments.
wasi_python.FileFault¶
@dataclass(frozen=True)
class FileFault:
method: str
path: str
error: str
One provider call that raised an exception that is not an answer, or answered outside its protocol.
Attributes:
method: The provider's method, such as "read".path: The path it was given; a rename's source.error: The exception's type and first line, or "bad answer:".
wasi_python.FileProvider¶
class FileProvider:
def stat(self, path: str) -> Stat | Errno
def list(self, path: str) -> Iterable[tuple[str, str]] | Errno
def read(self, path: str) -> bytes | bytearray | memoryview | Errno
What a mount's provider answers, for type checkers: a read-only mount's three methods.
Methods:
FileProvider.stat: What the path is: aStat, whose kind for / must be "dir", or anErrno.path: An absolute path within the mount.FileProvider.list: A folder's entries, as (name, kind) pairs, or anErrno.path: An absolute path within the mount.FileProvider.read: A file's whole content, or anErrno: EISDIR when the path itself is a folder, and ENOTDIR when a parent is a file.path: An absolute path within the mount.
wasi_python.FileRecord¶
@dataclass(frozen=True)
class FileRecord:
calls: Mapping[str, int]
faults: tuple[FileFault, ...] = ()
refused: tuple[RefusedWrite, ...] = ()
refused_total: int = 0
A call's provider calls, counted. No times, and no exception objects, so a result holds no frame of the call.
Attributes:
calls: Each method's count, all eight methods, zeros included.faults: The first 1,000 faults.refused: The first 1,000 refused writes.refused_total: How many writes were refused in all, so a cut list says so.
wasi_python.Grant¶
@dataclass(frozen=True)
class Grant:
host: str | os.PathLike[str]
guest: str
writable: bool = False # keyword-only
A host folder the guest sees at a guest path, read-only unless writable=True.
Attributes:
host: The host folder, a str or os.PathLike[str].guest: Where the guest sees it: an absolute guest path, not / and not the runtime's own /usr/local/lib.writable: Whether the guest may write there. Keyword-only.
wasi_python.Mount¶
@dataclass(frozen=True)
class Mount:
provider: FileProvider
guest: str
writable: bool = False # keyword-only
A provider whose files the guest sees at a guest path, read-only unless writable=True.
Attributes:
provider: The provider: aFileProvider, or aWritableFileProviderfor a writable mount.guest: Where the guest sees it: an absolute guest path, or / for the guest's whole filesystem (not withroot=).writable: Whether the guest may write there; it needs the provider's write half. Keyword-only.
wasi_python.RefusedWrite¶
@dataclass(frozen=True)
class RefusedWrite:
mount: str
method: str
paths: tuple[str, ...]
errno: Errno
at: str
cause: str
One write-half call the provider did not accept, or a write-back the runner owed and did not make.
Attributes:
mount: The mount's guest path.method: The provider's method, such as "write".paths: The provider path, or (src, dst) for a rename.errno: The provider's answer, or EIO for a fault or the limit.at: When: "open", "call", "close", "sync" or "exit".cause: Why: "refused", "fault" or "limit".
wasi_python.Session¶
class Session:
def __init__(
self,
*,
root: str | os.PathLike[str] | bool | None = False,
grants: Iterable[Grant] = (),
mounts: Iterable[Mount] = (),
import_path: Iterable[str] = (),
env: Mapping[str, str] | None = None,
host: Callable[[bytes], bytes | bytearray | memoryview] | None = None,
snapshot: bool = True,
fuel: int | None = None,
deadline_s: float | None = <default>,
memory_bytes: int | None = <default>,
output_bytes: int | None = <default>,
cache_dir: str | os.PathLike[str] | None | bool = None,
artifacts: Artifacts | None = None,
max_file_calls: int | None = <default>,
tools: Iterable[Tool | Callable[..., Any]] | None = None,
gate: Callable[[ToolCall], Any] | None = None,
max_tool_calls: int | None = <default>,
max_tool_request_bytes: int | None = <default>,
max_open_files: int | None = <default>,
idle_s: float | None = <default>,
lifetime_s: float | None = <default>,
shutdown_s: float = <default>,
) -> None
def __enter__(self) -> Session
def __exit__(self, *exc: object) -> None
@property
def ended(self) -> str | None
@property
def shutdown_outcome(self) -> str | None
def run(
self,
code: str,
*,
fuel: int | None = <default>,
deadline_s: float | None = <default>,
output_bytes: int | None = <default>,
cancel: threading.Event | None = None,
) -> Completed
async def run_async(
self,
code: str,
*,
fuel: int | None = <default>,
deadline_s: float | None = <default>,
output_bytes: int | None = <default>,
executor: concurrent.futures.Executor | None = None,
) -> Completed
def close(self) -> None
One instance of the runtime kept across several calls, each a turn: variables, functions and imports made in one turn are there in the next.
Methods:
Session.__init__: Open a session: check every argument, find the artifact set, make the folders and the store, and instantiate the guest. No guest code runs until the first turn.root: The host folder the guest sees as /, writable, made if missing.FalseorNone(the default) gives no / and nothing writable;Truegives a fresh private folder, removed when the session ends.grants: Host folders the guest sees at their guest paths, each aGrant, read-only unless it sayswritable=True. The stdlib zip is always granted, read-only, at /usr/local/lib.mounts: Providers whose files the guest sees at their guest paths, each aMount, served from your code, read-only unless it sayswritable=True.import_path: Guest paths put on the guest'ssys.pathafter the stdlib zip, in order: each a folder or a zip file inside a read-only grant. The root, a writable grant or a mount may not serve one, no writable grant or mount may lie under one, and no writable grant or root may lie inside the grant serving one.env: Environment variables laid over the guest's own: TMPDIR=/tmp, PYTHONTZPATH= empty (zoneinfo reads the zip's tzdata), and a HOME made fresh for the session where /tmp is writable, else an empty one the guest cannot write.host: The door's host function: called with each request's bytes, it returns the response's. It must answer synchronously: an async one raisesTypeError. Excludestools.snapshot:Trueruns the snapshot binary, with the stdlib's bootstrap imported (no package);Falsethe plain reactor.fuel: Each turn's cap on the guest's executed instructions, unless the turn passes its own; past it the turn ends "out of fuel", which ends the session. A session given a number is metered;None(the default) is unmetered, and its turns report no fuel.deadline_s: Each turn's wall-clock seconds, unless the turn passes its own; past them the turn ends "deadline". Default:defaults().deadline_s, 60.Nonemeans no limit but the lifetime's.memory_bytes: The most linear memory the guest may hold, in bytes, for the whole session. Default:defaults().memory_bytes, 1 GiB.Nonemeans no limit.output_bytes: The most bytes kept per stream in each turn, unless the turn passes its own; the rest are dropped and the stream named intruncated. Default:defaults().output_bytes, 16 MiB.Nonemeans no limit.cache_dir: Where compiled modules are kept as .cwasm files, a folder only you and the runner write: never one a guest is granted, in this or another call.NoneorTrueuses$WASI_PYTHON_CACHE, else the user's cache folder;Falsekeeps them in this process only, never on disk.artifacts: The artifact set to run;Nonefinds it withartifacts()and checks it against its SHA256SUMS first. A set you pass is used as you give it.max_file_calls: The most provider calls each turn with mounts makes; then it ends "out of file calls". Default:defaults().file_calls, 10,000.Nonemeans no limit.tools: Host functions offered to the guest through the tool protocol (from host import tools), each awasi_python.tools.Toolor a plain function, which goes throughtool(). A tool may be async: underrunit is awaited on one loop of the session's own for all its turns, underrun_asyncon the event loop that awaits it. Fixed for the session. Excludeshost.gate: Called with aToolCallbefore each tool runs. It allows by returningNoneorTrue, and refuses by returningFalseor raisingToolDenied. It may be async, as a tool may. Needstools.max_tool_calls: The most tool-protocol frames each turn answers; the guest getsToolBudgetExceededfor the rest. Default:defaults().tool_calls, 1,000.Nonemeans no limit. Needstools.max_tool_request_bytes: The most bytes of tool-protocol requests each turn reads, counted from each frame's length before it is read; a frame that would pass it is refused unread. Default:defaults().tool_request_bytes, 16 MiB.Nonemeans no limit. Needstools.max_open_files: The most files and folders the guest holds open at once, in grants, the root and mounts alike, counted across the session's turns and its ending, since its store keeps them; past it an open answersEMFILEand the guest runs on. 0 refuses every open, the stdlib's included. It protects your process only within the process's own soft descriptor limit. Default:defaults().open_files, 1,024.Nonemeans no limit.idle_s: The longest the session may rest between turns; then it ends "idle". Default:defaults().session_idle_s, 900.Nonemeans no bound.lifetime_s: How long after the opening the session's turns may run: no turn starts at or after it, and each turn's deadline is capped at what is left; then it ends "lifetime". Default:defaults().session_lifetime_s, 3,600.Nonemeans no bound.shutdown_s: The seconds the session's ending has to run Python's shutdown, capped so it ends by the opening plus the lifetime plus this; past it the session is stopped. Default:defaults().session_shutdown_s, 5. 0 skips the shutdown;Noneis refused.Session.__enter__: Enter awithblock; the session is closed when the block is left.Session.__exit__: Close the session as thewithblock is left, however it is left.exc: The exception leaving the block, if any; it is never suppressed.Session.ended:Nonewhile the session can run turns; once it cannot, why: "closed", "idle", "lifetime", "quit" (a turn calledexit()orquit()), "exit" (a turn calledos._exit), "error" (a turn raised in your process), or the stopped turn'strapped. Set once, and never changed.Session.shutdown_outcome:Noneuntil the session's ending has finished; then "finished" (Python's shutdown ran to its end), "stopped" (its budget ran out, or it trapped), or "skipped" (no shutdown ran: the session was stopped mid-turn, no turn ever ran,shutdown_swas 0, or, on Linux, wasmtime's trap handlers were replaced after the first run).close()waits for the ending.Session.run: Run one turn of code in the session and return how it ended.code: The turn's source, a str.fuel: This turn's cap on executed instructions in a metered session;Nonesets no cap. Default: the session's.deadline_s: This turn's wall-clock seconds;Nonemeans no limit but the lifetime's. Default: the session's.output_bytes: The most bytes kept per stream in this turn. Default: the session's.cancel: Athreading.Event. Once it is set and the guest runs, the turn stops within 5 ticks of 10 ms while it computes and ends "cancelled", which ends the session; set before the guest runs, the turn runs nothing and the session goes on.Session.run_async:runfrom async code, on a worker thread, so the event loop is never blocked.code: The turn's source, a str.fuel: This turn's cap on executed instructions in a metered session;Nonesets no cap. Default: the session's.deadline_s: This turn's wall-clock seconds;Nonemeans no limit but the lifetime's. Default: the session's.output_bytes: The most bytes kept per stream in this turn. Default: the session's.executor: Where the turn runs: aconcurrent.futures.Executorthat runs callables on threads of this process.Noneuses the runner's own pool.Session.close: End the session, run its ending, and return once its store is closed.
wasi_python.SessionEnded¶
class SessionEnded(RuntimeError):
...
A session has ended, so it runs no more turns.
wasi_python.Stat¶
@dataclass(frozen=True)
class Stat:
kind: str
size: int = 0
mtime_ns: int = 0
What a provider's stat answers for a path.
Attributes:
kind: "file" or "dir".size: The size in bytes, 0 to 2**63 - 1. A bool is refused.mtime_ns: The modification time in nanoseconds, 0 to 2**64 - 1. A bool is refused.
wasi_python.WritableFileProvider¶
class WritableFileProvider(FileProvider):
def write(
self,
path: str,
data: bytes,
) -> None | Errno
def mkdir(self, path: str) -> None | Errno
def remove(self, path: str) -> None | Errno
def rmdir(self, path: str) -> None | Errno
def rename(
self,
src: str,
dst: str,
) -> None | Errno
What a writable mount's provider answers, for type checkers: the read half and the write half.
Methods:
WritableFileProvider.write: Replace a file's whole content, creating the file if it is missing.path: An absolute path within the mount.data: The file's new content, a copy the provider may keep.WritableFileProvider.mkdir: Make a folder.path: An absolute path within the mount.WritableFileProvider.remove: Remove a file; EISDIR for a folder.path: An absolute path within the mount.WritableFileProvider.rmdir: Remove a folder; ENOTEMPTY for one that holds anything.path: An absolute path within the mount.WritableFileProvider.rename: Move a file or folder within the mount, by POSIX's rule for replacingdst.src: The absolute path, within the mount, of what moves.dst: The absolute path, within the mount, it moves to.
wasi_python.artifact_dir¶
def artifact_dir() -> Path
The folder holding the artifact set.
Raises:
ArtifactsMissing: None of the three is there.
wasi_python.artifacts¶
def artifacts() -> Artifacts
The artifact set's paths, in the folder artifact_dir() names.
Raises:
ArtifactsMissing: There is no artifact set, or its folder has no MANIFEST; the message says how to get one.
wasi_python.defaults¶
def defaults() -> Defaults
The Defaults a call reads now: the limits it has for each one it does not name.
wasi_python.run¶
def run(
argv: Iterable[str],
*,
root: str | os.PathLike[str] | bool | None = False,
grants: Iterable[Grant] = (),
mounts: Iterable[Mount] = (),
import_path: Iterable[str] = (),
env: Mapping[str, str] | None = None,
stdin: bytes | bytearray | memoryview | None = None,
host: Callable[[bytes], bytes | bytearray | memoryview] | None = None,
snapshot: bool = True,
fuel: int | None = None,
deadline_s: float | None = <default>,
memory_bytes: int | None = <default>,
output_bytes: int | None = <default>,
cache_dir: str | os.PathLike[str] | None | bool = None,
artifacts: Artifacts | None = None,
cancel: threading.Event | None = None,
max_file_calls: int | None = <default>,
tools: Iterable[Tool | Callable[..., Any]] | None = None,
gate: Callable[[ToolCall], Any] | None = None,
max_tool_calls: int | None = <default>,
max_tool_request_bytes: int | None = <default>,
max_open_files: int | None = <default>,
) -> Completed
Run one program in a fresh instance of the runtime and return how it ended.
Args:
argv: The guest's arguments after the program's name, as forpython:["-c", code],["script.py"]or["-m", "module"].root: The host folder the guest sees as /, writable, made if missing.FalseorNone(the default) gives no / and nothing writable;Truegives a fresh private folder, removed after the call.grants: Host folders the guest sees at their guest paths, each aGrant, read-only unless it sayswritable=True. The stdlib zip is always granted, read-only, at /usr/local/lib.mounts: Providers whose files the guest sees at their guest paths, each aMount, served from your code, read-only unless it sayswritable=True.import_path: Guest paths put on the guest'ssys.pathafter the stdlib zip, in order: each a folder or a zip file inside a read-only grant. The root, a writable grant or a mount may not serve one, no writable grant or mount may lie under one, and no writable grant or root may lie inside the grant serving one.env: Environment variables laid over the guest's own: TMPDIR=/tmp, PYTHONTZPATH= empty (zoneinfo reads the zip's tzdata), and a HOME made fresh for the call where /tmp is writable, else an empty one the guest cannot write.stdin: The guest's standard input, whole;Noneis empty.host: The door's host function: called with each request's bytes, on the thread that made the call, it returns the response's. It must answer synchronously: an async one raisesTypeError. Excludestools.snapshot:Trueruns the snapshot binary, with the stdlib's bootstrap imported (no package);Falsethe plain reactor.fuel: A cap on the guest's executed instructions; past it the call ends "out of fuel".None(the default) sets no cap.deadline_s: Wall-clock seconds; past them the call ends "deadline". Default:defaults().deadline_s, 60.Nonemeans no limit.memory_bytes: The most linear memory the guest may hold, in bytes. Default:defaults().memory_bytes, 1 GiB.Nonemeans no limit.output_bytes: The most bytes kept per stream; the rest are dropped and the stream named intruncated. Default:defaults().output_bytes, 16 MiB.Nonemeans no limit.cache_dir: Where compiled modules are kept as .cwasm files, a folder only you and the runner write: never one a guest is granted, in this or another call.NoneorTrueuses$WASI_PYTHON_CACHE, else the user's cache folder;Falsekeeps them in this process only, never on disk.artifacts: The artifact set to run;Nonefinds it withartifacts()and checks it against its SHA256SUMS first. A set you pass is used as you give it.cancel: Athreading.Event. Once it is set and the guest runs, the guest stops within 5 ticks of 10 ms while it computes, and the call ends "cancelled".max_file_calls: The most provider calls a call with mounts makes; then it ends "out of file calls". Default:defaults().file_calls, 10,000.Nonemeans no limit.tools: Host functions offered to the guest through the tool protocol (from host import tools), each awasi_python.tools.Toolor a plain function, which goes throughtool(). A tool may be async (anasync def, or a function returning an awaitable): it is awaited on the event loop that awaitsrun_async, or, underrun, on a loop of the call's own; it never starts once its call has been answered, and is cancelled when its call ends. Excludeshost.gate: Called with aToolCallbefore each tool runs. It allows by returningNoneorTrue, and refuses by returningFalseor raisingToolDenied. It may be async, as a tool may. Needstools.max_tool_calls: The most tool-protocol frames a call answers; the guest getsToolBudgetExceededfor the rest. Default:defaults().tool_calls, 1,000.Nonemeans no limit. Needstools.max_tool_request_bytes: The most bytes of tool-protocol requests a call reads, counted from each frame's length before it is read, whatever its answer; a frame that would pass it is refused unread, and the guest getsToolBudgetExceededand runs on. Default:defaults().tool_request_bytes, 16 MiB.Nonemeans no limit. Needstools.max_open_files: The most files and folders the guest holds open at once, in grants, the root and mounts alike; past it an open answersEMFILEand the guest runs on. 0 refuses every open, the stdlib's included. It protects your process only within the process's own soft descriptor limit. Default:defaults().open_files, 1,024.Nonemeans no limit.
Raises:
TypeError: An argument has the wrong type; raised before any work.ValueError: An argument is out of range; raised before any work.ArtifactsMissing: There is no usable artifact set.ImportError: wasmtime-py cannot load; its own error is chained as the cause.OSError: A host filesystem call failed while the root, the private folders, the zip's copy or the cache was prepared (its subclasses, such as FileExistsError, included).RuntimeError: This process was forked after it ran a guest; a folder or the zip's copy changed between the runner's check and its use; on Linux, a handler installed or removed after the first run (faulthandler,signal.signal, a native crash reporter) replaced wasmtime's trap handlers, so a guest trap would end the process; the guest's thread could not start, or has less stack than its 8 MiB wasm stack needs, or its stack cannot be read on this platform; this is CPython 3.14.0 on a C library other than glibc; or wasmtime-py cannot set its async stack size; raised before the guest runs.wasmtime.WasmtimeError: Today, for a folder removed between the runner's check and wasmtime's own open. The type is wasmtime-py's and is not promised.KeyboardInterrupt: Ctrl-C.BaseException: One that is not anException, raised by your own host, provider, tool or gate, propagates.
wasi_python.run_async¶
async def run_async(
argv: Iterable[str],
*,
root: str | os.PathLike[str] | bool | None = False,
grants: Iterable[Grant] = (),
mounts: Iterable[Mount] = (),
import_path: Iterable[str] = (),
env: Mapping[str, str] | None = None,
stdin: bytes | bytearray | memoryview | None = None,
host: Callable[[bytes], bytes | bytearray | memoryview] | None = None,
snapshot: bool = True,
fuel: int | None = None,
deadline_s: float | None = <default>,
memory_bytes: int | None = <default>,
output_bytes: int | None = <default>,
cache_dir: str | os.PathLike[str] | None | bool = None,
artifacts: Artifacts | None = None,
executor: concurrent.futures.Executor | None = None,
max_file_calls: int | None = <default>,
tools: Iterable[Tool | Callable[..., Any]] | None = None,
gate: Callable[[ToolCall], Any] | None = None,
max_tool_calls: int | None = <default>,
max_tool_request_bytes: int | None = <default>,
max_open_files: int | None = <default>,
) -> Completed
run from async code, on a worker thread, so the event loop is never blocked.
run's arguments except cancel, plus:
executor: Where the call runs: aconcurrent.futures.Executorthat runs callables on threads of this process (a process or interpreter pool is refused).None(the default) uses the runner's own pool ofdefaults().async_workersthreads.
Raises:
TypeError: An argument has the wrong type; raised before any work.ValueError: An argument is out of range; raised before any work.ArtifactsMissing: There is no usable artifact set.ImportError: wasmtime-py cannot load; its own error is chained as the cause.OSError: A host filesystem call failed while the root, the private folders, the zip's copy or the cache was prepared (its subclasses, such as FileExistsError, included).RuntimeError: This process was forked after it ran a guest; the interpreter is shutting down; a folder or the zip's copy changed between the runner's check and its use; on Linux, a handler installed or removed after the first run replaced wasmtime's trap handlers; or the guest's thread is refused (asrun).wasmtime.WasmtimeError: Today, for a folder removed between the runner's check and wasmtime's own open. The type is wasmtime-py's and is not promised.asyncio.CancelledError: The awaiting task was cancelled; the guest was stopped and its worker freed first.BaseException: One that is not anException, raised by your own host, provider, tool or gate, propagates.
wasi_python.set_defaults¶
def set_defaults(new: Defaults) -> Defaults
Replace the Defaults that calls read, and return the previous ones.
Args:
new: The limits calls read from now on, already checked byDefaults.
Raises:
TypeError:newis not aDefaults.
wasi_python.stats¶
def stats() -> dict[str, int]
Counts kept by this process, a diagnostic.
wasi_python.tools¶
wasi_python.tools.PROTOCOL¶
PROTOCOL = 1
PROTOCOL is the number of the protocol this host speaks. The rules for its frames, the schema subset and the bounds are the protocol's own, not the host Python's, so an answer is the same on every host; they change only with PROTOCOL (README.md, "The tool protocol").
wasi_python.tools.Tool¶
@dataclass(frozen=True, eq=False)
class Tool:
name: str
description: str
fn: Callable[..., Any]
validates_input: bool = False
timeout_s: float | None = None
read_only: bool = False
destructive: bool = False
idempotent: bool = False
open_world: bool = False
@property
def input_schema(self) -> dict
@property
def output_schema(self) -> dict | None
def descriptor(self) -> dict
def __call__(
self,
*args: Any,
**kwargs: Any,
) -> Any
A host function offered to the guest, made only by tool(). Immutable; compared and hashed by identity. Calling it calls the function.
Attributes:
name: The name the guest calls it by.description: What the guest is told it does.fn: The function it calls.validates_input: Whether schema keywords outside the protocol's subset are let through, unchecked, for a tool that checks its own input.timeout_s: The longest one call may run, in seconds;Nonefor no bound beyond the run's own.read_only: A label shown to the guest as MCP's readOnlyHint; it grants nothing.destructive: A label shown as MCP's destructiveHint; it grants nothing.idempotent: A label shown as MCP's idempotentHint; it grants nothing.open_world: A label shown as MCP's openWorldHint; it grants nothing.
Methods:
Tool.input_schema: A copy of the vetted input schema.Tool.output_schema: A copy of the output schema, or None. It is shown, never validated.Tool.descriptor: The tool in MCP's field names: name, description, inputSchema, and outputSchema and annotations when there are any. A fresh copy each time.Tool.__call__: Call the function with these arguments, on this thread, unchecked.
wasi_python.tools.ToolCall¶
@dataclass(frozen=True)
class ToolCall:
seq: int
tool: Tool
arguments: dict[str, Any]
request_bytes: int
What a gate sees of one tool call.
Attributes:
seq: The frame's number in this call, from 1.tool: The tool the guest called.arguments: A deep copy of the validated and decoded arguments; the tool gets its own.request_bytes: The frame's length in bytes.
wasi_python.tools.ToolDenied¶
class ToolDenied(ToolError):
...
Raised by a gate or a capability to refuse a call; its message crosses (denied).
wasi_python.tools.ToolError¶
class ToolError(Exception):
...
Raised by a tool to fail with a message the guest reads (tool_error), or by a gate to refuse with one (denied). Any other exception crosses only as "(internal error)".
wasi_python.tools.ToolOutcome¶
@dataclass(frozen=True)
class ToolOutcome:
seq: int
op: str | None
tool: str | None
outcome: str
message: str | None
request_bytes: int
response_bytes: int
fault: str | None = None
abandoned: bool = False
One frame, as the record lists it. Nothing in it measures time.
Attributes:
seq: The frame's number in this call, from 1.op: "list", "call", orNonewhen the frame was not read.tool: The tool's name, or the name the guest gave, cut to 64 characters.outcome: "ok", or one of the protocol's kinds of failure (README.md, "The tool protocol").message: What the guest was told on a failure.request_bytes: The frame's length in bytes.response_bytes: The answer's length in bytes.fault: An exception not meant for the guest: its type and first line.abandoned: Whether the tool still ran when the guest was answered.
wasi_python.tools.ToolRecord¶
@dataclass(frozen=True)
class ToolRecord:
frames: int
outcomes: tuple[ToolOutcome, ...]
unlisted: int
abandoned: int = 0
Every frame of one call counted, the first 1,000 listed.
Attributes:
frames: Every frame the guest sent.outcomes: The first 1,000 frames, each aToolOutcome.unlisted: How many frames are counted but not listed.abandoned: Every call answered while its tool ran on, listed or not, so a guest cannot hide a tool still running with your authority behind 1,000 cheap frames.
wasi_python.tools.stubs¶
def stubs(tools: Iterable[Tool | Callable[..., Any]]) -> str
Python stubs for a prompt: each tool as a def with the signature and docstring the guest's function carries, under a first line from host import tools.
Args:
tools: The tools, each aToolor a plain function, which goes throughtool().
Raises:
TypeError: A plain functiontool()refuses.ValueError: A plain functiontool()refuses.
wasi_python.tools.tool¶
def tool(
fn: Callable[..., Any] | None = None,
*,
name: str | None = None,
description: str | None = None,
input_schema: Any = None,
validates_input: bool = False,
timeout_s: float | None = None,
read_only: bool = False,
destructive: bool = False,
idempotent: bool = False,
open_world: bool = False,
) -> Any
@overload
def tool(
fn: Callable[..., Any],
/,
*,
name: str | None = None,
description: str | None = None,
input_schema: Any = None,
validates_input: bool = False,
timeout_s: float | None = None,
read_only: bool = False,
destructive: bool = False,
idempotent: bool = False,
open_world: bool = False,
) -> Tool
@overload
def tool(
fn: None = None,
/,
*,
name: str | None = None,
description: str | None = None,
input_schema: Any = None,
validates_input: bool = False,
timeout_s: float | None = None,
read_only: bool = False,
destructive: bool = False,
idempotent: bool = False,
open_world: bool = False,
) -> Callable[[Callable[..., Any]], Tool]
Describe a function as a tool: @tool, @tool(...) with arguments, or tool(fn, ...).
Args:
fn: The function. Without it,tool(...)returns a decorator.name: The name the guest calls it by; by default the function's own.description: What the guest is told it does; by default the docstring up to its first section heading.input_schema: The input schema instead of the derived one: a dict whose root is an object, or anything withmodel_json_schema().validates_input: Let schema keywords outside the protocol's subset through, unchecked, for a tool that checks its own input.timeout_s: The longest one call may run, in seconds;Nonefor no bound beyond the run's own.read_only: Shown to the guest as MCP's readOnlyHint.destructive: Shown to the guest as MCP's destructiveHint.idempotent: Shown to the guest as MCP's idempotentHint.open_world: Shown to the guest as MCP's openWorldHint.
Raises:
TypeError: An argument has the wrong type, the function is an async generator function, or a type hint has no schema.ValueError: A name or a schema the protocol does not allow.
The command¶
wasi-python run [OPTIONS] [--] ARG... (also python -m wasi_python):
| option | value | action |
|---|---|---|
--env |
KEY[=VALUE] |
append |
--stdin |
PATH |
store |
--deadline-s |
SECONDS |
store |
--memory-bytes |
BYTES |
store |
--output-bytes |
BYTES |
store |
--fuel |
N |
store |
--max-open-files |
N |
store |
--no-snapshot |
store_true | |
--grant |
GUEST=HOST |
append |
--grant-writable |
GUEST=HOST |
append |
--import-path |
GUEST |
append |
--root |
HOST |
once |
--private-root |
store_true | |
--no-root |
store_true |
Exit codes: 0, a result; 1, run raised; 2, a usage error; 130, interrupted.
One line of JSON: on a result, exit_code, fuel_used, stderr, stderr_base64, stdout, stdout_base64, trapped, truncated (a stream that is not UTF-8 comes as its _base64 key); on exit 1, error, kind.