PyWatt SDK
Standardized SDK for building PyWatt modules in Rust.
Overview
This crate provides the core building blocks for creating PyWatt modules that integrate seamlessly with the PyWatt orchestrator. It handles:
- IPC Handshake: Standardized startup communication (
read_init,send_announce). - Logging: Consistent logging to
stderrwith secret redaction (init_module). - Secret Management: Secure retrieval and rotation handling via the integrated
secret_clientmodule (get_secret,subscribe_secret_rotations). - Runtime IPC: Background task for processing orchestrator messages (
process_ipc_messages). - Core Types: Re-exports essential types from the integrated
ipc_typesmodule (OrchestratorInit,ModuleAnnounce, etc.). - (Optional) Macros: Proc macros for simplifying module definition (requires
proc_macrosfeature). - (Optional) JWT Auth: Middleware for Axum route protection (requires
jwt_authfeature).
Installation
Add this to your module's Cargo.toml:
[]
# Use the version from crates.io
= "0.1.0"
# Or use a path dependency during development
# pywatt_sdk = { path = "../pywatt_sdk" }
# Other dependencies like axum, tokio, etc.
= { = "1", = ["full"] }
= "0.7"
= "0.1"
Quickstart Example
Here's a minimal module using the SDK with Axum:
// Use the prelude for common types and functions
use *;
use ;
use ;
use Arc;
// Define your module-specific state if needed
// Use the SDK's AppState wrapper for shared state
type SharedState = ;
async
async
async
## Python SDK
Install the Python SDK:
pip install pywatt_sdk
Or use the development requirements:
pip install -r requirements.txt
### Quickstart Example
```python
from pywatt_sdk import (
init_module,
read_init,
send_announce,
process_ipc_messages,
get_module_secret_client,
get_secret,
subscribe_secret_rotations,
AppState,
EndpointAnnounce,
serve_module,
ModuleBuilder,
)
from starlette.applications import Starlette
from starlette.routing import Route
# Define a simple ASGI app
async def health(request):
return JSONResponse({"status": "OK"})
# Entry point
if __name__ == "__main__":
def state_builder(init, secrets):
# Build user state here
return {"secrets": secrets}
def app_builder(app_state):
routes = [Route("/health", health)]
app = Starlette(routes=routes)
return app
# Define endpoints to announce
endpoints = [EndpointAnnounce(path="/health", methods=["GET"])]
# Serve module
serve_module(
secret_keys=["MY_SECRET_KEY"],
endpoints=endpoints,
state_builder=state_builder,
app_builder=app_builder,
)
Core Functions & Types
(See the prelude module for the most common items)
init_module(): Configures logging to stderr with secret redaction.read_init() -> Result<OrchestratorInit, InitError>: Reads the initial handshake message from stdin.send_announce(announce: &ModuleAnnounce) -> Result<(), AnnounceError>: Sends the module's announcement message to stdout.process_ipc_messages(): An async function to spawn that listens for runtime IPC messages (like secret rotations, shutdown) from stdin.get_module_secret_client(...): Creates aSecretClientinstance.get_secret(...): Retrieves a secret, ensuring it's registered for redaction.subscribe_secret_rotations(...): Spawns a task to handle secret rotation notifications.AppState<T>: A generic state container holding SDK state and optional user stateT.- Types from
ipc_types:OrchestratorInit,ModuleAnnounce,AnnouncedEndpoint,ListenAddress, etc. - Types from
secret_client:SecretClient,SecretError,RequestMode.
JWT Authentication (Optional Feature)
The SDK provides an optional jwt_auth feature to easily protect Axum routes with JWT Bearer token validation.
Enable Feature:
[]
# Ensure the jwt_auth feature is enabled
= { = "0.1.0", = ["jwt_auth"] }
= { = "0.7", = ["json"] }
# ... other deps
Usage:
-
Request the HMAC signing secret (e.g.,
JWT_HMAC_SECRET) in your module configuration (how secrets are requested depends on the orchestrator/module definition). -
Fetch the secret using
get_secret:use *; use Router; async # async -
The middleware automatically:
- Expects an
Authorization: Bearer <token>header. - Validates the token using the provided HMAC secret (HS256 algorithm).
- Returns
401 Unauthorizedif the header is missing, the token is invalid, expired, or has the wrong signature. - On success, inserts the decoded JWT claims (as
serde_json::Value) into the request extensions.
- Expects an
-
Access claims in your handler:
use Extension; use Value; async
Contributing
Contributions are welcome! Please follow standard Rust practices and ensure tests pass.
License
This project is licensed under the MIT OR Apache-2.0 license. See LICENSE-MIT or LICENSE-APACHE for details.