use crate::conversion::{PyTryFrom, ToBorrowedObject};
use crate::err::{PyDowncastError, PyErr, PyResult};
use crate::gil;
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
use crate::types::{PyDict, PyTuple};
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer,
PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject,
};
use std::marker::PhantomData;
use std::mem;
use std::ptr::NonNull;
pub unsafe trait PyNativeType: Sized {
fn py(&self) -> Python {
unsafe { Python::assume_gil_acquired() }
}
unsafe fn unchecked_downcast(obj: &PyAny) -> &Self {
&*(obj.as_ptr() as *const Self)
}
}
#[repr(transparent)]
pub struct Py<T>(NonNull<ffi::PyObject>, PhantomData<T>);
unsafe impl<T> Send for Py<T> {}
unsafe impl<T> Sync for Py<T> {}
impl<T> Py<T>
where
T: PyClass,
{
pub fn new(py: Python, value: impl Into<PyClassInitializer<T>>) -> PyResult<Py<T>> {
let initializer = value.into();
let obj = initializer.create_cell(py)?;
let ob = unsafe { Py::from_owned_ptr(py, obj as _) };
Ok(ob)
}
}
impl<T> Py<T>
where
T: PyTypeInfo,
{
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py T::AsRefTarget {
let any = self.as_ptr() as *const PyAny;
unsafe { PyNativeType::unchecked_downcast(&*any) }
}
pub fn into_ref(self, py: Python) -> &T::AsRefTarget {
unsafe { py.from_owned_ptr(self.into_ptr()) }
}
}
impl<T> Py<T>
where
T: PyClass,
{
pub fn borrow<'py>(&'py self, py: Python<'py>) -> PyRef<'py, T> {
self.as_ref(py).borrow()
}
pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> {
self.as_ref(py).borrow_mut()
}
pub fn try_borrow<'py>(&'py self, py: Python<'py>) -> Result<PyRef<'py, T>, PyBorrowError> {
self.as_ref(py).try_borrow()
}
pub fn try_borrow_mut<'py>(
&'py self,
py: Python<'py>,
) -> Result<PyRefMut<'py, T>, PyBorrowMutError> {
self.as_ref(py).try_borrow_mut()
}
}
impl<T> Py<T> {
#[inline]
pub fn get_refcnt(&self, _py: Python) -> isize {
unsafe { ffi::Py_REFCNT(self.0.as_ptr()) }
}
#[inline]
pub fn clone_ref(&self, py: Python) -> Py<T> {
unsafe { Py::from_borrowed_ptr(py, self.0.as_ptr()) }
}
pub fn is_none(&self, _py: Python) -> bool {
unsafe { ffi::Py_None() == self.as_ptr() }
}
pub fn is_true(&self, py: Python) -> PyResult<bool> {
let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) };
if v == -1 {
Err(PyErr::fetch(py))
} else {
Ok(v != 0)
}
}
pub fn extract<'p, D>(&'p self, py: Python<'p>) -> PyResult<D>
where
D: FromPyObject<'p>,
{
FromPyObject::extract(unsafe { py.from_borrowed_ptr(self.as_ptr()) })
}
pub fn getattr<N>(&self, py: Python, attr_name: N) -> PyResult<PyObject>
where
N: ToPyObject,
{
attr_name.with_borrowed_ptr(py, |attr_name| unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_GetAttr(self.as_ptr(), attr_name))
})
}
pub fn call(
&self,
py: Python,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<PyObject> {
let args = args.into_py(py).into_ptr();
let kwargs = kwargs.into_ptr();
let result = unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_Call(self.as_ptr(), args, kwargs))
};
unsafe {
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs);
}
result
}
pub fn call1(&self, py: Python, args: impl IntoPy<Py<PyTuple>>) -> PyResult<PyObject> {
self.call(py, args, None)
}
pub fn call0(&self, py: Python) -> PyResult<PyObject> {
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::_PyObject_CallNoArg(self.as_ptr()))
}
} else {
self.call(py, (), None)
}
}
}
pub fn call_method(
&self,
py: Python,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<PyObject> {
name.with_borrowed_ptr(py, |name| unsafe {
let args = args.into_py(py).into_ptr();
let kwargs = kwargs.into_ptr();
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name);
if ptr.is_null() {
return Err(PyErr::fetch(py));
}
let result = PyObject::from_owned_ptr_or_err(py, ffi::PyObject_Call(ptr, args, kwargs));
ffi::Py_DECREF(ptr);
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs);
result
})
}
pub fn call_method1(
&self,
py: Python,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
) -> PyResult<PyObject> {
self.call_method(py, name, args, None)
}
pub fn call_method0(&self, py: Python, name: &str) -> PyResult<PyObject> {
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
unsafe {
let name = name.into_py(py);
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
}
} else {
self.call_method(py, name, (), None)
}
}
}
#[inline]
pub unsafe fn from_owned_ptr(py: Python, ptr: *mut ffi::PyObject) -> Py<T> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => Py(nonnull_ptr, PhantomData),
None => crate::err::panic_after_error(py),
}
}
#[inline]
pub unsafe fn from_owned_ptr_or_err(py: Python, ptr: *mut ffi::PyObject) -> PyResult<Py<T>> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => Ok(Py(nonnull_ptr, PhantomData)),
None => Err(PyErr::fetch(py)),
}
}
#[inline]
pub unsafe fn from_owned_ptr_or_opt(_py: Python, ptr: *mut ffi::PyObject) -> Option<Self> {
NonNull::new(ptr).map(|nonnull_ptr| Py(nonnull_ptr, PhantomData))
}
#[inline]
pub unsafe fn from_borrowed_ptr(py: Python, ptr: *mut ffi::PyObject) -> Py<T> {
match Self::from_borrowed_ptr_or_opt(py, ptr) {
Some(slf) => slf,
None => crate::err::panic_after_error(py),
}
}
#[inline]
pub unsafe fn from_borrowed_ptr_or_err(py: Python, ptr: *mut ffi::PyObject) -> PyResult<Self> {
Self::from_borrowed_ptr_or_opt(py, ptr).ok_or_else(|| PyErr::fetch(py))
}
#[inline]
pub unsafe fn from_borrowed_ptr_or_opt(_py: Python, ptr: *mut ffi::PyObject) -> Option<Self> {
NonNull::new(ptr).map(|nonnull_ptr| {
ffi::Py_INCREF(ptr);
Py(nonnull_ptr, PhantomData)
})
}
#[inline]
unsafe fn from_non_null(ptr: NonNull<ffi::PyObject>) -> Self {
Self(ptr, PhantomData)
}
#[inline]
fn into_non_null(self) -> NonNull<ffi::PyObject> {
let pointer = self.0;
mem::forget(self);
pointer
}
}
impl<T> ToPyObject for Py<T> {
fn to_object(&self, py: Python) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl<T> IntoPy<PyObject> for Py<T> {
#[inline]
fn into_py(self, _py: Python) -> PyObject {
unsafe { PyObject::from_non_null(self.into_non_null()) }
}
}
impl<T> AsPyPointer for Py<T> {
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.0.as_ptr()
}
}
impl<T> IntoPyPointer for Py<T> {
#[inline]
#[must_use]
fn into_ptr(self) -> *mut ffi::PyObject {
self.into_non_null().as_ptr()
}
}
impl<T> std::convert::From<&'_ T> for PyObject
where
T: AsPyPointer + PyNativeType,
{
fn from(obj: &T) -> Self {
unsafe { Py::from_borrowed_ptr(obj.py(), obj.as_ptr()) }
}
}
impl<T> std::convert::From<Py<T>> for PyObject
where
T: AsRef<PyAny>,
{
#[inline]
fn from(other: Py<T>) -> Self {
unsafe { Self::from_non_null(other.into_non_null()) }
}
}
impl<'a, T> std::convert::From<&PyCell<T>> for Py<T>
where
T: PyClass,
{
fn from(cell: &PyCell<T>) -> Self {
unsafe { Py::from_borrowed_ptr(cell.py(), cell.as_ptr()) }
}
}
impl<'a, T> std::convert::From<PyRef<'a, T>> for Py<T>
where
T: PyClass,
{
fn from(pyref: PyRef<'a, T>) -> Self {
unsafe { Py::from_borrowed_ptr(pyref.py(), pyref.as_ptr()) }
}
}
impl<'a, T> std::convert::From<PyRefMut<'a, T>> for Py<T>
where
T: PyClass,
{
fn from(pyref: PyRefMut<'a, T>) -> Self {
unsafe { Py::from_borrowed_ptr(pyref.py(), pyref.as_ptr()) }
}
}
impl<T> PartialEq for Py<T> {
#[inline]
fn eq(&self, o: &Py<T>) -> bool {
self.0 == o.0
}
}
impl<T> Clone for Py<T> {
fn clone(&self) -> Self {
unsafe {
gil::register_incref(self.0);
}
Self(self.0, PhantomData)
}
}
impl<T> Drop for Py<T> {
fn drop(&mut self) {
unsafe {
gil::register_decref(self.0);
}
}
}
impl<'a, T> FromPyObject<'a> for Py<T>
where
T: PyTypeInfo,
&'a T::AsRefTarget: FromPyObject<'a>,
T::AsRefTarget: 'a + AsPyPointer,
{
fn extract(ob: &'a PyAny) -> PyResult<Self> {
unsafe {
ob.extract::<&T::AsRefTarget>()
.map(|val| Py::from_borrowed_ptr(ob.py(), val.as_ptr()))
}
}
}
impl<T> std::error::Error for Py<T>
where
T: std::error::Error + PyTypeInfo,
T::AsRefTarget: std::fmt::Display,
{
}
impl<T> std::fmt::Display for Py<T>
where
T: PyTypeInfo,
T::AsRefTarget: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Python::with_gil(|py| std::fmt::Display::fmt(self.as_ref(py), f))
}
}
impl<T> std::fmt::Debug for Py<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple("Py").field(&self.0.as_ptr()).finish()
}
}
pub type PyObject = Py<PyAny>;
impl PyObject {
pub fn cast_as<'p, D>(&'p self, py: Python<'p>) -> Result<&'p D, PyDowncastError>
where
D: PyTryFrom<'p>,
{
D::try_from(unsafe { py.from_borrowed_ptr::<PyAny>(self.as_ptr()) })
}
}
#[cfg(test)]
mod tests {
use super::{Py, PyObject};
use crate::types::PyDict;
use crate::{ffi, AsPyPointer, Python};
#[test]
fn test_call_for_non_existing_method() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj: PyObject = PyDict::new(py).into();
assert!(obj.call_method0(py, "asdf").is_err());
assert!(obj
.call_method(py, "nonexistent_method", (1,), None)
.is_err());
assert!(obj.call_method0(py, "nonexistent_method").is_err());
assert!(obj.call_method1(py, "nonexistent_method", (1,)).is_err());
}
#[test]
fn py_from_dict() {
let dict: Py<PyDict> = {
let gil = Python::acquire_gil();
let py = gil.python();
let native = PyDict::new(py);
Py::from(native)
};
assert_eq!(unsafe { ffi::Py_REFCNT(dict.as_ptr()) }, 1);
}
#[test]
fn pyobject_from_py() {
Python::with_gil(|py| {
let dict: Py<PyDict> = PyDict::new(py).into();
let cnt = dict.get_refcnt(py);
let p: PyObject = dict.into();
assert_eq!(p.get_refcnt(py), cnt);
});
}
}