pyo3/
call.rs

1//! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information.
2
3use 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/// This trait marks types that can be used as arguments to Python function
22/// calls.
23///
24/// This trait is currently implemented for Rust tuple (up to a size of 12),
25/// [`Bound<'py, PyTuple>`] and [`Py<PyTuple>`]. Custom types that are
26/// convertible to `PyTuple` via `IntoPyObject` need to do so before passing it
27/// to `call`.
28///
29/// This trait is not intended to used by downstream crates directly. As such it
30/// has no publicly available methods and cannot be implemented outside of
31/// `pyo3`. The corresponding public API is available through [`call`]
32/// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`].
33///
34/// # What is `PyCallArgs` used for?
35/// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in
36/// the most optimal way for the current build configuration. Certain types,
37/// such as Rust tuples, do allow the usage of a faster calling convention of
38/// the Python interpreter (if available). More types that may take advantage
39/// from this may be added in the future.
40///
41/// [`call0`]: crate::types::PyAnyMethods::call0
42/// [`call1`]: crate::types::PyAnyMethods::call1
43/// [`call`]: crate::types::PyAnyMethods::call
44/// [`PyAnyMethods`]: crate::types::PyAnyMethods
45#[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            // Bound<'py, PyTuple>
259            check_call!(args.clone(), kwargs);
260
261            // &Bound<'py, PyTuple>
262            check_call!(&args, kwargs);
263
264            // Py<PyTuple>
265            check_call!(args.clone().unbind(), kwargs);
266
267            // &Py<PyTuple>
268            check_call!(&args.as_unbound(), kwargs);
269
270            // Borrowed<'_, '_, PyTuple>
271            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            // Bound<'py, PyTuple>
297            check_call!(args.clone(), kwargs);
298
299            // &Bound<'py, PyTuple>
300            check_call!(&args, kwargs);
301
302            // Py<PyTuple>
303            check_call!(args.clone().unbind(), kwargs);
304
305            // &Py<PyTuple>
306            check_call!(args.as_unbound(), kwargs);
307
308            // Borrowed<'_, '_, PyTuple>
309            check_call!(args.as_borrowed(), kwargs);
310        })
311    }
312}