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#[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 {
198 ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr())
199 .assume_owned_or_err(function.py())
200 }
201 }
202
203 #[inline]
204 fn call_positional(
205 self,
206 function: Borrowed<'_, 'py, PyAny>,
207 _: private::Token,
208 ) -> PyResult<Bound<'py, PyAny>> {
209 unsafe {
210 ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut())
211 .assume_owned_or_err(function.py())
212 }
213 }
214}
215
216#[cfg(test)]
217#[cfg(feature = "macros")]
218mod tests {
219 use crate::{
220 pyfunction,
221 types::{PyDict, PyTuple},
222 Py,
223 };
224
225 #[pyfunction(signature = (*args, **kwargs), crate = "crate")]
226 fn args_kwargs(
227 args: Py<PyTuple>,
228 kwargs: Option<Py<PyDict>>,
229 ) -> (Py<PyTuple>, Option<Py<PyDict>>) {
230 (args, kwargs)
231 }
232
233 #[test]
234 fn test_call() {
235 use crate::{
236 types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple},
237 wrap_pyfunction, Py, Python,
238 };
239
240 Python::attach(|py| {
241 let f = wrap_pyfunction!(args_kwargs, py).unwrap();
242
243 let args = PyTuple::new(py, [1, 2, 3]).unwrap();
244 let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap();
245
246 macro_rules! check_call {
247 ($args:expr, $kwargs:expr) => {
248 let (a, k): (Py<PyTuple>, Py<PyDict>) = f
249 .call(args.clone(), Some(kwargs))
250 .unwrap()
251 .extract()
252 .unwrap();
253 assert!(a.is(&args));
254 assert!(k.is(kwargs));
255 };
256 }
257
258 check_call!(args.clone(), kwargs);
260
261 check_call!(&args, kwargs);
263
264 check_call!(args.clone().unbind(), kwargs);
266
267 check_call!(&args.as_unbound(), kwargs);
269
270 check_call!(args.as_borrowed(), kwargs);
272 })
273 }
274
275 #[test]
276 fn test_call_positional() {
277 use crate::{
278 types::{PyAnyMethods, PyNone, PyTuple},
279 wrap_pyfunction, Py, Python,
280 };
281
282 Python::attach(|py| {
283 let f = wrap_pyfunction!(args_kwargs, py).unwrap();
284
285 let args = PyTuple::new(py, [1, 2, 3]).unwrap();
286
287 macro_rules! check_call {
288 ($args:expr, $kwargs:expr) => {
289 let (a, k): (Py<PyTuple>, Py<PyNone>) =
290 f.call1(args.clone()).unwrap().extract().unwrap();
291 assert!(a.is(&args));
292 assert!(k.is_none(py));
293 };
294 }
295
296 check_call!(args.clone(), kwargs);
298
299 check_call!(&args, kwargs);
301
302 check_call!(args.clone().unbind(), kwargs);
304
305 check_call!(args.as_unbound(), kwargs);
307
308 check_call!(args.as_borrowed(), kwargs);
310 })
311 }
312}