use crate::ffi_ptr_ext::FfiPtrExt as _;
use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple};
use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult};
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl Sealed for () {}
impl Sealed for Bound<'_, PyTuple> {}
impl Sealed for &'_ Bound<'_, PyTuple> {}
impl Sealed for Py<PyTuple> {}
impl Sealed for &'_ Py<PyTuple> {}
impl Sealed for Borrowed<'_, '_, PyTuple> {}
pub struct Token;
}
#[cfg_attr(
diagnostic_namespace,
diagnostic::on_unimplemented(
message = "`{Self}` cannot used as a Python `call` argument",
note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py<PyTuple>`",
note = "if your type is convertable to `PyTuple` via `IntoPyObject`, call `<arg>.into_pyobject(py)` manually",
note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(<arg>,)`"
)
)]
pub trait PyCallArgs<'py>: Sized + private::Sealed {
#[doc(hidden)]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>>;
#[doc(hidden)]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>>;
#[doc(hidden)]
fn call_method_positional(
self,
object: Borrowed<'_, 'py, PyAny>,
method_name: Borrowed<'_, 'py, PyString>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
object
.getattr(method_name)
.and_then(|method| method.call1(self))
}
}
impl<'py> PyCallArgs<'py> for () {
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
let args = self.into_pyobject_or_pyerr(function.py())?;
args.call(function, kwargs, token)
}
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
let args = self.into_pyobject_or_pyerr(function.py())?;
args.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Py<PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for &'_ Py<PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
unsafe {
ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr())
.assume_owned_or_err(function.py())
}
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
unsafe {
ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut())
.assume_owned_or_err(function.py())
}
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use crate::{
pyfunction,
types::{PyDict, PyTuple},
Py,
};
#[pyfunction(signature = (*args, **kwargs), crate = "crate")]
fn args_kwargs(
args: Py<PyTuple>,
kwargs: Option<Py<PyDict>>,
) -> (Py<PyTuple>, Option<Py<PyDict>>) {
(args, kwargs)
}
#[test]
fn test_call() {
use crate::{
types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple},
wrap_pyfunction, Py, Python,
};
Python::with_gil(|py| {
let f = wrap_pyfunction!(args_kwargs, py).unwrap();
let args = PyTuple::new(py, [1, 2, 3]).unwrap();
let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap();
macro_rules! check_call {
($args:expr, $kwargs:expr) => {
let (a, k): (Py<PyTuple>, Py<PyDict>) = f
.call(args.clone(), Some(kwargs))
.unwrap()
.extract()
.unwrap();
assert!(a.is(&args));
assert!(k.is(kwargs));
};
}
check_call!(args.clone(), kwargs);
check_call!(&args, kwargs);
check_call!(args.clone().unbind(), kwargs);
check_call!(&args.as_unbound(), kwargs);
check_call!(args.as_borrowed(), kwargs);
})
}
#[test]
fn test_call_positional() {
use crate::{
types::{PyAnyMethods, PyNone, PyTuple},
wrap_pyfunction, Py, Python,
};
Python::with_gil(|py| {
let f = wrap_pyfunction!(args_kwargs, py).unwrap();
let args = PyTuple::new(py, [1, 2, 3]).unwrap();
macro_rules! check_call {
($args:expr, $kwargs:expr) => {
let (a, k): (Py<PyTuple>, Py<PyNone>) =
f.call1(args.clone()).unwrap().extract().unwrap();
assert!(a.is(&args));
assert!(k.is_none(py));
};
}
check_call!(args.clone(), kwargs);
check_call!(&args, kwargs);
check_call!(args.clone().unbind(), kwargs);
check_call!(args.as_unbound(), kwargs);
check_call!(args.as_borrowed(), kwargs);
})
}
}