use anyhow::{anyhow, Result};
use pyo3::types::{PyDict, PyModule};
use pyo3::{pyfunction, wrap_pyfunction, PyObject, PyResult, Python};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug)]
pub struct PythonGIL {
_python: Python<'static>,
}
#[derive(Debug)]
pub struct PythonRuntime {
gil: Arc<PythonGIL>,
amber_api: Arc<AmberAPI>,
context_pool: Arc<RwLock<Vec<PythonContext>>>,
}
#[derive(Debug)]
struct PythonContext {
py: Python<'static>,
globals: PyObject,
locals: PyObject,
}
#[derive(Debug)]
pub struct AmberAPI {
runtime: Arc<dyn AmberRuntimeInterface>,
}
pub trait AmberRuntimeInterface: Send + Sync {
fn execute_script(&self, script: &str) -> Result<String>;
fn get_variable(&self, name: &str) -> Result<String>;
fn set_variable(&self, name: &str, value: &str) -> Result<()>;
}
impl PythonGIL {
pub fn new() -> Result<Self> {
Python::with_gil(|py| Ok(PythonGIL { _python: py }))
}
}
impl PythonRuntime {
pub fn new(amber_api: Arc<AmberAPI>) -> Result<Self> {
let gil: _ = Arc::new(PythonGIL::new()?);
let context_pool: _ = Arc::new(RwLock::new(Vec::new()));
Ok(PythonRuntime {
gil,
amber_api,
context_pool,
})
}
async fn get_context(&self) -> Result<PythonContext> {
let mut pool = self.context_pool.write().await;
if let Some(context) = pool.pop() {
Ok(context)
} else {
Python::with_gil(|py| {
let globals: _ = PyDict::new(py).into();
let locals: _ = PyDict::new(py).into();
Ok(PythonContext {
py,
globals,
locals,
})
})
}
}
async fn return_context(&self, context: PythonContext) {
let mut pool = self.context_pool.write().await;
pool.push(context);
}
pub async fn execute_python(&self, code: &str) -> Result<String> {
let context: _ = self.get_context().await?;
let result: _ = Python::with_gil(|py| {
let result: _ = py.run(
code,
Some(context.globals.clone()),
Some(context.locals.clone()),
);
match result {
Ok(_) => {
let value: _ = context.globals.get_item(py, "_");
match value {
Ok(Some(val)) => Ok(val.to_string()),
Ok(None) => Ok("None".to_string()),
Err(e) => Ok(format!("Error: {}", e)),
}
}
Err(e) => Err(anyhow!("Python execution error: {}", e)),
}
});
self.return_context(context).await;
result
}
pub async fn call_python_function(
&self,
module: &str,
func: &str,
args: &[String],
) -> Result<String> {
let code: _ = format!(
"import {}\nresult = {}({})",
module,
func,
args.iter().map(|a| format!("'{}'", a)).collect::<Vec<_>().join(", ")
);
self.execute_python(&code).await
}
pub async fn execute_with_amber_api(&self, code: &str) -> Result<String> {
let context: _ = self.get_context().await?;
let result: _ = Python::with_gil(|py| {
let amber_module: _ = PyModule::create(py, "amber_runtime")?;
amber_module.add_function(wrap_pyfunction!(amber_get_variable, amber_module)?)?;
amber_module.add_function(wrap_pyfunction!(amber_set_variable, amber_module)?)?;
amber_module.add_function(wrap_pyfunction!(amber_execute, amber_module)?)?;
let amber_api_obj: _ = pyo3::types::PyModule::from_object(py, amber_module)?;
context.globals.set_item(py, "amber", amber_api_obj)?;
let result: _ = py.run(
code,
Some(context.globals.clone()),
Some(context.locals.clone()),
);
match result {
Ok(_) => {
let value: _ = context.globals.get_item(py, "_");
match value {
Ok(Some(val)) => Ok(val.to_string()),
Ok(None) => Ok("None".to_string()),
Err(e) => Ok(format!("Error: {}", e)),
}
}
Err(e) => Err(anyhow!("Python execution error: {}", e)),
}
});
self.return_context(context).await;
result
}
}
#[pyfunction]
fn amber_get_variable(name: &str) -> PyResult<String> {
Ok(format!("Value of {} from Amber", name))
}
#[pyfunction]
fn amber_set_variable(name: &str, value: &str) -> PyResult<()> {
println!("Setting Amber variable {} = {}", name, value);
Ok(())
}
#[pyfunction]
fn amber_execute(script: &str) -> PyResult<String> {
Ok(format!("Executed in Amber: {}", script))
}
#[cfg(test)]
mod tests {
use super::*;
fn amber_api() -> Arc<AmberAPI> {
Arc::new(AmberAPI {
runtime: Arc::new(MockAmberRuntime),
})
}
#[tokio::test]
async fn test_python_basic_execution() {
let runtime: _ = PythonRuntime::new(amber_api()).unwrap();
let result: _ = runtime.execute_python("print('Hello from Python!')").await;
assert!(result.is_ok());
let result: _ = runtime.execute_python("2 + 2").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "4");
}
#[tokio::test]
async fn test_python_function_call() {
let runtime: _ = PythonRuntime::new(amber_api()).unwrap();
runtime
.execute_python("def test_func(x, y): return x + y")
.await
.unwrap();
let result: _ = runtime
.call_python_function("__main__", "test_func", &["3".to_string(), "4".to_string()])
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_python_amber_api() {
let runtime: _ = PythonRuntime::new(amber_api()).unwrap();
let code: _ = r#"
amber.get_variable("test_var")
"#;
let result: _ = runtime.execute_with_amber_api(code).await;
assert!(result.is_ok());
}
struct MockAmberRuntime;
impl AmberRuntimeInterface for MockAmberRuntime {
fn execute_script(&self, script: &str) -> Result<String> {
Ok(format!("Executed: {}", script))
}
fn get_variable(&self, name: &str) -> Result<String> {
Ok(format!("value_of_{}", name))
}
fn set_variable(&self, name: &str, value: &str) -> Result<()> {
Ok(())
}
}
}