use ndata::dataobject::DataObject;
use std::sync::{Once, RwLock};
use pyo3::prelude::*;
use std::path::Path;
use crate::DataStore;
use crate::pycmd::PyCmd;
use ndata::data::Data;
use pyo3::types::PyDict;
use pyo3::types::PyList;
use ndata::databytes::DataBytes;
use ndata::dataarray::DataArray;
use pyo3::types::PyBytes;
static START: Once = Once::new();
static PYENV: RwLock<Option<PyEnv>> = RwLock::new(None);
pub struct PyEnv;
impl PyEnv {
pub fn new(venv_path: Option<&str>) -> PyEnv {
if let Some(path) = venv_path {
if !Path::new(path).exists() {
panic!("Virtual environment not found at {}", path);
}
PyEnv::activate_venv(path);
}
PyEnv
}
fn activate_venv(venv_path: &str) {
let venv_bin = format!("{}/bin/python", venv_path);
std::env::set_var("PYTHONHOME", "");
std::env::set_var("PYTHONPATH", "");
std::env::set_var("PATH", format!("{}/bin:{}", venv_path, std::env::var("PATH").unwrap()));
std::env::set_var("VIRTUAL_ENV", venv_path);
let output = std::process::Command::new(&venv_bin)
.arg("--version")
.output()
.expect("Failed to activate virtual environment");
if !output.status.success() {
panic!(
"Failed to use Python from virtual environment: {}",
String::from_utf8_lossy(&output.stderr)
);
} else {
}
}
pub fn execute_function(&self, file_path: &str, name: &str, params: DataObject) -> PyResult<DataObject> {
let base_path = "cmd/src";
let module_name = compute_module_name(base_path, file_path);
Python::with_gil(|py| {
let sys_module = py.import("sys")?;
let sys_path: &PyList = sys_module.getattr("path")?.downcast::<PyList>()?;
if !sys_path.contains(base_path)? {
sys_path.insert(0, base_path)?;
}
let file_dir = Path::new(file_path).parent().unwrap();
let file_dir_str = file_dir.to_str().unwrap();
if !sys_path.contains(file_dir_str)? {
sys_path.insert(0, file_dir_str)?;
}
let module = py.import(module_name.as_str())?;
let kwargs = PyDict::new(py);
for (key, data) in params.objects() {
let py_value = data_to_py(data, py)?;
kwargs.set_item(key, py_value)?;
}
let function = module.getattr(name)?;
let result: &PyAny = function.call((), Some(kwargs))?;
Ok(py_any_to_data_object(result, py)?)
})
}
}
pub fn data_to_py(data: Data, py: Python) -> PyResult<PyObject> {
match data {
Data::DInt(i) => Ok(i.to_object(py)), Data::DFloat(f) => Ok(f.to_object(py)), Data::DBoolean(b) => Ok(b.to_object(py)), Data::DString(s) => Ok(s.to_object(py)), Data::DNull => Ok(py.None()),
Data::DBytes(bytes_ref) => {
let bytes = DataBytes::get(bytes_ref); Ok(PyBytes::new(py, &bytes.get_data()).to_object(py)) },
Data::DArray(array_ref) => {
let array = DataArray::get(array_ref);
let py_objects: PyResult<Vec<PyObject>> = array.objects()
.iter()
.map(|item| data_to_py(item.clone(), py))
.collect();
Ok(PyList::new(py, py_objects?).to_object(py))
},
Data::DObject(object_ref) => {
let obj = DataObject::get(object_ref); let py_dict = PyDict::new(py);
for (key, value) in obj.objects().iter() {
py_dict.set_item(key, data_to_py(value.clone(), py)?)?;
}
Ok(py_dict.to_object(py)) },
}
}
fn py_any_to_data_object(py_any: &PyAny, py: Python) -> PyResult<DataObject> {
let mut data = DataObject::new();
data.put_string("status", "ok");
if let Ok(s) = py_any.extract::<String>() {
data.put_string("data", &s); } else if let Ok(i) = py_any.extract::<i64>() {
data.put_int("data", i); } else if let Ok(f) = py_any.extract::<f64>() {
data.put_float("data", f); } else if let Ok(b) = py_any.extract::<bool>() {
data.put_boolean("data", b); } else if let Ok(bytes) = py_any.extract::<Vec<u8>>() {
data.put_bytes("data", DataBytes::from_bytes(&bytes)); } else if let Ok(py_dict) = py_any.extract::<&PyDict>() {
let mut map = DataObject::new();
for (key, value) in py_dict.iter() {
let key: String = key.extract()?;
let value_do = py_any_to_data_object(value, py)?;
map.set_property(&key, value_do.get_property("data"));
}
data.put_object("data", map); } else if let Ok(py_list) = py_any.extract::<&PyList>() {
let mut vec = DataArray::new();
for item in py_list.iter() {
let value_do = py_any_to_data_object(item, py)?;
vec.push_property(value_do.get_property("data"));
}
data.put_array("data", vec); } else if py_any.is_none() {
data.put_null("data"); } else {
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"Unsupported type for conversion to DataObject.",
));
}
Ok(data) }
fn compute_module_name(base_path: &str, file_path: &str) -> String {
let relative_path = Path::new(file_path)
.strip_prefix(base_path)
.expect("Base path not found in file path");
let module_path = relative_path.with_extension(""); let module_name = module_path.to_string_lossy().replace("/", ".").replace("\\", ".");
module_name.to_string()
}
pub fn dopy(lib: &str, id: &str, args: DataObject) -> DataObject {
START.call_once(|| {
let venv = DataStore::new().root.parent().unwrap().join("venv");
let venvstr = venv.display().to_string();
let venv = match venv.exists() {
true => Some(venvstr.as_ref()),
_ => None,
};
*PYENV.write().unwrap() = Some(PyEnv::new(venv));
});
let store = DataStore::new();
let cmd = store.get_data(lib, id);
let cmd = cmd.get_object("data");
let jsid = cmd.get_string("python");
let name = cmd.get_string("name");
let f = PyCmd::get_path(lib, id);
let cmd = store.get_data(lib, &jsid);
let cmd = cmd.get_object("data");
let params = cmd.get_array("params");
let mut a = DataObject::new();
for o in params.objects() {
let key = o.object().get_string("name");
let val = args.get_property(&key);
a.set_property(&key, val);
}
let py_env = PYENV.read().unwrap();
let result = py_env.as_ref()
.expect("PyEnv not initialized")
.execute_function(&f, &name, a);
match result {
Ok(data_object) => data_object,
Err(e) => {
eprintln!("Error calling Python function: {}", e);
DataObject::new() }
}
}