use crate::conversion::IntoPyObject;
#[cfg(any(doc, not(Py_3_10)))]
use crate::err::PyErr;
use crate::err::{self, PyResult};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::gil::{GILGuard, SuspendGIL};
use crate::impl_::not_send::NotSend;
use crate::py_result_ext::PyResultExt;
use crate::types::any::PyAnyMethods;
use crate::types::{
PyAny, PyDict, PyEllipsis, PyModule, PyNone, PyNotImplemented, PyString, PyType,
};
use crate::version::PythonVersionInfo;
#[allow(deprecated)]
use crate::IntoPy;
use crate::{ffi, Bound, Py, PyObject, PyTypeInfo};
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::c_int;
#[cfg_attr(docsrs, doc(cfg(all())))] #[cfg(not(feature = "nightly"))]
pub unsafe trait Ungil {}
#[cfg_attr(docsrs, doc(cfg(all())))] #[cfg(not(feature = "nightly"))]
unsafe impl<T: Send> Ungil for T {}
#[cfg(feature = "nightly")]
mod nightly {
macro_rules! define {
($($tt:tt)*) => { $($tt)* }
}
define! {
pub unsafe auto trait Ungil {}
}
impl !Ungil for crate::Python<'_> {}
impl !Ungil for crate::PyAny {}
impl<T> !Ungil for crate::PyRef<'_, T> {}
impl<T> !Ungil for crate::PyRefMut<'_, T> {}
impl !Ungil for crate::ffi::PyObject {}
impl !Ungil for crate::ffi::PyLongObject {}
impl !Ungil for crate::ffi::PyThreadState {}
impl !Ungil for crate::ffi::PyInterpreterState {}
impl !Ungil for crate::ffi::PyWeakReference {}
impl !Ungil for crate::ffi::PyFrameObject {}
impl !Ungil for crate::ffi::PyCodeObject {}
#[cfg(not(Py_LIMITED_API))]
impl !Ungil for crate::ffi::PyDictKeysObject {}
#[cfg(not(any(Py_LIMITED_API, Py_3_10)))]
impl !Ungil for crate::ffi::PyArena {}
}
#[cfg(feature = "nightly")]
pub use nightly::Ungil;
#[derive(Copy, Clone)]
pub struct Python<'py>(PhantomData<(&'py GILGuard, NotSend)>);
impl Python<'_> {
#[cfg_attr(
not(any(PyPy, GraalPy)),
doc = "[`prepare_freethreaded_python`](crate::prepare_freethreaded_python)"
)]
#[cfg_attr(PyPy, doc = "`prepare_freethreaded_python`")]
#[inline]
pub fn with_gil<F, R>(f: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> R,
{
let guard = GILGuard::acquire();
f(guard.python())
}
#[cfg_attr(
all(Py_3_8, not(PyPy)),
doc = "[`_Py_InitializeMain`](crate::ffi::_Py_InitializeMain)"
)]
#[cfg_attr(any(not(Py_3_8), PyPy), doc = "`_Py_InitializeMain`")]
#[inline]
pub unsafe fn with_gil_unchecked<F, R>(f: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> R,
{
let guard = GILGuard::acquire_unchecked();
f(guard.python())
}
}
impl<'py> Python<'py> {
pub fn allow_threads<T, F>(self, f: F) -> T
where
F: Ungil + FnOnce() -> T,
T: Ungil,
{
let _guard = unsafe { SuspendGIL::new() };
f()
}
pub fn eval(
self,
code: &CStr,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
self.run_code(code, ffi::Py_eval_input, globals, locals)
}
#[deprecated(since = "0.23.0", note = "renamed to `Python::eval`")]
#[track_caller]
#[inline]
pub fn eval_bound(
self,
code: &str,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
let code = CString::new(code)?;
self.eval(&code, globals, locals)
}
pub fn run(
self,
code: &CStr,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<()> {
let res = self.run_code(code, ffi::Py_file_input, globals, locals);
res.map(|obj| {
debug_assert!(obj.is_none());
})
}
#[deprecated(since = "0.23.0", note = "renamed to `Python::run`")]
#[track_caller]
#[inline]
pub fn run_bound(
self,
code: &str,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<()> {
let code = CString::new(code)?;
self.run(&code, globals, locals)
}
fn run_code(
self,
code: &CStr,
start: c_int,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
let mptr = unsafe {
ffi::compat::PyImport_AddModuleRef(ffi::c_str!("__main__").as_ptr())
.assume_owned_or_err(self)?
};
let attr = mptr.getattr(crate::intern!(self, "__dict__"))?;
let globals = match globals {
Some(globals) => globals,
None => attr.downcast::<PyDict>()?,
};
let locals = locals.unwrap_or(globals);
#[cfg(not(Py_3_10))]
{
let builtins_s = crate::intern!(self, "__builtins__").as_ptr();
let has_builtins = unsafe { ffi::PyDict_Contains(globals.as_ptr(), builtins_s) };
if has_builtins == -1 {
return Err(PyErr::fetch(self));
}
if has_builtins == 0 {
let builtins = unsafe { ffi::PyEval_GetBuiltins() };
if unsafe { ffi::PyDict_SetItem(globals.as_ptr(), builtins_s, builtins) } == -1 {
return Err(PyErr::fetch(self));
}
}
}
let code_obj = unsafe {
ffi::Py_CompileString(code.as_ptr(), ffi::c_str!("<string>").as_ptr(), start)
.assume_owned_or_err(self)?
};
unsafe {
ffi::PyEval_EvalCode(code_obj.as_ptr(), globals.as_ptr(), locals.as_ptr())
.assume_owned_or_err(self)
.downcast_into_unchecked()
}
}
#[inline]
pub fn get_type<T>(self) -> Bound<'py, PyType>
where
T: PyTypeInfo,
{
T::type_object(self)
}
#[deprecated(since = "0.23.0", note = "renamed to `Python::get_type`")]
#[track_caller]
#[inline]
pub fn get_type_bound<T>(self) -> Bound<'py, PyType>
where
T: PyTypeInfo,
{
self.get_type::<T>()
}
pub fn import<N>(self, name: N) -> PyResult<Bound<'py, PyModule>>
where
N: IntoPyObject<'py, Target = PyString>,
{
PyModule::import(self, name)
}
#[deprecated(since = "0.23.0", note = "renamed to `Python::import`")]
#[allow(deprecated)]
#[track_caller]
#[inline]
pub fn import_bound<N>(self, name: N) -> PyResult<Bound<'py, PyModule>>
where
N: IntoPy<Py<PyString>>,
{
self.import(name.into_py(self))
}
#[allow(non_snake_case)] #[inline]
pub fn None(self) -> PyObject {
PyNone::get(self).to_owned().into_any().unbind()
}
#[allow(non_snake_case)] #[inline]
pub fn Ellipsis(self) -> PyObject {
PyEllipsis::get(self).to_owned().into_any().unbind()
}
#[allow(non_snake_case)] #[inline]
pub fn NotImplemented(self) -> PyObject {
PyNotImplemented::get(self).to_owned().into_any().unbind()
}
pub fn version(self) -> &'py str {
unsafe {
CStr::from_ptr(ffi::Py_GetVersion())
.to_str()
.expect("Python version string not UTF-8")
}
}
pub fn version_info(self) -> PythonVersionInfo<'py> {
let version_str = self.version();
let version_number_str = version_str.split(' ').next().unwrap_or(version_str);
PythonVersionInfo::from_str(version_number_str).unwrap()
}
pub fn check_signals(self) -> PyResult<()> {
err::error_on_minusone(self, unsafe { ffi::PyErr_CheckSignals() })
}
}
impl<'unbound> Python<'unbound> {
#[inline]
pub unsafe fn assume_gil_acquired() -> Python<'unbound> {
Python(PhantomData)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{IntoPyDict, PyList};
#[test]
fn test_eval() {
Python::with_gil(|py| {
let v: i32 = py
.eval(ffi::c_str!("min(1, 2)"), None, None)
.map_err(|e| e.display(py))
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 1);
let d = [("foo", 13)].into_py_dict(py).unwrap();
let v: i32 = py
.eval(ffi::c_str!("foo + 29"), Some(&d), None)
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 42);
let v: i32 = py
.eval(ffi::c_str!("foo + 29"), None, Some(&d))
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 42);
let v: i32 = py
.eval(ffi::c_str!("min(foo, 2)"), None, Some(&d))
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 2);
});
}
#[test]
#[cfg(not(target_arch = "wasm32"))] fn test_allow_threads_releases_and_acquires_gil() {
Python::with_gil(|py| {
let b = std::sync::Arc::new(std::sync::Barrier::new(2));
let b2 = b.clone();
std::thread::spawn(move || Python::with_gil(|_| b2.wait()));
py.allow_threads(|| {
b.wait();
});
unsafe {
let tstate = ffi::PyEval_SaveThread();
ffi::PyEval_RestoreThread(tstate);
}
});
}
#[test]
fn test_allow_threads_panics_safely() {
Python::with_gil(|py| {
let result = std::panic::catch_unwind(|| unsafe {
let py = Python::assume_gil_acquired();
py.allow_threads(|| {
panic!("There was a panic!");
});
});
assert!(result.is_err());
let list = PyList::new(py, [1, 2, 3, 4]).unwrap();
assert_eq!(list.extract::<Vec<i32>>().unwrap(), vec![1, 2, 3, 4]);
});
}
#[cfg(not(pyo3_disable_reference_pool))]
#[test]
fn test_allow_threads_pass_stuff_in() {
let list = Python::with_gil(|py| PyList::new(py, vec!["foo", "bar"]).unwrap().unbind());
let mut v = vec![1, 2, 3];
let a = std::sync::Arc::new(String::from("foo"));
Python::with_gil(|py| {
py.allow_threads(|| {
drop((list, &mut v, a));
});
});
}
#[test]
#[cfg(not(Py_LIMITED_API))]
fn test_acquire_gil() {
const GIL_NOT_HELD: c_int = 0;
const GIL_HELD: c_int = 1;
#[cfg(not(any(PyPy, GraalPy)))]
crate::prepare_freethreaded_python();
let state = unsafe { crate::ffi::PyGILState_Check() };
assert_eq!(state, GIL_NOT_HELD);
Python::with_gil(|_| {
let state = unsafe { crate::ffi::PyGILState_Check() };
assert_eq!(state, GIL_HELD);
});
let state = unsafe { crate::ffi::PyGILState_Check() };
assert_eq!(state, GIL_NOT_HELD);
}
#[test]
fn test_ellipsis() {
Python::with_gil(|py| {
assert_eq!(py.Ellipsis().to_string(), "Ellipsis");
let v = py
.eval(ffi::c_str!("..."), None, None)
.map_err(|e| e.display(py))
.unwrap();
assert!(v.eq(py.Ellipsis()).unwrap());
});
}
#[test]
fn test_py_run_inserts_globals() {
use crate::types::dict::PyDictMethods;
Python::with_gil(|py| {
let namespace = PyDict::new(py);
py.run(
ffi::c_str!("class Foo: pass\na = int(3)"),
Some(&namespace),
Some(&namespace),
)
.unwrap();
assert!(matches!(namespace.get_item("Foo"), Ok(Some(..))));
assert!(matches!(namespace.get_item("a"), Ok(Some(..))));
#[cfg(not(Py_3_10))]
assert!(matches!(namespace.get_item("__builtins__"), Ok(Some(..))));
})
}
}