use crate::run::run_python_code;
use crate::PythonBlock;
use pyo3::{types::PyDict, AsPyRef, FromPyObject, Py, PyObject, PyResult, Python, ToPyObject};
pub struct Context {
pub(crate) globals: Py<PyDict>,
}
impl Context {
pub fn new() -> Self {
Self::new_with_gil(Python::acquire_gil().python())
}
pub fn new_with_gil(py: Python) -> Self {
match Self::try_new(py) {
Ok(x) => x,
Err(error) => {
error.print(py);
panic!("failed to create Python context");
}
}
}
fn try_new(py: Python) -> PyResult<Self> {
Ok(Self {
globals: py.import("__main__")?.dict().copy()?.into(),
})
}
pub fn globals<'p>(&'p self, py: Python<'p>) -> &'p PyDict {
self.globals.as_ref(py)
}
pub fn get<T: for<'p> FromPyObject<'p>>(&self, name: &str) -> T {
self.get_with_gil(Python::acquire_gil().python(), name)
}
pub fn get_with_gil<'p, T: FromPyObject<'p>>(&'p self, py: Python<'p>, name: &str) -> T {
match self.globals(py).get_item(name) {
None => panic!("Python context does not contain a variable named `{}`", name),
Some(value) => match FromPyObject::extract(value) {
Ok(value) => value,
Err(e) => {
e.print(py);
panic!("Unable to convert `{}` to `{}`", name, std::any::type_name::<T>());
}
},
}
}
pub fn set<T: ToPyObject>(&self, name: &str, value: T) {
self.set_with_gil(Python::acquire_gil().python(), name, value)
}
pub fn set_with_gil<'p, T: ToPyObject>(&self, py: Python<'p>, name: &str, value: T) {
match self.globals(py).set_item(name, value) {
Ok(()) => (),
Err(e) => {
e.print(py);
panic!("Unable to set `{}` from a `{}`", name, std::any::type_name::<T>());
}
}
}
pub fn add_wrapped(&self, wrapper: &impl Fn(Python) -> PyObject) {
self.add_wrapped_with_gil(Python::acquire_gil().python(), wrapper);
}
pub fn add_wrapped_with_gil<'p>(&self, py: Python<'p>, wrapper: &impl Fn(Python) -> PyObject) {
let obj = wrapper(py);
let name = obj.getattr(py, "__name__").expect("Missing __name__");
self.set_with_gil(py, name.extract(py).unwrap(), obj)
}
pub fn run<F: FnOnce(&PyDict)>(&self, code: PythonBlock<F>) {
self.run_with_gil(Python::acquire_gil().python(), code);
}
pub fn run_with_gil<'p, F: FnOnce(&PyDict)>(&self, py: Python<'p>, code: PythonBlock<F>) {
(code.set_variables)(self.globals(py));
match run_python_code(py, self, code.bytecode) {
Ok(_) => (),
Err(e) => {
e.print(py);
panic!("python!{...} failed to execute");
}
}
}
}