pywatt_macros

Crates.iopywatt_macros
lib.rspywatt_macros
version0.2.9
created_at2025-06-05 16:42:57.339735+00
updated_at2025-06-05 16:42:57.339735+00
descriptionProcedural macros for the PyWatt SDK
homepage
repository
max_upload_size
id1701808
size17,988
(frostdev-ops)

documentation

README

PyWatt Macros

This crate provides procedural macros to simplify the development of Rust-based PyWatt modules.

#[pywatt_sdk::module]

The primary macro offered is #[pywatt_sdk::module]. This attribute macro is designed to be applied to an async function that returns an axum::Router and takes an pywatt_sdk::AppState<T> as its argument (where T is your custom module state).

It automates the following boilerplate tasks:

  1. Initialization: Sets up module logging.
  2. Orchestrator Handshake: Reads initial configuration from the orchestrator.
  3. Secret Management:
    • Creates a SecretClient for interacting with the orchestrator's secret service.
    • Prefetches specified secrets.
    • Optionally subscribes to secret rotation events for the prefetched secrets.
  4. State Creation:
    • Builds the user-defined module state. It calls a user-provided state builder function (or Default::default() if none is specified), passing the orchestrator's initialization data and any prefetched secrets.
    • Constructs the main pywatt_sdk::AppState which wraps the user state and other SDK components.
  5. Router Setup:
    • Calls the annotated function to get the base Axum router.
    • Layers the pywatt_sdk::AppState into the router so it's accessible to handlers.
  6. Standard Endpoints:
    • Adds a health check endpoint (defaults to /health, path is configurable).
    • Optionally adds a Prometheus metrics endpoint (defaults to /metrics, currently a placeholder).
  7. Module Announcement: Announces the module's listen address and its HTTP endpoints (including health and metrics if enabled) to the orchestrator.
  8. Server Startup: Starts an Axum HTTP server listening on the TCP address provided by the orchestrator.

Arguments

The #[pywatt_sdk::module] macro accepts the following arguments:

  • secrets: [&str]: An array of secret keys (string literals) to be prefetched at startup. Example: secrets = ["api_key", "db_password"].
  • rotate: bool: If true, subscribes to rotation events for the secrets specified in the secrets argument. Defaults to false. Example: rotate = true.
  • endpoints: [AnnouncedEndpoint]: An array of pywatt_sdk::AnnouncedEndpoint structs describing the custom endpoints your module provides. Example: endpoints = [pywatt_sdk::AnnouncedEndpoint { path: "/my/data".to_string(), methods: vec!["GET".to_string(), "POST".to_string()], auth: None }].
  • health: &str (or health_path: &str): Optional. Custom path for the health check endpoint. Defaults to "/health". Example: health = "/status".
  • metrics: bool: If true, enables a /metrics endpoint. Defaults to false. Example: metrics = true.
  • version: &str: Optional. A version string to prefix all announced custom endpoint paths (health and metrics paths are not prefixed). Example: version = "v1" would change an endpoint /data to /v1/data in the announcement.
  • state: fn_path: Optional. A path to a function that builds your custom module state. The function must have a signature compatible with fn(&pywatt_sdk::OrchestratorInit, Vec<secrecy::SecretString>) -> UserStateType. If not provided, Default::default() is used to create the user state. Example: state = my_custom_state_builder.
  • channels, security_level, auth_required: These arguments are currently parsed but ignored. They are included for potential future compatibility.

Example Usage

use pywatt_sdk::{self, AppState, AnnouncedEndpoint, Result};
use axum::{routing::get, Router};
use secrecy::ExposeSecret;

// Define your custom module state (if any)
#[derive(Default, Clone)]
struct MyModuleState {
    api_key: Option<String>,
}

// Optional: Define a custom state builder function
fn build_my_state(init_data: &pywatt_sdk::OrchestratorInit, secrets: Vec<secrecy::SecretString>) -> MyModuleState {
    println!("Building MyModuleState. Orchestrator API: {}", init_data.orchestrator_api);
    let mut api_key = None;
    if let Some(secret_value) = secrets.get(0) { // Assuming "MY_API_KEY" is the first secret
        api_key = Some(secret_value.expose_secret().clone());
        println!("API Key prefetched!");
    }
    MyModuleState { api_key }
}

async fn get_hello(state: AppState<MyModuleState>) -> String {
    if let Some(key) = &state.user_state.api_key {
        format!("Hello from PyWatt module with API key: {}...", &key[..5])
    } else {
        "Hello from PyWatt module!".to_string()
    }
}

#[pywatt_sdk::module(
    secrets = ["MY_API_KEY"],
    rotate = true,
    endpoints = [
        AnnouncedEndpoint {
            path: "/hello".to_string(),
            methods: vec!["GET".to_string()],
            auth: None,
        }
    ],
    state = build_my_state,
    version = "v1"
)]
async fn my_module_router(state: AppState<MyModuleState>) -> Router {
    Router::new().route("/hello", get(get_hello).with_state(state))
}

// The macro will generate the main function and all boilerplate.
// To run this (assuming it's in src/main.rs and Cargo.toml is set up):
// cargo run

This macro significantly reduces the amount of code needed to get a PyWatt module up and running, allowing developers to focus on the module's core logic.

Commit count: 0

cargo fmt