blob: a75dddfdb40fe25e0ede0e80fc0fff9b0b2ef69b [file] [log] [blame]
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use std::sync::atomic::Ordering;
use rustpython::{
self,
vm::{
self, identifier,
stdlib::{atexit, sys},
PyObjectRef,
},
InterpreterConfig,
};
/// An ergonomic wrapper for a Python interpreter.
///
/// This type is intentionally not exported outside of the crate for
/// now. Ideally all details of python code invocation will be private
/// to the crate.
pub(crate) struct Py {
interpreter: vm::Interpreter,
}
impl Py {
/// Create a new Python interpreter.
pub fn new() -> Py {
let mut config = InterpreterConfig::new();
config = config.init_stdlib();
Py {
interpreter: config.interpreter(),
}
}
/// Run Python code inside the interpreter.
///
/// `init_scope` is run before the code is executed giving the caller the
/// ability to set up the scope before code execution.
pub fn run(
&mut self,
init_scope: impl FnOnce(&vm::VirtualMachine, &mut vm::scope::Scope) -> vm::PyResult<()>,
code: &str,
) -> vm::PyResult<PyObjectRef> {
self.interpreter.enter(|vm| -> vm::PyResult<PyObjectRef> {
let mut scope = vm.new_scope_with_builtins();
vm.import("site", None, 0)?;
init_scope(vm, &mut scope)?;
let result = vm
.compile(code, vm::compiler::Mode::Single, "<qg>".to_owned())
.map_err(|err| vm.new_syntax_error(&err))
.and_then(|code_obj| vm.run_code_obj(code_obj, scope.clone()));
Self::flush_std(vm);
result
})
}
fn flush_std(vm: &vm::VirtualMachine) {
if let Ok(stdout) = sys::get_stdout(vm) {
let _res = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ());
}
if let Ok(stderr) = sys::get_stderr(vm) {
let _res = vm.call_method(&stderr, identifier!(vm, flush).as_str(), ());
}
}
}
impl Drop for Py {
fn drop(&mut self) {
self.interpreter.enter(|vm| {
atexit::_run_exitfuncs(vm);
vm.state.finalizing.store(true, Ordering::Release);
Self::flush_std(vm);
});
}
}
#[cfg(test)]
mod test {
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn function_callback() {
// The Python VM is treated as another thread context so types must
// implement Send and Sync.
let lines = Arc::new(Mutex::new(Vec::new()));
// A clone for the python side
let py_lines = lines.clone();
let mut py = Py::new();
let code = r#"
qg_test_func("line1")
qg_test_func("line2")
"#;
py.run(
// Scope initialization function.
|vm, scope| {
// Add a `rust_print` function to the scope before running the code.
scope.globals.set_item(
"qg_test_func",
vm.ctx
.new_function("qg_test_func", move |s: String| {
println!("qg_test_func: {}", s);
py_lines.lock().unwrap().push(s);
})
.into(),
vm,
)?;
Ok(())
},
code,
)
.unwrap();
assert_eq!(
*lines.lock().unwrap(),
vec!["line1".to_string(), "line2".to_string()]
);
}
}