use std::{collections::HashMap, fs, io::{BufWriter, Cursor}, path::PathBuf, sync::Arc};
use image::ImageFormat;
use pyo3::{prelude::*, types::{PyBytes, PyDict, PyFloat, PyInt, PyList, PyNone, PySet, PyString, PyTuple}};
use juiz_sdk::serde_json::Map;
use juiz_sdk::anyhow::{self, anyhow};
use crate::{containers::{bind_container_function, container_factory_create, container_process_factory_create_from_trait}, prelude::*, processes::process_factory_create_from_trait};
pub struct PythonPlugin {
path: PathBuf,
pythonpaths: Option<Vec<PathBuf>>,
}
pub struct PythonContainerStruct {
pub pyobj: Py<PyAny>
}
impl PythonPlugin {
pub fn profile_full(&self) -> JuizResult<Value> {
Ok(jvalue!({
"path": self.path,
}))
}
pub fn load(path: PathBuf, pythonpaths: Option<Vec<PathBuf>>) -> JuizResult<PythonPlugin> {
log::trace!("PythonPlugin::load({:?}) called", path);
Ok(PythonPlugin{path, pythonpaths})
}
fn init_path(&self, working_dir: Option<PathBuf>) -> JuizResult<()> {
let fullpath = working_dir.clone().unwrap_or(env!("CARGO_MANIFEST_DIR").into()).join(self.path.clone());
let pythonpaths = self.pythonpaths.clone();
log::debug!("pythonpaths:{pythonpaths:?}");
Python::with_gil(|py| -> PyResult<()> {
log::trace!("in get_manifest_with_name(), Python:with_gil called (fullpath={:?}", fullpath.clone());
log::debug!("PythonPlugin uses python version={:?}", py.version_info());
let parent = fullpath.parent().unwrap().to_str().unwrap();
let _ = PyModule::from_code_bound(py, &format!(r#"
import sys
if not "{parent:}" in sys.path:
sys.path.append("{parent:}")
"#), "", "");
if let Some(paths) = pythonpaths {
paths.iter().for_each(|p| {
let path = if p.is_absolute() { p.clone() } else { fullpath.parent().unwrap().join(p) };
let path_str = path.to_str().unwrap();
let _ = PyModule::from_code_bound(py, &format!(r#"
import sys
if not "{path_str:}" in sys.path:
sys.path.append("{path_str:}")
"#), "", "");
});
}
Ok(())
})?;
Ok(())
}
fn get_manifest_with_name(&self, working_dir: Option<PathBuf>, symbol_name: &str) -> JuizResult<Value> {
let fullpath = working_dir.clone().unwrap_or(env!("CARGO_MANIFEST_DIR").into()).join(self.path.clone());
let pythonpaths = self.pythonpaths.clone();
match Python::with_gil(|py| -> PyResult<Value> {
log::trace!("in get_manifest_with_name(), Python:with_gil called (fullpath={:?}", fullpath.clone());
log::debug!("PythonPlugin uses python version={:?}", py.version_info());
let parent = fullpath.parent().unwrap().to_str().unwrap();
let _ = PyModule::from_code_bound(py, &format!(r#"
import sys
if not "{parent:}" in sys.path:
sys.path.append("{parent:}")
"#), "", "");
if let Some(paths) = pythonpaths {
paths.iter().for_each(|p| {
let path = if p.is_absolute() { p.clone() } else { fullpath.join(p) };
let path_str = path.to_str().unwrap();
let _ = PyModule::from_code_bound(py, &format!(r#"
import sys
if not "{path_str:}" in sys.path:
sys.path.append("{path_str:}")
"#), "", "");
});
}
let py_app = fs::read_to_string(fullpath.clone())?;
let module = PyModule::from_code_bound(py, &py_app, "", "")?;
let manifest_func: Py<PyAny> = module.getattr(symbol_name)?.into();
Ok(pydict_to_value(manifest_func.call0(py)?.extract::<&PyDict>(py)?)?) }) {
Ok(manifest) => { Ok(manifest) }
Err(e) => {
log::error!("get_manifest_with_name() failed. {e:}");
Err(anyhow::Error::from(e))
}
}
}
pub fn load_process_factory(&self, working_dir: Option<PathBuf>, symbol_name: &str, type_name_opt: Option<&str>) -> JuizResult<ProcessFactoryPtr> {
log::trace!("PythonPlugin({:?})::load_process_factory(symbol_name='{symbol_name}, type_name_opt={type_name_opt:?}') called", self.path);
self.init_path(working_dir.clone())?;
let type_name = match type_name_opt {
Some(v) => v,
None => self.path.file_stem().unwrap().to_str().unwrap()
};
let fullpath = working_dir.unwrap_or(env!("CARGO_MANIFEST_DIR").into()).join(self.path.clone());
let mut manifest = jvalue!({});
let py_app = fs::read_to_string(fullpath.clone()).unwrap();
let pyfunc2 = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
let module = PyModule::from_code_bound(py, &py_app.to_string(), fullpath.clone().to_str().unwrap(), "")?;
let proc_object = module.getattr(type_name)?.into_py(py); let manifest_object = proc_object.getattr(py, "manifest")?.into_py(py).call0(py)?;
manifest = pyany_to_value(manifest_object.extract::<&PyAny>(py)?)?;
let pyfunc = proc_object;
Ok(pyfunc.to_object(py))
})?;
let signature = get_python_function_signature(&pyfunc2)?;
let function = move |argument: CapsuleMap| -> JuizResult<Capsule> {
Python::with_gil(|py| {
python_process_call(py, &pyfunc2, PyTuple::new_bound(py, capsulemap_to_pytuple(py, &argument, &signature, 0)?))
}).or_else(|e| { Err(anyhow!(e)) })
};
process_factory_create_from_trait(manifest.try_into()?, function)
}
pub fn load_container_process_factory(&self, working_dir: Option<PathBuf>, symbol_name: &str, type_name_opt: Option<&str>) -> JuizResult<ContainerProcessFactoryPtr> {
log::trace!("PythonPlugin({:?})::load_container_process_factory(symbol_name='{symbol_name}') called", self.path);
self.init_path(working_dir.clone())?;
let type_name = match type_name_opt {
Some(v) => v,
None => self.path.file_stem().unwrap().to_str().unwrap()
};
let fullpath = working_dir.unwrap_or(env!("CARGO_MANIFEST_DIR").into()).join(self.path.clone());
let mut manifest = jvalue!({});
let py_app = fs::read_to_string(fullpath.clone()).unwrap();
let pyfunc2 = match Python::with_gil(|py| -> PyResult<Py<PyAny>> {
log::debug!("Python::with_gil({py_app}) for load_Container_process_factory({symbol_name}, {type_name_opt:?}), fullpath={fullpath:?}");
let module = PyModule::from_code_bound(py, &py_app.to_owned(), fullpath.clone().to_str().unwrap(), "")?;
let proc_object = module.getattr(type_name)?.into_py(py);
let manifest_object = proc_object.getattr(py, "manifest")?.into_py(py).call0(py)?;
manifest = pyany_to_value(manifest_object.extract::<&PyAny>(py)?)?;
let pyfunc = proc_object;
Ok(pyfunc.to_object(py))
}) {
Ok(v) => Ok(v),
Err(e) => {
Python::with_gil(|py| {
log::error!("Python::with_gil() failed. Error({e:?})");
if let Some(tb) = e.traceback_bound(py) {
log::error!("Traceback: {}", tb);
}
});
Err(e)
}
}?;
let signature = get_python_function_signature(&pyfunc2)?;
let function = move |container: &mut ContainerImpl<PythonContainerStruct>, argument: CapsuleMap| -> JuizResult<Capsule> {
Python::with_gil(|py| {
let start_index = 1;
let v = capsulemap_to_pytuple(py, &argument, &signature, start_index)?;
let elements = arg_to_pyargs(container, &v);
let return_value = python_process_call(py, &pyfunc2, PyTuple::new_bound(py, elements));
return_value
}).or_else(|e| { Err(anyhow!(e)) })
};
container_process_factory_create_from_trait(manifest.try_into()?, bind_container_function(function)).or_else(|e| {
log::error!("container_process_factory_create_from_trait() failed.");
Err(e)
})
}
pub fn load_container_factory(&self, working_dir: Option<PathBuf>, symbol_name: &str, type_name_opt: Option<&str>) -> JuizResult<ContainerFactoryPtr> {
log::trace!("PythonPlugin({:?})::load_container_factory(symbol_name='{symbol_name}') called", self.path);
let type_name = match type_name_opt {
Some(v) => v,
None => self.path.file_stem().unwrap().to_str().unwrap()
};
let fullpath = working_dir.unwrap_or(env!("CARGO_MANIFEST_DIR").into()).join(self.path.clone());
let mut manifest = jvalue!({});
let py_app = fs::read_to_string(fullpath.clone()).unwrap();
let pyfunc2 = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
let module = PyModule::from_code_bound(py, &py_app.to_string(), fullpath.clone().to_str().unwrap(), "")?;
let proc_object = module.getattr(type_name)?.into_py(py); let manifest_object = proc_object.getattr(py, "manifest")?.into_py(py).call0(py)?;
manifest = pyany_to_value(manifest_object.extract::<&PyAny>(py)?)?;
let pyfunc = proc_object;
Ok(pyfunc.to_object(py))
})?;
let signature = get_python_function_signature(&pyfunc2)?;
let constructor = move |cm: ContainerManifest, argument: CapsuleMap| -> JuizResult<ContainerPtr> {
let pyobj = Python::with_gil(|py| {
let start_index = 0;
let v = capsulemap_to_pytuple(py, &argument, &signature, start_index).unwrap();
pyfunc2.call1(py, PyTuple::new_bound(py, v))
})?;
Ok(ContainerPtr::new(ContainerImpl::new(cm, Box::new(PythonContainerStruct{
pyobj,
}))?))
};
container_factory_create(manifest.try_into()?, Arc::new(constructor))
}
pub fn load_component_manifest(&self, working_dir: Option<PathBuf>) -> JuizResult<ComponentManifest> {
log::trace!("load_component_manifest() called");
self.get_manifest_with_name(working_dir, "component_manifest")?.try_into()
}
}
fn arg_to_pyargs<'a>(c: &'a mut ContainerImpl<PythonContainerStruct>, arg: &'a Vec<Py<PyAny>> ) -> Vec<&'a Py<PyAny>> {
let mut vec_arg: Vec<&Py<PyAny>> = Vec::new();
vec_arg.push(&c.t.pyobj);
vec_arg.extend(arg.iter());
vec_arg
}
pub fn get_python_function_signature(func: &Py<PyAny>) -> PyResult<Value> {
Python::with_gil(|py| {
let py_app = r#"
from inspect import signature
def argument_info(func):
sig = signature(func)
sig_dict = []
for k, v in sig.parameters.items():
sig_dict.append( {
"key": k,
"kind": str(v.kind),
"name": v.name,
"annotation": str(v.annotation.__name__) if v.annotation is not v.empty else "empty",
"default": str(v.default) if v.default is not v.empty else "empty",
} )
return sig_dict
"#;
let module = PyModule::from_code_bound(py, &py_app.to_string(), "", "")?;
let value = module.getattr("argument_info")?.into_py(py).call1(py, PyTuple::new_bound(py,[func.clone_ref(py)]))?;
pyany_to_value(value.extract::<&PyAny>(py)?)
})
}
pub fn check_object_is_ndarray(_py: &Python, value: &PyAny) -> bool {
value.get_type().to_string() == "<class 'numpy.ndarray'>".to_owned()
}
fn function_argument_name_list(signature: &Value) -> JuizResult<Vec<String>> {
Ok(get_array(signature)?.iter().map(|v| { obj_get_str(v, "name").unwrap().to_owned() }).collect::<Vec<String>>())
}
pub fn capsulemap_to_pytuple<'a>(py: Python, value: &'a CapsuleMap, signature: &Value, start_index: usize) -> JuizResult<Vec<Py<PyAny>>> {
let arg_name_list = function_argument_name_list(signature)?;
let mut index = start_index;
let mut vec: Vec<Py<PyAny>> = Vec::new();
for (i, arg_name) in arg_name_list.iter().enumerate() {
if i < index { continue; };
let v = value.get(arg_name.as_str())?;
vec.push(capsuleptr_to_pyany(py, &v));
index += 1;
}
return Ok(vec);
}
#[allow(unused)]
pub fn value_to_pytuple<'a>(py: Python, value: &'a Value) -> Vec<Py<PyAny>> {
vec!(value_to_pyany(py, value))
}
fn valuearray_to_pyany(py: Python, arr: &Vec<Value>) -> Py<PyAny> {
arr.iter().map(|v| { value_to_pyany(py, v) }).collect::<Vec<Py<PyAny>>>().into_py(py)
}
fn valueobj_to_pyany(py: Python, map: &Map<String, Value>) -> Py<PyAny> {
map.iter().map(|(k, v)| { (k.clone(), value_to_pyany(py, v)) }).collect::<HashMap<String, Py<PyAny>>>().into_py(py)
}
fn value_to_pyany(py: Python, value: &Value) -> Py<PyAny> {
if value.is_i64() {
return (value.as_i64().unwrap()).into_py(py);
} else if value.is_f64() {
return (value.as_f64().unwrap()).into_py(py);
} else if value.is_boolean() {
return (value.as_bool().unwrap()).into_py(py);
} else if value.is_string() {
return (value.as_str().unwrap()).into_py(py);
} else if value.is_null() {
return (value.as_null().unwrap()).into_py(py);
} else if value.is_u64() {
return (value.as_u64().unwrap()).into_py(py);
} else if value.is_array() {
return valuearray_to_pyany(py, value.as_array().unwrap());
} else if value.is_object() {
return valueobj_to_pyany(py, value.as_object().unwrap());
}
todo!()
}
fn capsuleptr_to_pyany(py: Python, value: &CapsulePtr) -> Py<PyAny> {
if value.is_value().unwrap() {
return value.lock_as_value(|v| {
value_to_pyany(py, v)
}).unwrap();
} else if value.is_image().unwrap() {
return value.lock_as_image(|img| {
image_to_pyany(py, img)
}).unwrap()
}
todo!("capsuleptr_to_pyany failed. CapsulePtr type is not available yet. Value is {value:?}")
}
fn image_to_pyany(py: Python, image: &DynamicImage) -> Py<PyAny> {
let mut buffer = BufWriter::new(Cursor::new(Vec::new()));
image.write_to(&mut buffer, ImageFormat::Bmp).unwrap();
let w = image.width();
let h = image.height();
unsafe {
let py_app = r"
import io
from PIL import Image
def image_from_bytes(w, h, image_data):
# print('image_data', image_data, type(image_data))
return Image.frombytes('RGB', (w,h), image_data)
# return Image.open(io.BytesIO(image_data))
";
let b = PyBytes::bound_from_ptr(py, image.as_bytes().as_ptr(), image.as_bytes().len());
let module = PyModule::from_code_bound(py, &py_app.to_string(), "", "").unwrap();
module.getattr("image_from_bytes").unwrap().into_py(py).call1(py, PyTuple::new_bound(py, [w.into_py(py), h.into_py(py), b.into()])).unwrap()
}
}
pub fn python_process_call(py: Python, entry_point: &Py<PyAny>, pytuple: pyo3::Bound<PyTuple>) -> JuizResult<Capsule> {
match entry_point.call1(py, pytuple) {
Ok(v) => {
let object = v.extract::<&PyAny>(py)?;
Ok(if check_object_is_ndarray(&py, object) {
todo!()
} else {
pyany_to_capsule(object)?.into()
})
},
Err(e) => {
let trace_str = e.traceback_bound(py).and_then(|trace| { Some(format!("{:}", trace.format().unwrap())) }).or(Some("".to_owned())).unwrap();
log::error!("Error in Python::with_gil for ContainerProcess.call(). Error({e:}). Traceback: {trace_str:}");
Err(anyhow!(e))
},
}
}
pub fn pyany_to_value(value: &PyAny) -> PyResult<Value> {
if value.is_instance_of::<PyString>() {
Ok(Value::from(value.extract::<String>()?))
} else if value.is_instance_of::<PyFloat>() {
Ok(Value::from(value.extract::<f64>()?))
} else if value.is_instance_of::<PyInt>() {
Ok(Value::from(value.extract::<i64>()?))
} else if value.is_instance_of::<PyList>() {
pylist_to_value(value.extract::<&PyList>()?)
} else if value.is_instance_of::<PyTuple>() {
pytuple_to_value(value.extract::<&PyTuple>()?)
} else if value.is_instance_of::<PySet>() {
pyset_to_value(value.extract::<&PySet>()?)
} else if value.is_instance_of::<PyDict>() {
pydict_to_value(value.extract::<&PyDict>()?)
} else if value.is_instance_of::<PyNone>() {
Ok(Value::Null)
} else {
let pytype = value.get_type();
if pytype.to_string() == "PIL.Image.Iage" {
todo!()
} else {
log::error!("Error for pyany_to_value. Error({pytype:?} is not available)");
todo!("PythonのProcessの値としてjuizが対応していないタイプが渡されました。")
}
}
}
pub fn pyany_to_capsule(value: &PyAny) -> PyResult<Capsule> {
if value.is_instance_of::<PyString>() {
Ok(Value::from(value.extract::<String>()?).into())
} else if value.is_instance_of::<PyFloat>() {
Ok(Value::from(value.extract::<f64>()?).into())
} else if value.is_instance_of::<PyInt>() {
Ok(Value::from(value.extract::<i64>()?).into())
} else if value.is_instance_of::<PyList>() {
pylist_to_capsule(value.extract::<&PyList>()?)
} else if value.is_instance_of::<PyTuple>() {
pytuple_to_capsule(value.extract::<&PyTuple>()?)
} else if value.is_instance_of::<PySet>() {
pyset_to_capsule(value.extract::<&PySet>()?)
} else if value.is_instance_of::<PyDict>() {
pydict_to_capsule(value.extract::<&PyDict>()?)
} else if value.is_instance_of::<PyNone>() {
Ok(Value::Null.into())
} else {
let pytype = value.get_type();
if pytype.to_string() == "<class 'PIL.Image.Image'>" {
let image = Python::with_gil(|py| -> PyResult<DynamicImage> {
let app_code = r"
import io
def convert_img(img):
output = io.BytesIO()
img.save(output, format='PNG')
return output.getvalue() # Hex Data
";
let module = PyModule::from_code_bound(py, &app_code.to_owned(), "", "")?;
let byte_output = module.getattr("convert_img")?.into_py(py).call1(py, PyTuple::new_bound(py, vec![value]))?.extract::<&PyBytes>(py)?;
match image::load_from_memory_with_format(byte_output.as_bytes(), ImageFormat::Png) {
Ok(i) => Ok(i),
Err(e) => {
log::error!("Image load from memmory error. Error: {e:?}");
panic!()
}
}
})?;
Ok(image.into())
} else {
log::error!("Error for pyany_to_capsule. Error({pytype:?} is not available)");
todo!("PythonのProcessの値としてjuizが対応していないタイプが渡されました。")
}
}
}
fn pylist_to_value(pylist: &PyList) -> PyResult<Value> {
let mut vec: Vec<Value> = Vec::new();
for value in pylist.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
fn pytuple_to_value(pytuple: &PyTuple) -> PyResult<Value> {
let mut vec: Vec<Value> = Vec::new();
for value in pytuple.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
fn pyset_to_value(pyset: &PySet) -> PyResult<Value> {
let mut vec: Vec<Value> = Vec::new();
for value in pyset.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
pub fn pydict_to_value(pydict: &PyDict) -> PyResult<Value> {
let mut map: HashMap<String, Value> = HashMap::new();
for (key, value) in pydict.into_iter() {
map.insert(key.extract::<String>()?, pyany_to_value(value)?);
}
Ok(jvalue!(map))
}
fn pylist_to_capsule(pylist: &PyList) -> PyResult<Capsule> {
let mut vec: Vec<Value> = Vec::new();
for value in pylist.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
fn pytuple_to_capsule(pytuple: &PyTuple) -> PyResult<Capsule> {
let mut vec: Vec<Value> = Vec::new();
for value in pytuple.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
fn pyset_to_capsule(pyset: &PySet) -> PyResult<Capsule> {
let mut vec: Vec<Value> = Vec::new();
for value in pyset.into_iter() {
vec.push(pyany_to_value(value)?);
}
Ok(vec.into())
}
pub fn pydict_to_capsule(pydict: &PyDict) -> PyResult<Capsule> {
let mut map: HashMap<String, Value> = HashMap::new();
for (key, value) in pydict.into_iter() {
map.insert(key.extract::<String>()?, pyany_to_value(value)?);
}
Ok(jvalue!(map).into())
}