Python API
This document provides a comprehensive guide to the pie_client Python library, an asynchronous client designed for interacting with the Pie WebSocket server. It allows you to compile, upload, and manage WebAssembly programs on a remote server.
Before using the client, ensure you have the necessary libraries installed:
cd client/python
pip install -e .
Quick Start
Here’s a complete example demonstrating the typical workflow: connecting to the server, compiling and uploading a Rust program, launching it as an instance, and communicating with it.
- main.py
import asyncio
import blake3
from pie_client import PieClient, compile_program, Event
# A simple Rust program that echoes messages and sends back a blob.
RUST_CODE = r"""
use inferlet::{Args, Result};
#[inferlet::main]
async fn main(mut args: Args) -> Result<()> {
println!("Hello, world!");
Ok(())
}
"""
async def main():
# Connect to the Pie server using an async context manager
async with PieClient("ws://127.0.0.1:8080") as client:
# Authenticate with the server
success, msg = await client.authenticate("your-secret-token")
if not success:
raise Exception(f"Authentication failed: {msg}")
# 1. Compile the Rust code to WASM
# Dependencies can be specified, e.g., ["serde = \"1.0\""]
wasm_bytes = await compile_program(RUST_CODE, dependencies=[])
program_hash = blake3.blake3(wasm_bytes).hexdigest()
# 2. Upload the program if it doesn't already exist on the server
if not await client.program_exists(program_hash):
print(f"Program {program_hash} not found. Uploading...")
await client.upload_program(wasm_bytes)
else:
print(f"Program {program_hash} already exists on server.")
# 3. Launch the program as a new instance
print("Launching instance...")
instance = await client.launch_instance(program_hash)
print(f"Instance launched with ID: {instance.instance_id}")
# 4. Interact with the instance
await instance.send("Hello from the client!")
# 5. Listen for events from the instance
while True:
event, message = await instance.recv()
print(f"Received Event: {event.name}, Message: {message}")
if event == Event.Blob:
# The message for a Blob event is the binary data itself
print(f"Decoded blob content: '{message.decode()}'")
elif event in [Event.Completed, Event.Aborted, Event.Exception]:
print("Instance has terminated. Exiting event loop.")
break
if __name__ == "__main__":
asyncio.run(main())
PieClient Class
The PieClient is the primary interface for communicating with the Pie server. It handles connection, authentication, and program management. It's designed to be used as an asynchronous context manager.
Initialization
class PieClient:
def __init__(self, server_uri: str):
"""
Initializes the client.
Args:
server_uri (str): The WebSocket URI of the Pie server (e.g., "ws://127.0.0.1:8080").
"""
Usage:
client = PieClient("ws://localhost:8080")
Connection Management
The client is best used with an async with block, which automatically handles connecting and disconnecting.
async def main():
async with PieClient("ws://127.0.0.1:8080") as client:
# Client is connected and ready to use
...
# Client is automatically disconnected here
Methods
authenticate
Authenticates the client session. This should be the first call after connecting.
async def authenticate(self, token: str) -> tuple[bool, str]:
- Args:
token(str): The authentication token.
- Returns: A tuple
(successful, result)wheresuccessfulis a boolean andresultis a message from the server.
upload_program
Uploads a compiled WASM program to the server. The data is sent in chunks.
async def upload_program(self, program_bytes: bytes):
- Args:
program_bytes(bytes): The raw binary content of the WASM file.
- Raises:
Exceptionif the upload fails.
program_exists
Checks if a program with a given hash is already available on the server, avoiding unnecessary uploads.
async def program_exists(self, program_hash: str) -> bool:
- Args:
program_hash(str): The blake3 hash of the program bytes.
- Returns:
Trueif the program exists,Falseotherwise.
launch_instance
Requests the server to start a new instance of an uploaded program.
async def launch_instance(self, program_hash: str, arguments: list[str] = None) -> Instance:
- Args:
program_hash(str): The hash of the program to launch.arguments(list[str], optional): A list of command-line arguments for the instance.
- Returns: An
Instanceobject to interact with the running program. - Raises:
Exceptionif the launch fails.
launch_server_instance
Launches a program that acts as a server, listening on a specified port on the host machine.
async def launch_server_instance(self, program_hash: str, port: int, arguments: list[str] = None):
- Args:
program_hash(str): The hash of the program to launch.port(int): The host port the instance should listen on.arguments(list[str], optional): A list of command-line arguments.
- Raises:
Exceptionif the launch fails.
Instance Class
An Instance object represents a single running program on the server. It is returned by PieClient.launch_instance() and provides methods for direct interaction.
Methods
send
Sends a string message to the instance. This is typically received by the instance via its standard input or a specific function call.
async def send(self, message: str):
- Args:
message(str): The string message to send.
upload_blob
Uploads a blob of binary data to the instance. This is useful for providing large data files or inputs to a running program.
async def upload_blob(self, blob_bytes: bytes):
- Args:
blob_bytes(bytes): The binary data to upload.
recv
Waits for and receives the next event from the instance. This is a blocking call.
async def recv(self) -> tuple[Event, str | bytes]:
- Returns: A tuple
(event, message).event: AnEventenum member indicating the event type.message: Astrfor most events, orbytesif the event isEvent.Blob.
terminate
Sends a request to the server to terminate the instance. This is a fire-and-forget operation.
async def terminate(self):
Event Enum
The Event enumeration defines the types of events you can receive from an instance via instance.recv().
class Event(Enum):
Message = 0 # Standard output (stdout) from the instance.
Completed = 1 # The instance finished successfully (exit code 0).
Aborted = 2 # The instance terminated with a non-zero exit code.
Exception = 3 # An internal WASM trap/exception occurred.
ServerError = 4 # The server encountered an error managing the instance.
OutOfResources = 5 # The instance was terminated for exceeding resource limits.
Blob = 6 # The instance sent a binary data blob.
compile_program Utility
A helper function that compiles Rust source code into a WASM binary compatible with the Pie server. It runs the Rust compiler (cargo) in a separate process.
This function executes a local cargo command and writes temporary files to your disk. It requires a working Rust environment.
async def compile_program(source: str | Path, dependencies: list[str]) -> bytes:
- Args:
source(str | Path): The Rust source code as a string, or apathlib.Pathto a.rsfile.dependencies(list[str]): A list of Cargo dependencies, e.g.,['serde = "1.0"'].
- Returns: The compiled WASM binary as
bytes. - Raises:
RuntimeErrorif compilation fails orcargois not found.
Example:
# Compile from a string
rust_code = 'fn main() { println!("Hello"); }'
wasm_bytes = await compile_program(rust_code, dependencies=[])
# Compile from a file
from pathlib import Path
wasm_bytes_from_file = await compile_program(Path("src/my_program.rs"), dependencies=[])