Building Wasm Components with Python
This cookbook guide shows you how to build WebAssembly components using Python that work with Wassette.
Quick Start
Prerequisites
- Python 3.10 or higher
- uv - Fast Python package manager
Install Tools
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install componentize-py
uv pip install componentize-py
Step-by-Step Guide
1. Create Your Project
mkdir my-python-tool
cd my-python-tool
mkdir wit wit_world
2. Define Your Interface (WIT)
Create wit/world.wit:
package local:my-tool;
/// Example calculator tool
world calculator {
/// Add two numbers and return the result
export add: func(a: f64, b: f64) -> result<f64, string>;
/// Perform a calculation from a string expression
export calculate: func(expression: string) -> result<string, string>;
}
3. Generate Python Bindings
uv run componentize-py -d wit -w calculator bindings .
This creates Python bindings in the wit_world/ directory.
4. Implement Your Component
Create main.py:
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import wit_world
from wit_world.types import Err
import json
def handle_error(e: Exception) -> Err[str]:
"""Helper function to convert Python exceptions to WIT errors"""
message = str(e)
if message == "":
return Err(f"{type(e).__name__}")
else:
return Err(f"{type(e).__name__}: {message}")
class Calculator(wit_world.Calculator):
def add(self, a: float, b: float) -> float:
"""Add two numbers together"""
try:
result = a + b
return result
except Exception as e:
raise handle_error(e)
def calculate(self, expression: str) -> str:
"""Evaluate a mathematical expression and return JSON result"""
try:
# WARNING: eval() is unsafe for untrusted input
# In production, use ast.literal_eval() or a proper expression parser
result = eval(expression)
return json.dumps({"result": result})
except Exception as e:
raise handle_error(e)
5. Create Build Configuration
Create Justfile:
install-uv:
if ! command -v uv &> /dev/null; then curl -LsSf https://astral.sh/uv/install.sh | sh; fi
install: install-uv
uv pip install componentize-py
bindings:
uv run componentize-py -d wit -w calculator bindings .
build:
uv run componentize-py -d wit -w calculator componentize -s main -o calculator.wasm
all: bindings build
6. Build Your Component
# Install build tools
just install
# Generate bindings and build Wasm component
just all
# Or run commands manually:
# uv run componentize-py -d wit -w calculator bindings .
# uv run componentize-py -d wit -w calculator componentize -s main -o calculator.wasm
7. Inject WIT Documentation
To make your component’s documentation available to AI agents, inject the WIT documentation into the compiled WASM binary:
# Install wit-docs-inject (if not already installed)
cargo install --git https://github.com/Mossaka/wit-docs-inject
# Inject documentation into your component
wit-docs-inject --component calculator.wasm \
--wit-dir wit/ \
--inplace
This embeds the documentation from your WIT files as a package-docs custom section in the WASM binary. When Wassette loads your component, it extracts this documentation and uses it to describe your tools to AI agents.
For more information, see the Documenting WIT Interfaces guide.
8. Test Your Component
wassette serve --sse --plugin-dir .
Complete Examples
Simple Calculator
wit/world.wit:
package local:calculator;
world calculator {
export add: func(a: f64, b: f64) -> f64;
export subtract: func(a: f64, b: f64) -> f64;
export multiply: func(a: f64, b: f64) -> f64;
export divide: func(a: f64, b: f64) -> result<f64, string>;
}
main.py:
import wit_world
from wit_world.types import Err, Ok
class Calculator(wit_world.Calculator):
def add(self, a: float, b: float) -> float:
return a + b
def subtract(self, a: float, b: float) -> float:
return a - b
def multiply(self, a: float, b: float) -> float:
return a * b
def divide(self, a: float, b: float):
if b == 0:
return Err("Division by zero")
return Ok(a / b)
Data Processing Tool
wit/world.wit:
package local:data-processor;
world processor {
export process-csv: func(data: string) -> result<string, string>;
export analyze-data: func(data: string) -> result<string, string>;
}
main.py:
import wit_world
from wit_world.types import Ok, Err
import csv
import json
from io import StringIO
class Processor(wit_world.Processor):
def process_csv(self, data: str) -> str:
try:
reader = csv.DictReader(StringIO(data))
rows = list(reader)
return Ok(json.dumps(rows))
except Exception as e:
return Err(f"CSV processing error: {str(e)}")
def analyze_data(self, data: str) -> str:
try:
items = json.loads(data)
analysis = {
"count": len(items),
"summary": f"Processed {len(items)} items"
}
return Ok(json.dumps(analysis))
except Exception as e:
return Err(f"Analysis error: {str(e)}")
Error Handling
Python components use WIT’s result type for error handling:
from wit_world.types import Ok, Err
# Success
return Ok(result_value)
# Error
return Err("Error message")
# Or raise an exception
raise handle_error(exception)
Working with WIT Types
Type Mappings
# WIT type -> Python type
# s32, s64, u32, u64 -> int
# f32, f64 -> float
# string -> str
# bool -> bool
# list<T> -> List[T]
# option<T> -> Optional[T]
# result<T, E> -> Ok[T] | Err[E]
# record -> dataclass or dict
Complex Types
from dataclasses import dataclass
from typing import Optional, List
@dataclass
class Person:
name: str
age: int
email: Optional[str]
def process_people(people: List[Person]) -> str:
return json.dumps([p.__dict__ for p in people])
Best Practices
- Use type hints - Python type hints help catch errors early
- Handle errors properly - Always return
OkorErrfor result types - Document your code - Use docstrings to explain functionality
- Test thoroughly - Validate edge cases and error conditions
- Keep it simple - Avoid complex dependencies that might not work in Wasm
- Avoid
eval()for untrusted input - Useast.literal_eval()or proper parsers instead ofeval()to prevent code injection
Common Patterns
JSON Processing
import json
def process_json(data: str) -> str:
try:
parsed = json.loads(data)
# Process data
result = {"processed": True, "data": parsed}
return Ok(json.dumps(result))
except json.JSONDecodeError as e:
return Err(f"Invalid JSON: {str(e)}")
File Processing
def read_file(path: str) -> str:
try:
with open(path, 'r') as f:
content = f.read()
return Ok(content)
except FileNotFoundError:
return Err(f"File not found: {path}")
except PermissionError:
return Err(f"Permission denied: {path}")
Data Validation
def validate_input(data: str) -> str:
if not data:
return Err("Input cannot be empty")
if len(data) > 1000:
return Err("Input too large (max 1000 characters)")
return Ok(f"Valid input: {data}")
Troubleshooting
Build Errors
- Ensure Python 3.10+ is installed
- Verify
componentize-pyis installed via uv - Check that WIT interface matches your Python class
Runtime Errors
- Validate all imports are available in Wasm environment
- Check that file paths are correct
- Review Wassette logs for detailed errors
Import Errors
Some Python libraries may not work in Wasm. Stick to:
- Standard library modules (json, csv, math, etc.)
- Pure Python packages
- Modules explicitly tested with componentize-py
Full Documentation
For complete details, including advanced topics and more examples, see the Python Development Guide.
Working Examples
See this complete working example in the repository:
- eval-py - Python code execution component
Next Steps
- Review the complete Python guide
- Check out working examples
- Learn about componentize-py
- Read the FAQ