use libc::c_int;
use std::ffi::CString;
use std::marker::PhantomData;
use crate::err::{self, PyErr, PyResult};
use crate::ffi;
use crate::objects::{PyBool, PyDict, PyModule, PyObject, PyType};
use crate::pythonrun::GILGuard;
#[derive(Copy, Clone)]
pub struct Python<'p>(PhantomData<&'p GILGuard>);
pub trait PythonObject: crate::conversion::ToPyObject + Send + Sized + 'static {
fn as_object(&self) -> &PyObject;
fn into_object(self) -> PyObject;
unsafe fn unchecked_downcast_from(obj: PyObject) -> Self;
unsafe fn unchecked_downcast_borrow_from(obj: &PyObject) -> &Self;
}
pub struct PythonObjectDowncastError<'p> {
pub(crate) py: Python<'p>,
pub(crate) expected_type_name: String,
pub(crate) received_type: PyType,
}
impl<'p> PythonObjectDowncastError<'p> {
pub fn new(
py: Python<'p>,
expected_type_name: impl Into<String>,
received_type: PyType,
) -> Self {
let expected_type_name = expected_type_name.into();
PythonObjectDowncastError {
py,
expected_type_name,
received_type,
}
}
}
pub trait PythonObjectWithCheckedDowncast: PythonObject {
fn downcast_from(py: Python<'_>, obj: PyObject) -> Result<Self, PythonObjectDowncastError<'_>>;
fn downcast_borrow_from<'a, 'p>(
py: Python<'p>,
obj: &'a PyObject,
) -> Result<&'a Self, PythonObjectDowncastError<'p>>;
}
pub trait PythonObjectWithTypeObject: PythonObjectWithCheckedDowncast {
fn type_object(py: Python) -> PyType;
}
pub trait PyClone: Sized {
fn clone_ref(&self, py: Python) -> Self;
}
impl<T> PyClone for T
where
T: PythonObject,
{
#[inline]
fn clone_ref(&self, py: Python) -> T {
let ptr = self.as_object().as_ptr();
unsafe { T::unchecked_downcast_from(PyObject::from_borrowed_ptr(py, ptr)) }
}
}
impl<T> PyClone for Option<T>
where
T: PyClone,
{
#[inline]
fn clone_ref(&self, py: Python) -> Option<T> {
self.as_ref().map(|v| v.clone_ref(py))
}
}
pub trait PyDrop: Sized {
fn release_ref(self, py: Python);
}
impl<T> PyDrop for T
where
T: PythonObject,
{
#[inline]
fn release_ref(self, _py: Python) {
let ptr = self.into_object().steal_ptr();
unsafe {
ffi::Py_DECREF(ptr);
}
}
}
impl<T> PyDrop for Option<T>
where
T: PyDrop,
{
#[inline]
fn release_ref(self, py: Python) {
if let Some(v) = self {
v.release_ref(py)
}
}
}
pub trait ToPythonPointer {
fn as_ptr(&self) -> *mut ffi::PyObject;
fn steal_ptr(self, py: Python) -> *mut ffi::PyObject;
}
impl ToPythonPointer for PyObject {
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.as_ptr()
}
#[inline]
fn steal_ptr(self, _py: Python) -> *mut ffi::PyObject {
self.steal_ptr()
}
}
impl<'a, T> ToPythonPointer for &'a T
where
T: PythonObject,
{
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.as_object().as_ptr()
}
#[inline]
fn steal_ptr(self, py: Python) -> *mut ffi::PyObject {
self.as_object().clone_ref(py).steal_ptr()
}
}
impl<T> ToPythonPointer for Option<T>
where
T: ToPythonPointer,
{
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
match *self {
Some(ref t) => t.as_ptr(),
None => std::ptr::null_mut(),
}
}
#[inline]
fn steal_ptr(self, py: Python) -> *mut ffi::PyObject {
match self {
Some(t) => t.steal_ptr(py),
None => std::ptr::null_mut(),
}
}
}
impl<'p> Python<'p> {
#[inline]
pub unsafe fn assume_gil_acquired() -> Python<'p> {
Python(PhantomData)
}
#[inline]
pub fn acquire_gil() -> GILGuard {
GILGuard::acquire()
}
pub fn allow_threads<T, F>(self, f: F) -> T
where
F: Send + FnOnce() -> T,
{
unsafe {
let save = ffi::PyEval_SaveThread();
let result = f();
ffi::PyEval_RestoreThread(save);
result
}
}
pub fn eval(
self,
code: &str,
globals: Option<&PyDict>,
locals: Option<&PyDict>,
) -> PyResult<PyObject> {
self.run_code(code, ffi::Py_eval_input, globals, locals)
}
pub fn run(
self,
code: &str,
globals: Option<&PyDict>,
locals: Option<&PyDict>,
) -> PyResult<()> {
self.run_code(code, ffi::Py_file_input, globals, locals)?;
Ok(())
}
fn run_code(
self,
code: &str,
start: c_int,
globals: Option<&PyDict>,
locals: Option<&PyDict>,
) -> PyResult<PyObject> {
let code = CString::new(code).unwrap();
unsafe {
let mptr = ffi::PyImport_AddModule("__main__\0".as_ptr() as *const _);
if mptr.is_null() {
return Err(PyErr::fetch(self));
}
let mdict = ffi::PyModule_GetDict(mptr);
let globals = match globals {
Some(g) => g.as_ptr(),
None => mdict,
};
let locals = match locals {
Some(l) => l.as_ptr(),
None => globals,
};
let res_ptr =
ffi::PyRun_StringFlags(code.as_ptr(), start, globals, locals, std::ptr::null_mut());
err::result_from_owned_ptr(self, res_ptr)
}
}
#[allow(non_snake_case)] #[inline]
pub fn None(self) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_None()) }
}
#[allow(non_snake_case)] #[inline]
pub fn True(self) -> PyBool {
unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_True()).unchecked_cast_into::<PyBool>() }
}
#[allow(non_snake_case)] #[inline]
pub fn False(self) -> PyBool {
unsafe {
PyObject::from_borrowed_ptr(self, ffi::Py_False()).unchecked_cast_into::<PyBool>()
}
}
#[allow(non_snake_case)] #[inline]
pub fn NotImplemented(self) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_NotImplemented()) }
}
pub fn get_type<T>(self) -> PyType
where
T: PythonObjectWithTypeObject,
{
T::type_object(self)
}
pub fn import(self, name: &str) -> PyResult<PyModule> {
PyModule::import(self, name)
}
}
impl<'p> std::fmt::Debug for PythonObjectDowncastError<'p> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.write_str("PythonObjectDowncastError")
}
}
#[cfg(test)]
mod test {
use crate::{PyDict, Python};
#[test]
fn test_eval() {
let gil = Python::acquire_gil();
let py = gil.python();
let v: i32 = py
.eval("min(1, 2)", None, None)
.unwrap()
.extract(py)
.unwrap();
assert_eq!(v, 1);
let d = PyDict::new(py);
d.set_item(py, "foo", 13).unwrap();
let v: i32 = py
.eval("foo + 29", None, Some(&d))
.unwrap()
.extract(py)
.unwrap();
assert_eq!(v, 42);
let v: i32 = py
.eval("min(foo, 2)", None, Some(&d))
.unwrap()
.extract(py)
.unwrap();
assert_eq!(v, 2);
}
}