1use crate::run::run_python_code;
2use crate::PythonBlock;
3use pyo3::{
4 prelude::*,
5 types::{PyCFunction, PyDict},
6 FromPyObject, IntoPyObject, Py, PyResult, Python,
7};
8
9pub struct Context {
42 pub(crate) globals: Py<PyDict>,
43}
44
45impl Context {
46 #[allow(clippy::new_without_default)]
50 pub fn new() -> Self {
51 Python::with_gil(Self::new_with_gil)
52 }
53
54 pub(crate) fn new_with_gil(py: Python) -> Self {
55 match Self::try_new(py) {
56 Ok(x) => x,
57 Err(error) => {
58 error.print(py);
59 panic!("failed to create Python context");
60 }
61 }
62 }
63
64 fn try_new(py: Python) -> PyResult<Self> {
65 Ok(Self {
66 globals: py.import("__main__")?.dict().copy()?.into(),
67 })
68 }
69
70 pub fn globals<'p>(&self) -> &Py<PyDict> {
72 &self.globals
73 }
74
75 pub fn get<T: for<'p> FromPyObject<'p>>(&self, name: &str) -> T {
79 Python::with_gil(|py| match self.globals.bind(py).get_item(name) {
80 Err(_) | Ok(None) => panic!("Python context does not contain a variable named `{}`", name),
81 Ok(Some(value)) => match FromPyObject::extract_bound(&value) {
82 Ok(value) => value,
83 Err(e) => {
84 e.print(py);
85 panic!("Unable to convert `{}` to `{}`", name, std::any::type_name::<T>());
86 }
87 },
88 })
89 }
90
91 pub fn set<T: for<'p> IntoPyObject<'p>>(&self, name: &str, value: T) {
95 Python::with_gil(|py| match self.globals().bind(py).set_item(name, value) {
96 Ok(()) => (),
97 Err(e) => {
98 e.print(py);
99 panic!("Unable to set `{}` from a `{}`", name, std::any::type_name::<T>());
100 }
101 })
102 }
103
104 pub fn add_wrapped(&self, wrapper: &impl Fn(Python) -> PyResult<Bound<'_, PyCFunction>>) {
128 Python::with_gil(|py| {
129 let obj = wrapper(py).unwrap();
130 let name = obj.getattr("__name__").expect("Missing __name__");
131 if let Err(e) = self.globals().bind(py).set_item(name, obj) {
132 e.print(py);
133 panic!("Unable to add wrapped function");
134 }
135 })
136 }
137
138 pub fn run<F: FnOnce(&Bound<PyDict>)>(&self, code: PythonBlock<F>) {
153 Python::with_gil(|py| self.run_with_gil(py, code));
154 }
155
156 pub(crate) fn run_with_gil<F: FnOnce(&Bound<PyDict>)>(&self, py: Python<'_>, code: PythonBlock<F>) {
157 (code.set_variables)(self.globals().bind(py));
158 match run_python_code(py, self, code.bytecode) {
159 Ok(_) => (),
160 Err(e) => {
161 e.print(py);
162 panic!("{}", "python!{...} failed to execute");
163 }
164 }
165 }
166}