use crate::catalog::Catalog as RustCatalog;
use crate::command_types::CommandType;
use crate::probe::{probe_device as probe_device_rs, ProbeConfig, ProbeReport};
use pyo3::exceptions::{PyKeyError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyDict;
use std::time::Duration;
#[pyclass(name = "Catalog", module = "mediacast_netcatalog._native")]
struct PyCatalog {
inner: RustCatalog,
}
#[pymethods]
impl PyCatalog {
#[staticmethod]
fn load_bundled() -> PyResult<Self> {
RustCatalog::load_bundled()
.map(|c| PyCatalog { inner: c })
.map_err(|e| PyValueError::new_err(e.to_string()))
}
#[staticmethod]
fn load_dir(path: &str) -> PyResult<Self> {
RustCatalog::load_dir(path)
.map(|c| PyCatalog { inner: c })
.map_err(|e| PyValueError::new_err(e.to_string()))
}
fn vendors(&self) -> Vec<String> {
self.inner.vendors().map(|s| s.to_owned()).collect()
}
fn cli(&self, vendor: &str, firmware: &str, command: &str) -> PyResult<String> {
let cmd = parse_command_type(command)?;
match self.inner.lookup(vendor, firmware, cmd) {
Ok(Some(entry)) => Ok(entry.cli.clone()),
Ok(None) => Err(PyKeyError::new_err(format!(
"no entry for {} {:?}",
vendor, cmd
))),
Err(e) => Err(PyValueError::new_err(e.to_string())),
}
}
}
fn parse_command_type(s: &str) -> PyResult<CommandType> {
let v: serde_yaml::Value = serde_yaml::Value::String(s.to_owned());
serde_yaml::from_value(v)
.map_err(|_| PyValueError::new_err(format!("unknown CommandType: {}", s)))
}
#[pyfunction]
#[pyo3(signature = (host, vendor, timeout_seconds=5.0, skip=Vec::new()))]
fn probe_device(
py: Python<'_>,
host: &str,
vendor: &str,
timeout_seconds: f64,
skip: Vec<String>,
) -> PyResult<PyObject> {
let cfg = ProbeConfig {
timeout: Duration::from_secs_f64(timeout_seconds.max(0.001)),
skip,
};
let report = py
.allow_threads(|| probe_device_rs(host, vendor, &cfg))
.map_err(|e| PyValueError::new_err(e.to_string()))?;
report_to_pydict(py, &report)
}
fn report_to_pydict(py: Python<'_>, r: &ProbeReport) -> PyResult<PyObject> {
let d = PyDict::new_bound(py);
d.set_item("host", &r.host)?;
d.set_item("vendor", &r.vendor)?;
d.set_item("netconf_available", r.netconf_available)?;
d.set_item("gnmi_available", r.gnmi_available)?;
d.set_item("restconf_available", r.restconf_available)?;
d.set_item("ssh_banner", r.ssh_banner.as_deref())?;
d.set_item("firmware", r.firmware.as_deref())?;
d.set_item("diagnostics", r.diagnostics.clone())?;
d.set_item("elapsed_ms", r.elapsed_ms)?;
Ok(d.into())
}
#[pymodule]
fn _native(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyCatalog>()?;
m.add_function(wrap_pyfunction!(probe_device, m)?)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}