use crate::class::basic::CompareOp;
use crate::conversion::{
AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject,
};
use crate::err::{PyDowncastError, PyErr, PyResult};
use crate::exceptions::TypeError;
use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType};
use crate::{err, ffi, Py, PyNativeType, PyObject, Python};
use libc::c_int;
use std::cell::UnsafeCell;
use std::cmp::Ordering;
#[repr(transparent)]
pub struct PyAny(UnsafeCell<ffi::PyObject>);
impl crate::AsPyPointer for PyAny {
#[inline]
fn as_ptr(&self) -> *mut ffi::PyObject {
self.0.get()
}
}
impl PartialEq for PyAny {
#[inline]
fn eq(&self, o: &PyAny) -> bool {
self.as_ptr() == o.as_ptr()
}
}
unsafe impl crate::PyNativeType for PyAny {}
unsafe impl crate::type_object::PyLayout<PyAny> for ffi::PyObject {}
impl crate::type_object::PySizedLayout<PyAny> for ffi::PyObject {}
pyobject_native_type_convert!(
PyAny,
ffi::PyObject,
ffi::PyBaseObject_Type,
Some("builtins"),
ffi::PyObject_Check
);
pyobject_native_type_extract!(PyAny);
impl PyAny {
pub fn downcast<T>(&self) -> Result<&T, PyDowncastError>
where
for<'py> T: PyTryFrom<'py>,
{
<T as PyTryFrom>::try_from(self)
}
pub fn hasattr<N>(&self, attr_name: N) -> PyResult<bool>
where
N: ToPyObject,
{
attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe {
Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0)
})
}
pub fn getattr<N>(&self, attr_name: N) -> PyResult<&PyAny>
where
N: ToPyObject,
{
attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe {
self.py()
.from_owned_ptr_or_err(ffi::PyObject_GetAttr(self.as_ptr(), attr_name))
})
}
pub fn setattr<N, V>(&self, attr_name: N, value: V) -> PyResult<()>
where
N: ToBorrowedObject,
V: ToBorrowedObject,
{
attr_name.with_borrowed_ptr(self.py(), move |attr_name| {
value.with_borrowed_ptr(self.py(), |value| unsafe {
err::error_on_minusone(
self.py(),
ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value),
)
})
})
}
pub fn delattr<N>(&self, attr_name: N) -> PyResult<()>
where
N: ToPyObject,
{
attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe {
err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name))
})
}
pub fn compare<O>(&self, other: O) -> PyResult<Ordering>
where
O: ToPyObject,
{
let py = self.py();
let do_compare = |other, op| unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_RichCompare(self.as_ptr(), other, op))
.and_then(|obj| obj.is_true(py))
};
other.with_borrowed_ptr(py, |other| {
if do_compare(other, ffi::Py_EQ)? {
Ok(Ordering::Equal)
} else if do_compare(other, ffi::Py_LT)? {
Ok(Ordering::Less)
} else if do_compare(other, ffi::Py_GT)? {
Ok(Ordering::Greater)
} else {
Err(TypeError::py_err(
"PyAny::compare(): All comparisons returned false",
))
}
})
}
pub fn rich_compare<O>(&self, other: O, compare_op: CompareOp) -> PyResult<&PyAny>
where
O: ToPyObject,
{
unsafe {
other.with_borrowed_ptr(self.py(), |other| {
self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare(
self.as_ptr(),
other,
compare_op as c_int,
))
})
}
}
pub fn is_callable(&self) -> bool {
unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 }
}
pub fn call(
&self,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
let args = args.into_py(self.py()).into_ptr();
let kwargs = kwargs.into_ptr();
let result = unsafe {
let return_value = ffi::PyObject_Call(self.as_ptr(), args, kwargs);
self.py().from_owned_ptr_or_err(return_value)
};
unsafe {
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs);
}
result
}
pub fn call0(&self) -> PyResult<&PyAny> {
self.call((), None)
}
pub fn call1(&self, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call(args, None)
}
pub fn call_method(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
name.with_borrowed_ptr(self.py(), |name| unsafe {
let py = self.py();
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name);
if ptr.is_null() {
return Err(PyErr::fetch(py));
}
let args = args.into_py(py).into_ptr();
let kwargs = kwargs.into_ptr();
let result_ptr = ffi::PyObject_Call(ptr, args, kwargs);
let result = py.from_owned_ptr_or_err(result_ptr);
ffi::Py_DECREF(ptr);
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs);
result
})
}
pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> {
self.call_method(name, (), None)
}
pub fn call_method1(&self, name: &str, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call_method(name, args, None)
}
pub fn is_true(&self) -> PyResult<bool> {
let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) };
if v == -1 {
Err(PyErr::fetch(self.py()))
} else {
Ok(v != 0)
}
}
pub fn is_none(&self) -> bool {
unsafe { ffi::Py_None() == self.as_ptr() }
}
pub fn is_empty(&self) -> PyResult<bool> {
self.len().map(|l| l == 0)
}
pub fn get_item<K>(&self, key: K) -> PyResult<&PyAny>
where
K: ToBorrowedObject,
{
key.with_borrowed_ptr(self.py(), |key| unsafe {
self.py()
.from_owned_ptr_or_err(ffi::PyObject_GetItem(self.as_ptr(), key))
})
}
pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
where
K: ToBorrowedObject,
V: ToBorrowedObject,
{
key.with_borrowed_ptr(self.py(), move |key| {
value.with_borrowed_ptr(self.py(), |value| unsafe {
err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value))
})
})
}
pub fn del_item<K>(&self, key: K) -> PyResult<()>
where
K: ToBorrowedObject,
{
key.with_borrowed_ptr(self.py(), |key| unsafe {
err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key))
})
}
pub fn iter(&self) -> PyResult<PyIterator> {
Ok(PyIterator::from_object(self.py(), self)?)
}
pub fn get_type(&self) -> &PyType {
unsafe { PyType::from_type_ptr(self.py(), (*self.as_ptr()).ob_type) }
}
#[inline]
pub fn get_type_ptr(&self) -> *mut ffi::PyTypeObject {
unsafe { (*self.as_ptr()).ob_type }
}
pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError>
where
D: PyTryFrom<'a>,
{
D::try_from(self)
}
pub fn extract<'a, D>(&'a self) -> PyResult<D>
where
D: FromPyObject<'a>,
{
FromPyObject::extract(self)
}
pub fn get_refcnt(&self) -> isize {
unsafe { ffi::Py_REFCNT(self.as_ptr()) }
}
pub fn repr(&self) -> PyResult<&PyString> {
unsafe {
self.py()
.from_owned_ptr_or_err(ffi::PyObject_Repr(self.as_ptr()))
}
}
pub fn str(&self) -> PyResult<&PyString> {
unsafe {
self.py()
.from_owned_ptr_or_err(ffi::PyObject_Str(self.as_ptr()))
}
}
pub fn hash(&self) -> PyResult<isize> {
let v = unsafe { ffi::PyObject_Hash(self.as_ptr()) };
if v == -1 {
Err(PyErr::fetch(self.py()))
} else {
Ok(v)
}
}
pub fn len(&self) -> PyResult<usize> {
let v = unsafe { ffi::PyObject_Size(self.as_ptr()) };
if v == -1 {
Err(PyErr::fetch(self.py()))
} else {
Ok(v as usize)
}
}
pub fn dir(&self) -> &PyList {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) }
}
}
#[cfg(test)]
mod test {
use crate::types::{IntoPyDict, PyList};
use crate::Python;
use crate::ToPyObject;
#[test]
fn test_call_for_non_existing_method() {
let gil = Python::acquire_gil();
let py = gil.python();
let a = py.eval("42", None, None).unwrap();
a.call_method0("__str__").unwrap();
assert!(a.call_method("nonexistent_method", (1,), None).is_err());
assert!(a.call_method0("nonexistent_method").is_err());
assert!(a.call_method1("nonexistent_method", (1,)).is_err());
}
#[test]
fn test_call_with_kwargs() {
let gil = Python::acquire_gil();
let py = gil.python();
let list = vec![3, 6, 5, 4, 7].to_object(py);
let dict = vec![("reverse", true)].into_py_dict(py);
list.call_method(py, "sort", (), Some(dict)).unwrap();
assert_eq!(list.extract::<Vec<i32>>(py).unwrap(), vec![7, 6, 5, 4, 3]);
}
#[test]
fn test_type() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = py.eval("42", None, None).unwrap();
assert_eq!(unsafe { obj.get_type().as_type_ptr() }, obj.get_type_ptr())
}
#[test]
fn test_dir() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = py.eval("42", None, None).unwrap();
let dir = py
.eval("dir(42)", None, None)
.unwrap()
.downcast::<PyList>()
.unwrap();
let a = obj
.dir()
.into_iter()
.map(|x| x.extract::<String>().unwrap());
let b = dir.into_iter().map(|x| x.extract::<String>().unwrap());
assert!(a.eq(b));
}
#[test]
fn test_nan_eq() {
let gil = Python::acquire_gil();
let py = gil.python();
let nan = py.eval("float('nan')", None, None).unwrap();
assert!(nan.compare(nan).is_err());
}
}