use crate::err::{self, PyErr, 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;
use crate::{ffi, Bound, IntoPy, Py, PyObject, PyTypeInfo};
#[allow(deprecated)]
#[cfg(feature = "gil-refs")]
use crate::{gil::GILPool, FromPyPointer, PyNativeType};
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 {}
#[allow(deprecated)]
#[cfg(feature = "gil-refs")]
impl<T> !Ungil for crate::PyCell<T> {}
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()
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`Python::eval` will be replaced by `Python::eval_bound` in a future PyO3 version"
)]
pub fn eval(
self,
code: &str,
globals: Option<&'py PyDict>,
locals: Option<&'py PyDict>,
) -> PyResult<&'py PyAny> {
self.eval_bound(
code,
globals.map(PyNativeType::as_borrowed).as_deref(),
locals.map(PyNativeType::as_borrowed).as_deref(),
)
.map(Bound::into_gil_ref)
}
pub fn eval_bound(
self,
code: &str,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
self.run_code(code, ffi::Py_eval_input, globals, locals)
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`Python::run` will be replaced by `Python::run_bound` in a future PyO3 version"
)]
pub fn run(
self,
code: &str,
globals: Option<&PyDict>,
locals: Option<&PyDict>,
) -> PyResult<()> {
self.run_bound(
code,
globals.map(PyNativeType::as_borrowed).as_deref(),
locals.map(PyNativeType::as_borrowed).as_deref(),
)
}
pub fn run_bound(
self,
code: &str,
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());
})
}
fn run_code(
self,
code: &str,
start: c_int,
globals: Option<&Bound<'py, PyDict>>,
locals: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
let code = CString::new(code)?;
unsafe {
let mptr = ffi::PyImport_AddModule(ffi::c_str!("__main__").as_ptr());
if mptr.is_null() {
return Err(PyErr::fetch(self));
}
let globals = globals
.map(|dict| dict.as_ptr())
.unwrap_or_else(|| ffi::PyModule_GetDict(mptr));
let locals = locals.map(|dict| dict.as_ptr()).unwrap_or(globals);
let builtins_s = crate::intern!(self, "__builtins__").as_ptr();
let has_builtins = ffi::PyDict_Contains(globals, builtins_s);
if has_builtins == -1 {
return Err(PyErr::fetch(self));
}
if has_builtins == 0 {
let builtins = ffi::PyEval_GetBuiltins();
if ffi::PyDict_SetItem(globals, builtins_s, builtins) == -1 {
return Err(PyErr::fetch(self));
}
}
let code_obj =
ffi::Py_CompileString(code.as_ptr(), ffi::c_str!("<string>").as_ptr(), start);
if code_obj.is_null() {
return Err(PyErr::fetch(self));
}
let res_ptr = ffi::PyEval_EvalCode(code_obj, globals, locals);
ffi::Py_DECREF(code_obj);
res_ptr.assume_owned_or_err(self).downcast_into_unchecked()
}
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`Python::get_type` will be replaced by `Python::get_type_bound` in a future PyO3 version"
)]
#[inline]
pub fn get_type<T>(self) -> &'py PyType
where
T: PyTypeInfo,
{
self.get_type_bound::<T>().into_gil_ref()
}
#[inline]
pub fn get_type_bound<T>(self) -> Bound<'py, PyType>
where
T: PyTypeInfo,
{
T::type_object_bound(self)
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`Python::import` will be replaced by `Python::import_bound` in a future PyO3 version"
)]
pub fn import<N>(self, name: N) -> PyResult<&'py PyModule>
where
N: IntoPy<Py<PyString>>,
{
Self::import_bound(self, name).map(Bound::into_gil_ref)
}
pub fn import_bound<N>(self, name: N) -> PyResult<Bound<'py, PyModule>>
where
N: IntoPy<Py<PyString>>,
{
PyModule::import_bound(self, name)
}
#[allow(non_snake_case)] #[inline]
pub fn None(self) -> PyObject {
PyNone::get_bound(self).into_py(self)
}
#[allow(non_snake_case)] #[inline]
pub fn Ellipsis(self) -> PyObject {
PyEllipsis::get_bound(self).into_py(self)
}
#[allow(non_snake_case)] #[inline]
pub fn NotImplemented(self) -> PyObject {
PyNotImplemented::get_bound(self).into_py(self)
}
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()
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `obj.downcast_bound::<T>(py)` instead of `py.checked_cast_as::<T>(obj)`"
)]
pub fn checked_cast_as<T>(
self,
obj: PyObject,
) -> Result<&'py T, crate::err::PyDowncastError<'py>>
where
T: crate::PyTypeCheck<AsRefTarget = T>,
{
#[allow(deprecated)]
obj.into_ref(self).downcast()
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `obj.downcast_bound_unchecked::<T>(py)` instead of `py.cast_as::<T>(obj)`"
)]
pub unsafe fn cast_as<T>(self, obj: PyObject) -> &'py T
where
T: crate::type_object::HasPyGilRef<AsRefTarget = T>,
{
#[allow(deprecated)]
obj.into_ref(self).downcast_unchecked()
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_owned_ptr(py, ptr)` or `Bound::from_owned_ptr(py, ptr)` instead"
)]
pub unsafe fn from_owned_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'py T
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_owned_ptr(self, ptr)
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_owned_ptr_or_err(py, ptr)` or `Bound::from_owned_ptr_or_err(py, ptr)` instead"
)]
pub unsafe fn from_owned_ptr_or_err<T>(self, ptr: *mut ffi::PyObject) -> PyResult<&'py T>
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_owned_ptr_or_err(self, ptr)
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_owned_ptr_or_opt(py, ptr)` or `Bound::from_owned_ptr_or_opt(py, ptr)` instead"
)]
pub unsafe fn from_owned_ptr_or_opt<T>(self, ptr: *mut ffi::PyObject) -> Option<&'py T>
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_owned_ptr_or_opt(self, ptr)
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_borrowed_ptr(py, ptr)` or `Bound::from_borrowed_ptr(py, ptr)` instead"
)]
pub unsafe fn from_borrowed_ptr<T>(self, ptr: *mut ffi::PyObject) -> &'py T
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_borrowed_ptr(self, ptr)
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_borrowed_ptr_or_err(py, ptr)` or `Bound::from_borrowed_ptr_or_err(py, ptr)` instead"
)]
pub unsafe fn from_borrowed_ptr_or_err<T>(self, ptr: *mut ffi::PyObject) -> PyResult<&'py T>
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_borrowed_ptr_or_err(self, ptr)
}
#[allow(clippy::wrong_self_convention, deprecated)]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `Py::from_borrowed_ptr_or_opt(py, ptr)` or `Bound::from_borrowed_ptr_or_opt(py, ptr)` instead"
)]
pub unsafe fn from_borrowed_ptr_or_opt<T>(self, ptr: *mut ffi::PyObject) -> Option<&'py T>
where
T: FromPyPointer<'py>,
{
FromPyPointer::from_borrowed_ptr_or_opt(self, ptr)
}
pub fn check_signals(self) -> PyResult<()> {
err::error_on_minusone(self, unsafe { ffi::PyErr_CheckSignals() })
}
#[inline]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "code not using the GIL Refs API can safely remove use of `Python::new_pool`"
)]
#[allow(deprecated)]
pub unsafe fn new_pool(self) -> GILPool {
GILPool::new()
}
}
impl Python<'_> {
#[inline]
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "code not using the GIL Refs API can safely remove use of `Python::with_pool`"
)]
#[allow(deprecated)]
pub fn with_pool<F, R>(&self, f: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> R + Ungil,
{
let pool = unsafe { GILPool::new() };
f(pool.python())
}
}
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_bound("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_bound(py);
let v: i32 = py
.eval_bound("foo + 29", Some(&d), None)
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 42);
let v: i32 = py
.eval_bound("foo + 29", None, Some(&d))
.unwrap()
.extract()
.unwrap();
assert_eq!(v, 42);
let v: i32 = py
.eval_bound("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_bound(py, [1, 2, 3, 4]);
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_bound(py, vec!["foo", "bar"]).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_bound("...", 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_bound(py);
py.run_bound("class Foo: pass", Some(&namespace), Some(&namespace))
.unwrap();
assert!(matches!(namespace.get_item("Foo"), Ok(Some(..))));
assert!(matches!(namespace.get_item("__builtins__"), Ok(Some(..))));
})
}
}