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