| 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 | |