Appearance
Python Guide
Build Noorle plugins with Python.
Why Python?
Strengths
- Rapid Development - Familiar syntax and extensive standard library
- Easy JSON Handling - Native support for data processing
- Dynamic Typing - Flexible for varied data structures
- Quick Prototyping - Test ideas fast before optimization
- Clear Syntax - Readable code that's easy to maintain
Best For
- API integrations
- Data transformation
- Quick prototypes
- JSON/XML processing
- Script automation ports
Trade-offs
- Larger binary size than compiled languages
- Performance overhead vs native code
- Limited to componentize-py supported features
Quick Start
Prerequisites
bash
# Install Noorle CLI
curl -L cli.noorle.dev | sh
Create Plugin
bash
# Initialize from template
noorle plugin init my-plugin --template python
cd my-plugin
# Install dependencies (Python, uv, componentize-py, wasm tools)
noorle plugin prepare
# Build and deploy
noorle plugin publish
Minimal Example
python
import wit_world
from wit_world.types import Ok, Err
class WitWorld(wit_world.WitWorld):
def process(self, input: str) -> wit_world.Result[str, str]:
if not input:
return Err("Input cannot be empty")
return Ok(f"Processed: {input}")
Project Structure
tree
my-plugin/
├── app.py # Main implementation
├── wit/world.wit # API definition
├── pyproject.toml # Python dependencies
├── build.sh # Build script
├── prepare.sh # Dependency installer
├── noorle.yaml # Plugin config (optional)
└── dist/ # Build output
├── plugin.wasm # Compiled component
└── *.npack # Deployment archive
File Descriptions
File | Purpose |
---|---|
app.py | Plugin implementation class |
wit/world.wit | WebAssembly interface definition |
pyproject.toml | Dependencies (uv/pip format) |
build.sh | Compilation and component creation |
prepare.sh | Installs Python, uv, and wasm tools |
Implementation Guide
Basic Plugin Structure
python
import wit_world
from wit_world.types import Ok, Err
class WitWorld(wit_world.WitWorld):
"""Your plugin implementation"""
def say_hi(self, name: str) -> str:
"""Simple string function"""
return f"Hello, {name}!"
def add(self, a: float, b: float) -> float:
"""Numeric operations"""
return a + b
def process(self, input: str) -> wit_world.Result[str, str]:
"""Function with error handling"""
if not input:
return Err("Input required")
return Ok(f"Processed: {input}")
Working with WIT
Define your API in wit/world.wit
:
wit
package example:my-plugin;
world my-plugin-component {
/// Greet a person by name
export say-hi: func(name: string) -> string;
/// Add two numbers
export add: func(a: f64, b: f64) -> f64;
/// Process with error handling
export process: func(input: string) -> result<string, string>;
}
Python-specific binding:
python
import wit_world
from wit_world.types import Ok, Err
class WitWorld(wit_world.WitWorld):
def process(self, input: str) -> wit_world.Result[str, str]:
# Result maps to WIT result type
if not input:
return Err("Input cannot be empty")
# Process and return Ok
result = self.do_processing(input)
return Ok(result)
Error Handling
python
from wit_world.types import Ok, Err
import json
def process(self, data: str) -> wit_world.Result[str, str]:
"""Robust error handling pattern"""
try:
# Validate input
if not data:
return Err("Data is required")
if len(data) > 10000:
return Err(f"Data too large: {len(data)} bytes")
# Parse and process
parsed = json.loads(data)
# Business logic
result = self.transform_data(parsed)
return Ok(json.dumps(result))
except json.JSONDecodeError as e:
return Err(f"Invalid JSON: {e}")
except KeyError as e:
return Err(f"Missing field: {e}")
except Exception as e:
return Err(f"Processing failed: {e}")
Environment Variables
python
from wit_world.imports import environment
def get_config(self) -> dict:
"""Read environment configuration"""
env_vars = environment.get_environment()
config = {}
for name, value in env_vars:
if name == "API_KEY":
config["api_key"] = value
elif name == "DEBUG":
config["debug"] = value.lower() == "true"
elif name == "TIMEOUT":
config["timeout"] = int(value)
return config
JSON Processing
python
import json
from wit_world.types import Ok, Err
def process_json(self, input: str) -> wit_world.Result[str, str]:
"""JSON transformation example"""
try:
# Parse input
data = json.loads(input)
# Validate structure
if not isinstance(data, dict):
return Err("Expected JSON object")
# Transform data
data["processed"] = True
data["plugin"] = "my-plugin"
# Add nested structures
if "items" in data:
data["count"] = len(data["items"])
# Return formatted JSON
return Ok(json.dumps(data, indent=2))
except json.JSONDecodeError as e:
return Err(f"Invalid JSON: {e}")
HTTP Requests (Optional)
Required WIT imports:
wit
world my-plugin-component {
import wasi:http/types@0.2.0;
import wasi:http/outgoing-handler@0.2.0;
export fetch-data: func(url: string) -> result<string, string>;
}
Implementation:
python
from wit_world.imports import types as http_types
from wit_world.imports import outgoing_handler
from wit_world.imports import poll as poll_module
def fetch_data(self, url: str) -> wit_world.Result[str, str]:
try:
# Create headers
headers = http_types.Fields()
headers.append("User-Agent", b"my-plugin/1.0")
headers.append("Accept", b"application/json")
# Create request
request = http_types.OutgoingRequest(headers)
request.set_method(http_types.Method_Get())
request.set_scheme(http_types.Scheme_Https())
request.set_authority("api.example.com")
request.set_path_with_query("/data")
# Send request
response = outgoing_handler.handle(request, None)
response_pollable = response.subscribe()
poll_module.poll([response_pollable])
# Get response
result = response.get()
if result.is_err():
return Err("Request failed")
# Read body
body = self.read_response_body(result.ok())
return Ok(body.decode('utf-8'))
except Exception as e:
return Err(f"HTTP request failed: {e}")
Required permissions:
yaml
permissions:
network:
allow:
- host: "api.example.com"
Testing
Local Testing with Wasmtime
bash
# Build the plugin
noorle plugin build
# Test with WAVE format
wasmtime run --invoke 'say_hi("World")' dist/plugin.wasm
wasmtime run --invoke 'add(2.5, 3.7)' dist/plugin.wasm
wasmtime run --invoke 'process("test data")' dist/plugin.wasm
# With environment variables
wasmtime run --env API_KEY=test123 --invoke 'process("test")' dist/plugin.wasm
Language-Specific Features
componentize-py Integration
Python code is compiled to WebAssembly using componentize-py:
toml
# pyproject.toml
[project]
dependencies = [
"componentize-py==0.17.2", # Required
# Add your dependencies here
]
Type Hints
python
from typing import List, Dict, Optional
import wit_world
from wit_world.types import Ok, Err
class WitWorld(wit_world.WitWorld):
def process_list(self, items: List[str]) -> wit_world.Result[str, str]:
"""Type hints improve code clarity"""
if not items:
return Err("List cannot be empty")
processed: List[str] = [item.upper() for item in items]
return Ok(", ".join(processed))
def process_dict(self, data: Dict[str, any]) -> wit_world.Result[str, str]:
"""Handle dictionary data"""
required_fields = ["id", "name"]
for field in required_fields:
if field not in data:
return Err(f"Missing required field: {field}")
return Ok(json.dumps(data))
Python Built-ins
python
import json
import base64
import hashlib
from datetime import datetime
class WitWorld(wit_world.WitWorld):
def encode_data(self, data: str) -> str:
"""Use standard library functions"""
# Base64 encoding
encoded = base64.b64encode(data.encode()).decode()
# Generate hash
hash_obj = hashlib.sha256(data.encode())
data_hash = hash_obj.hexdigest()
# Add timestamp
timestamp = datetime.utcnow().isoformat()
return json.dumps({
"encoded": encoded,
"hash": data_hash,
"timestamp": timestamp
})
Build Configuration
Dependencies
pyproject.toml:
toml
[project]
name = "my-plugin"
dependencies = [
"componentize-py==0.17.2", # Required for WASM compilation
# Add your dependencies here
# Note: Not all Python packages work in WASM
]
[build-system]
requires = ["setuptools", "componentize-py"]
Build Optimization
The build process automatically optimizes for size:
bash
# Build handled by noorle
noorle plugin build
Build Process
bash
# Build your plugin (handles compilation, optimization, packaging)
noorle plugin build
The build process:
- Installs Python dependencies via uv
- Compiles Python to WASM with componentize-py
wkg
fetches WIT dependencies from imports- Creates component with wasm-tools
- Optimizes output
- Packages into .npack archive
Troubleshooting
Common Issues
Build Fails
Problem: componentize-py: command not found
Solution:
bash
noorle plugin prepare # Installs all required tools
Runtime Error
Problem: ModuleNotFoundError: No module named 'wit_world'
Solution:
python
# Ensure class name matches
class WitWorld(wit_world.WitWorld): # Must be "WitWorld"
pass
Package Not Supported
Problem: Package X not available in WASM
Solution:
python
# Use alternatives or implement functionality
# Check componentize-py docs for supported packages
# Consider using pure Python implementations
Debugging
Enable debug output:
bash
DEBUG=1 noorle plugin build
Check generated files:
bash
ls -la wit_world/
View WASM exports:
bash
wasm-tools component wit dist/plugin.wasm
Performance
Optimization tips:
- Keep dependencies minimal
- Use built-in functions when possible
- Avoid heavy libraries
- Pre-compute when possible
- Cache processed results
Best Practices
Do's
- ✅ Use Result types for error handling
- ✅ Validate input early
- ✅ Use type hints for clarity
- ✅ Handle all exceptions
- ✅ Test with wasmtime locally
- ✅ Keep functions focused
Don'ts
- ❌ Don't use packages with C extensions
- ❌ Don't rely on filesystem access
- ❌ Don't use threading or multiprocessing
- ❌ Don't ignore error cases
- ❌ Don't use global mutable state
Security
- Validate all inputs
- Use minimal permissions in noorle.yaml
- Never log sensitive data
- Sanitize error messages
- Handle timeouts gracefully
Advanced Topics
Custom WASI Interfaces
wit
// Advanced WIT with custom types
interface types {
record config {
endpoint: string,
timeout: u32,
retries: u8,
}
record response {
status: u16,
body: string,
headers: list<tuple<string, string>>,
}
}
world my-plugin {
use types.{config, response};
export process-with-config: func(cfg: config) -> response;
}
python
from wit_world import types
def process_with_config(self, cfg: types.Config) -> types.Response:
"""Use generated types from WIT"""
# Process with config
return types.Response(
status=200,
body="Success",
headers=[("Content-Type", "application/json")]
)
Performance Monitoring
python
import time
def timed_process(self, input: str) -> wit_world.Result[str, str]:
"""Monitor execution time"""
start = time.time()
try:
result = self.process_internal(input)
elapsed = time.time() - start
# Log timing (visible in wasmtime stderr)
print(f"Processing took: {elapsed:.3f}s", file=sys.stderr)
return Ok(result)
except Exception as e:
return Err(str(e))
Working with Custom Types
python
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class ProcessRequest:
"""Custom request type"""
id: str
data: List[str]
options: Optional[dict] = None
class WitWorld(wit_world.WitWorld):
def handle_complex(self, input: str) -> wit_world.Result[str, str]:
"""Handle complex custom types"""
try:
data = json.loads(input)
# Parse to custom type
request = ProcessRequest(
id=data.get("id"),
data=data.get("data", []),
options=data.get("options")
)
# Validate
if not request.id:
return Err("Request ID required")
# Process
result = self.process_request(request)
return Ok(json.dumps(result))
except Exception as e:
return Err(f"Failed to process: {e}")
Resources
Documentation
Tools
- uv - Fast Python package manager
- componentize-py - Python to WASM compiler
- wasm-tools - WASM utilities
Examples
Next Steps
Continue Learning
- Other Language Guides - Explore TypeScript, JavaScript, Go, Rust
- Configuration - Advanced plugin settings
- Publishing - Deploy your plugin
Build Your Plugin
- Quick Start - Step-by-step guide
- Project Structure - Understanding plugin anatomy
- Platform Overview - How plugins work in Noorle