1 | //! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information.
|
2 |
|
3 | use crate::ffi_ptr_ext::FfiPtrExt as _;
|
4 | use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple};
|
5 | use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult};
|
6 |
|
7 | pub(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 | /// convertable 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 ouside 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 | #[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 | )]
|
54 | pub 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 |
|
83 | impl<'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: Bound<'_, PyTuple> = 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: Bound<'_, PyTuple> = self.into_pyobject_or_pyerr(function.py())?;
|
100 | args.call_positional(function, token)
|
101 | }
|
102 | }
|
103 |
|
104 | impl<'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 |
|
125 | impl<'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 |
|
146 | impl<'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 |
|
169 | impl<'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 |
|
192 | impl<'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" )]
|
221 | mod 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 | // Bound<'py, PyTuple>
|
262 | check_call!(args.clone(), kwargs);
|
263 |
|
264 | // &Bound<'py, PyTuple>
|
265 | check_call!(&args, kwargs);
|
266 |
|
267 | // Py<PyTuple>
|
268 | check_call!(args.clone().unbind(), kwargs);
|
269 |
|
270 | // &Py<PyTuple>
|
271 | check_call!(&args.as_unbound(), kwargs);
|
272 |
|
273 | // Borrowed<'_, '_, PyTuple>
|
274 | 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 | // Bound<'py, PyTuple>
|
300 | check_call!(args.clone(), kwargs);
|
301 |
|
302 | // &Bound<'py, PyTuple>
|
303 | check_call!(&args, kwargs);
|
304 |
|
305 | // Py<PyTuple>
|
306 | check_call!(args.clone().unbind(), kwargs);
|
307 |
|
308 | // &Py<PyTuple>
|
309 | check_call!(args.as_unbound(), kwargs);
|
310 |
|
311 | // Borrowed<'_, '_, PyTuple>
|
312 | check_call!(args.as_borrowed(), kwargs);
|
313 | })
|
314 | }
|
315 | }
|
316 | |