1use crate::ffi_ptr_ext::FfiPtrExt as _;
4use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple};
5use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult};
6
7pub(crate) mod private {
8 use super::*;
9
10 pub trait Sealed {}
11
12 impl Sealed for () {}
13 impl Sealed for Bound<'_, PyTuple> {}
14 impl Sealed for &'_ Bound<'_, PyTuple> {}
15 impl Sealed for Py<PyTuple> {}
16 impl Sealed for &'_ Py<PyTuple> {}
17 impl Sealed for Borrowed<'_, '_, PyTuple> {}
18 pub struct Token;
19}
20
21#[cfg_attr(
46 diagnostic_namespace,
47 diagnostic::on_unimplemented(
48 message = "`{Self}` cannot used as a Python `call` argument",
49 note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py<PyTuple>`",
50 note = "if your type is convertable to `PyTuple` via `IntoPyObject`, call `<arg>.into_pyobject(py)` manually",
51 note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(<arg>,)`"
52 )
53)]
54pub trait PyCallArgs<'py>: Sized + private::Sealed {
55 #[doc(hidden)]
56 fn call(
57 self,
58 function: Borrowed<'_, 'py, PyAny>,
59 kwargs: Borrowed<'_, 'py, PyDict>,
60 token: private::Token,
61 ) -> PyResult<Bound<'py, PyAny>>;
62
63 #[doc(hidden)]
64 fn call_positional(
65 self,
66 function: Borrowed<'_, 'py, PyAny>,
67 token: private::Token,
68 ) -> PyResult<Bound<'py, PyAny>>;
69
70 #[doc(hidden)]
71 fn call_method_positional(
72 self,
73 object: Borrowed<'_, 'py, PyAny>,
74 method_name: Borrowed<'_, 'py, PyString>,
75 _: private::Token,
76 ) -> PyResult<Bound<'py, PyAny>> {
77 object
78 .getattr(method_name)
79 .and_then(|method| method.call1(self))
80 }
81}
82
83impl<'py> PyCallArgs<'py> for () {
84 fn call(
85 self,
86 function: Borrowed<'_, 'py, PyAny>,
87 kwargs: Borrowed<'_, 'py, PyDict>,
88 token: private::Token,
89 ) -> PyResult<Bound<'py, PyAny>> {
90 let args = self.into_pyobject_or_pyerr(function.py())?;
91 args.call(function, kwargs, token)
92 }
93
94 fn call_positional(
95 self,
96 function: Borrowed<'_, 'py, PyAny>,
97 token: private::Token,
98 ) -> PyResult<Bound<'py, PyAny>> {
99 let args = self.into_pyobject_or_pyerr(function.py())?;
100 args.call_positional(function, token)
101 }
102}
103
104impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> {
105 #[inline]
106 fn call(
107 self,
108 function: Borrowed<'_, 'py, PyAny>,
109 kwargs: Borrowed<'_, 'py, PyDict>,
110 token: private::Token,
111 ) -> PyResult<Bound<'py, PyAny>> {
112 self.as_borrowed().call(function, kwargs, token)
113 }
114
115 #[inline]
116 fn call_positional(
117 self,
118 function: Borrowed<'_, 'py, PyAny>,
119 token: private::Token,
120 ) -> PyResult<Bound<'py, PyAny>> {
121 self.as_borrowed().call_positional(function, token)
122 }
123}
124
125impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> {
126 #[inline]
127 fn call(
128 self,
129 function: Borrowed<'_, 'py, PyAny>,
130 kwargs: Borrowed<'_, 'py, PyDict>,
131 token: private::Token,
132 ) -> PyResult<Bound<'py, PyAny>> {
133 self.as_borrowed().call(function, kwargs, token)
134 }
135
136 #[inline]
137 fn call_positional(
138 self,
139 function: Borrowed<'_, 'py, PyAny>,
140 token: private::Token,
141 ) -> PyResult<Bound<'py, PyAny>> {
142 self.as_borrowed().call_positional(function, token)
143 }
144}
145
146impl<'py> PyCallArgs<'py> for Py<PyTuple> {
147 #[inline]
148 fn call(
149 self,
150 function: Borrowed<'_, 'py, PyAny>,
151 kwargs: Borrowed<'_, 'py, PyDict>,
152 token: private::Token,
153 ) -> PyResult<Bound<'py, PyAny>> {
154 self.bind_borrowed(function.py())
155 .call(function, kwargs, token)
156 }
157
158 #[inline]
159 fn call_positional(
160 self,
161 function: Borrowed<'_, 'py, PyAny>,
162 token: private::Token,
163 ) -> PyResult<Bound<'py, PyAny>> {
164 self.bind_borrowed(function.py())
165 .call_positional(function, token)
166 }
167}
168
169impl<'py> PyCallArgs<'py> for &'_ Py<PyTuple> {
170 #[inline]
171 fn call(
172 self,
173 function: Borrowed<'_, 'py, PyAny>,
174 kwargs: Borrowed<'_, 'py, PyDict>,
175 token: private::Token,
176 ) -> PyResult<Bound<'py, PyAny>> {
177 self.bind_borrowed(function.py())
178 .call(function, kwargs, token)
179 }
180
181 #[inline]
182 fn call_positional(
183 self,
184 function: Borrowed<'_, 'py, PyAny>,
185 token: private::Token,
186 ) -> PyResult<Bound<'py, PyAny>> {
187 self.bind_borrowed(function.py())
188 .call_positional(function, token)
189 }
190}
191
192impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> {
193 #[inline]
194 fn call(
195 self,
196 function: Borrowed<'_, 'py, PyAny>,
197 kwargs: Borrowed<'_, 'py, PyDict>,
198 _: private::Token,
199 ) -> PyResult<Bound<'py, PyAny>> {
200 unsafe {
201 ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr())
202 .assume_owned_or_err(function.py())
203 }
204 }
205
206 #[inline]
207 fn call_positional(
208 self,
209 function: Borrowed<'_, 'py, PyAny>,
210 _: private::Token,
211 ) -> PyResult<Bound<'py, PyAny>> {
212 unsafe {
213 ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut())
214 .assume_owned_or_err(function.py())
215 }
216 }
217}
218
219#[cfg(test)]
220#[cfg(feature = "macros")]
221mod tests {
222 use crate::{
223 pyfunction,
224 types::{PyDict, PyTuple},
225 Py,
226 };
227
228 #[pyfunction(signature = (*args, **kwargs), crate = "crate")]
229 fn args_kwargs(
230 args: Py<PyTuple>,
231 kwargs: Option<Py<PyDict>>,
232 ) -> (Py<PyTuple>, Option<Py<PyDict>>) {
233 (args, kwargs)
234 }
235
236 #[test]
237 fn test_call() {
238 use crate::{
239 types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple},
240 wrap_pyfunction, Py, Python,
241 };
242
243 Python::with_gil(|py| {
244 let f = wrap_pyfunction!(args_kwargs, py).unwrap();
245
246 let args = PyTuple::new(py, [1, 2, 3]).unwrap();
247 let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap();
248
249 macro_rules! check_call {
250 ($args:expr, $kwargs:expr) => {
251 let (a, k): (Py<PyTuple>, Py<PyDict>) = f
252 .call(args.clone(), Some(kwargs))
253 .unwrap()
254 .extract()
255 .unwrap();
256 assert!(a.is(&args));
257 assert!(k.is(kwargs));
258 };
259 }
260
261 check_call!(args.clone(), kwargs);
263
264 check_call!(&args, kwargs);
266
267 check_call!(args.clone().unbind(), kwargs);
269
270 check_call!(&args.as_unbound(), kwargs);
272
273 check_call!(args.as_borrowed(), kwargs);
275 })
276 }
277
278 #[test]
279 fn test_call_positional() {
280 use crate::{
281 types::{PyAnyMethods, PyNone, PyTuple},
282 wrap_pyfunction, Py, Python,
283 };
284
285 Python::with_gil(|py| {
286 let f = wrap_pyfunction!(args_kwargs, py).unwrap();
287
288 let args = PyTuple::new(py, [1, 2, 3]).unwrap();
289
290 macro_rules! check_call {
291 ($args:expr, $kwargs:expr) => {
292 let (a, k): (Py<PyTuple>, Py<PyNone>) =
293 f.call1(args.clone()).unwrap().extract().unwrap();
294 assert!(a.is(&args));
295 assert!(k.is_none(py));
296 };
297 }
298
299 check_call!(args.clone(), kwargs);
301
302 check_call!(&args, kwargs);
304
305 check_call!(args.clone().unbind(), kwargs);
307
308 check_call!(args.as_unbound(), kwargs);
310
311 check_call!(args.as_borrowed(), kwargs);
313 })
314 }
315}