facett-py/src/lib.rs
//! **facett_native** — native Python bindings for facett's component registry.
//!
//! This is the **headless, direct-call** companion to the `facet-wrapped` wasm
//! panel. Where `facet-wrapped` mounts a *live egui canvas* inside a Streamlit
//! iframe (the interactive embed), this extension exposes the same crate's **pure-Rust
//! [`Registry`]** to Python with no browser, no wasm, and no eframe event loop:
//!
//! - [`components`] / [`manifest`] — the embeddable component manifest
//! (`[{name, description, title}, …]`), the exact list a Streamlit sidebar offers;
//! - [`component_names`] — just the registry names;
//! - [`component_count`] — how many components ship;
//! - [`component_state`] — a single component's **initial `state_json`** as a Python
//! object (the data a host reads back to render server-side or assert on).
//!
//! The registry is reused verbatim from `facet-wrapped` (one source of truth for the
//! component set), so this wheel and the Streamlit panel can never drift apart.
//!
//! JSON is materialised into Python objects by handing the compact `state_json` string
//! to CPython's own `json.loads` — no extra Rust serialization crate, and the result is
//! plain `dict`/`list`/`str`/`int`/`float`/`bool`/`None` the caller can use directly.
use pyo3::prelude::*;
use facet_wrapped::Registry;
/// Parse a JSON string into a Python object via CPython's `json.loads`.
fn json_loads(py: Python<'_>, s: &str) -> PyResult<Py<PyAny>> {
let json = py.import("json")?;
Ok(json.call_method1("loads", (s,))?.unbind())
}
/// `facett_native.__version__` mirror — the facett crate version this wheel was built
/// from. Exposed as a function so it always reflects the compiled crate.
#[pyfunction]
fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
/// The component **manifest** as a list of dicts: `[{name, description, title}, …]`.
/// The same list the Streamlit `components()` helper reads from the built bundle, but
/// computed live here with no wasm build required.
#[pyfunction]
fn components(py: Python<'_>) -> PyResult<Py<PyAny>> {
let manifest = Registry::with_builtins().manifest_json().to_string();
json_loads(py, &manifest)
}
/// Alias for [`components`] — the embeddable-component manifest.
#[pyfunction]
fn manifest(py: Python<'_>) -> PyResult<Py<PyAny>> {
components(py)
}
/// Every embeddable component **name**, in registration order.
#[pyfunction]
fn component_names() -> Vec<String> {
Registry::with_builtins().names().into_iter().map(str::to_string).collect()
}
/// How many components are embeddable.
#[pyfunction]
fn component_count() -> usize {
Registry::with_builtins().len()
}
/// The **initial `state_json`** of component `name` as a Python object (`dict`/`list`),
/// or `None` if the name is unknown. This is the exact data a Streamlit host reads back
/// across the wasm boundary — here delivered directly, with no browser.
#[pyfunction]
fn component_state(py: Python<'_>, name: &str) -> PyResult<Option<Py<PyAny>>> {
match Registry::with_builtins().component_state_json(name) {
Some(v) => Ok(Some(json_loads(py, &v.to_string())?)),
None => Ok(None),
}
}
/// The `facett_native` extension module.
#[pymodule]
fn facett_native(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_function(wrap_pyfunction!(components, m)?)?;
m.add_function(wrap_pyfunction!(manifest, m)?)?;
m.add_function(wrap_pyfunction!(component_names, m)?)?;
m.add_function(wrap_pyfunction!(component_count, m)?)?;
m.add_function(wrap_pyfunction!(component_state, m)?)?;
Ok(())
}