1 | use crate::{ |
2 | exceptions::PyTypeError, |
3 | ffi, |
4 | pyclass::boolean_struct::False, |
5 | types::{PyDict, PyString, PyTuple}, |
6 | FromPyObject, PyAny, PyClass, PyErr, PyRef, PyRefMut, PyResult, Python, |
7 | }; |
8 | |
9 | /// A trait which is used to help PyO3 macros extract function arguments. |
10 | /// |
11 | /// `#[pyclass]` structs need to extract as `PyRef<T>` and `PyRefMut<T>` |
12 | /// wrappers rather than extracting `&T` and `&mut T` directly. The `Holder` type is used |
13 | /// to hold these temporary wrappers - the way the macro is constructed, these wrappers |
14 | /// will be dropped as soon as the pyfunction call ends. |
15 | /// |
16 | /// There exists a trivial blanket implementation for `T: FromPyObject` with `Holder = ()`. |
17 | pub trait PyFunctionArgument<'a, 'py>: Sized + 'a { |
18 | type Holder: FunctionArgumentHolder; |
19 | fn extract(obj: &'py PyAny, holder: &'a mut Self::Holder) -> PyResult<Self>; |
20 | } |
21 | |
22 | impl<'a, 'py, T> PyFunctionArgument<'a, 'py> for T |
23 | where |
24 | T: FromPyObject<'py> + 'a, |
25 | { |
26 | type Holder = (); |
27 | |
28 | #[inline ] |
29 | fn extract(obj: &'py PyAny, _: &'a mut ()) -> PyResult<Self> { |
30 | obj.extract() |
31 | } |
32 | } |
33 | |
34 | /// Trait for types which can be a function argument holder - they should |
35 | /// to be able to const-initialize to an empty value. |
36 | pub trait FunctionArgumentHolder: Sized { |
37 | const INIT: Self; |
38 | } |
39 | |
40 | impl FunctionArgumentHolder for () { |
41 | const INIT: Self = (); |
42 | } |
43 | |
44 | impl<T> FunctionArgumentHolder for Option<T> { |
45 | const INIT: Self = None; |
46 | } |
47 | |
48 | #[inline ] |
49 | pub fn extract_pyclass_ref<'a, 'py: 'a, T: PyClass>( |
50 | obj: &'py PyAny, |
51 | holder: &'a mut Option<PyRef<'py, T>>, |
52 | ) -> PyResult<&'a T> { |
53 | Ok(&*holder.insert(obj.extract()?)) |
54 | } |
55 | |
56 | #[inline ] |
57 | pub fn extract_pyclass_ref_mut<'a, 'py: 'a, T: PyClass<Frozen = False>>( |
58 | obj: &'py PyAny, |
59 | holder: &'a mut Option<PyRefMut<'py, T>>, |
60 | ) -> PyResult<&'a mut T> { |
61 | Ok(&mut *holder.insert(obj.extract()?)) |
62 | } |
63 | |
64 | /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. |
65 | #[doc (hidden)] |
66 | pub fn extract_argument<'a, 'py, T>( |
67 | obj: &'py PyAny, |
68 | holder: &'a mut T::Holder, |
69 | arg_name: &str, |
70 | ) -> PyResult<T> |
71 | where |
72 | T: PyFunctionArgument<'a, 'py>, |
73 | { |
74 | match PyFunctionArgument::extract(obj, holder) { |
75 | Ok(value: T) => Ok(value), |
76 | Err(e: PyErr) => Err(argument_extraction_error(obj.py(), arg_name, error:e)), |
77 | } |
78 | } |
79 | |
80 | /// Alternative to [`extract_argument`] used for `Option<T>` arguments. This is necessary because Option<&T> |
81 | /// does not implement `PyFunctionArgument` for `T: PyClass`. |
82 | #[doc (hidden)] |
83 | pub fn extract_optional_argument<'a, 'py, T>( |
84 | obj: Option<&'py PyAny>, |
85 | holder: &'a mut T::Holder, |
86 | arg_name: &str, |
87 | default: fn() -> Option<T>, |
88 | ) -> PyResult<Option<T>> |
89 | where |
90 | T: PyFunctionArgument<'a, 'py>, |
91 | { |
92 | match obj { |
93 | Some(obj: &PyAny) => { |
94 | if obj.is_none() { |
95 | // Explicit `None` will result in None being used as the function argument |
96 | Ok(None) |
97 | } else { |
98 | extract_argument(obj, holder, arg_name).map(op:Some) |
99 | } |
100 | } |
101 | _ => Ok(default()), |
102 | } |
103 | } |
104 | |
105 | /// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation. |
106 | #[doc (hidden)] |
107 | pub fn extract_argument_with_default<'a, 'py, T>( |
108 | obj: Option<&'py PyAny>, |
109 | holder: &'a mut T::Holder, |
110 | arg_name: &str, |
111 | default: fn() -> T, |
112 | ) -> PyResult<T> |
113 | where |
114 | T: PyFunctionArgument<'a, 'py>, |
115 | { |
116 | match obj { |
117 | Some(obj: &PyAny) => extract_argument(obj, holder, arg_name), |
118 | None => Ok(default()), |
119 | } |
120 | } |
121 | |
122 | /// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation. |
123 | #[doc (hidden)] |
124 | pub fn from_py_with<'py, T>( |
125 | obj: &'py PyAny, |
126 | arg_name: &str, |
127 | extractor: fn(&'py PyAny) -> PyResult<T>, |
128 | ) -> PyResult<T> { |
129 | match extractor(obj) { |
130 | Ok(value: T) => Ok(value), |
131 | Err(e: PyErr) => Err(argument_extraction_error(obj.py(), arg_name, error:e)), |
132 | } |
133 | } |
134 | |
135 | /// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation and also a default value. |
136 | #[doc (hidden)] |
137 | pub fn from_py_with_with_default<'py, T>( |
138 | obj: Option<&'py PyAny>, |
139 | arg_name: &str, |
140 | extractor: fn(&'py PyAny) -> PyResult<T>, |
141 | default: fn() -> T, |
142 | ) -> PyResult<T> { |
143 | match obj { |
144 | Some(obj: &PyAny) => from_py_with(obj, arg_name, extractor), |
145 | None => Ok(default()), |
146 | } |
147 | } |
148 | |
149 | /// Adds the argument name to the error message of an error which occurred during argument extraction. |
150 | /// |
151 | /// Only modifies TypeError. (Cannot guarantee all exceptions have constructors from |
152 | /// single string.) |
153 | #[doc (hidden)] |
154 | #[cold ] |
155 | pub fn argument_extraction_error(py: Python<'_>, arg_name: &str, error: PyErr) -> PyErr { |
156 | if error.get_type(py).is(py.get_type::<PyTypeError>()) { |
157 | let remapped_error: PyErr = |
158 | PyTypeError::new_err(args:format!("argument ' {}': {}" , arg_name, error.value(py))); |
159 | remapped_error.set_cause(py, error.cause(py)); |
160 | remapped_error |
161 | } else { |
162 | error |
163 | } |
164 | } |
165 | |
166 | /// Unwraps the Option<&PyAny> produced by the FunctionDescription `extract_arguments_` methods. |
167 | /// They check if required methods are all provided. |
168 | /// |
169 | /// # Safety |
170 | /// `argument` must not be `None` |
171 | #[doc (hidden)] |
172 | #[inline ] |
173 | pub unsafe fn unwrap_required_argument(argument: Option<&PyAny>) -> &PyAny { |
174 | match argument { |
175 | Some(value: &PyAny) => value, |
176 | #[cfg (debug_assertions)] |
177 | None => unreachable!("required method argument was not extracted" ), |
178 | #[cfg (not(debug_assertions))] |
179 | None => std::hint::unreachable_unchecked(), |
180 | } |
181 | } |
182 | |
183 | pub struct KeywordOnlyParameterDescription { |
184 | pub name: &'static str, |
185 | pub required: bool, |
186 | } |
187 | |
188 | /// Function argument specification for a `#[pyfunction]` or `#[pymethod]`. |
189 | pub struct FunctionDescription { |
190 | pub cls_name: Option<&'static str>, |
191 | pub func_name: &'static str, |
192 | pub positional_parameter_names: &'static [&'static str], |
193 | pub positional_only_parameters: usize, |
194 | pub required_positional_parameters: usize, |
195 | pub keyword_only_parameters: &'static [KeywordOnlyParameterDescription], |
196 | } |
197 | |
198 | impl FunctionDescription { |
199 | fn full_name(&self) -> String { |
200 | if let Some(cls_name) = self.cls_name { |
201 | format!(" {}. {}()" , cls_name, self.func_name) |
202 | } else { |
203 | format!(" {}()" , self.func_name) |
204 | } |
205 | } |
206 | |
207 | /// Equivalent of `extract_arguments_tuple_dict` which uses the Python C-API "fastcall" convention. |
208 | /// |
209 | /// # Safety |
210 | /// - `args` must be a pointer to a C-style array of valid `ffi::PyObject` pointers, or NULL. |
211 | /// - `kwnames` must be a pointer to a PyTuple, or NULL. |
212 | /// - `nargs + kwnames.len()` is the total length of the `args` array. |
213 | #[cfg (not(Py_LIMITED_API))] |
214 | pub unsafe fn extract_arguments_fastcall<'py, V, K>( |
215 | &self, |
216 | py: Python<'py>, |
217 | args: *const *mut ffi::PyObject, |
218 | nargs: ffi::Py_ssize_t, |
219 | kwnames: *mut ffi::PyObject, |
220 | output: &mut [Option<&'py PyAny>], |
221 | ) -> PyResult<(V::Varargs, K::Varkeywords)> |
222 | where |
223 | V: VarargsHandler<'py>, |
224 | K: VarkeywordsHandler<'py>, |
225 | { |
226 | let num_positional_parameters = self.positional_parameter_names.len(); |
227 | |
228 | debug_assert!(nargs >= 0); |
229 | debug_assert!(self.positional_only_parameters <= num_positional_parameters); |
230 | debug_assert!(self.required_positional_parameters <= num_positional_parameters); |
231 | debug_assert_eq!( |
232 | output.len(), |
233 | num_positional_parameters + self.keyword_only_parameters.len() |
234 | ); |
235 | |
236 | // Handle positional arguments |
237 | // Safety: Option<&PyAny> has the same memory layout as `*mut ffi::PyObject` |
238 | let args: *const Option<&PyAny> = args.cast(); |
239 | let positional_args_provided = nargs as usize; |
240 | let remaining_positional_args = if args.is_null() { |
241 | debug_assert_eq!(positional_args_provided, 0); |
242 | &[] |
243 | } else { |
244 | // Can consume at most the number of positional parameters in the function definition, |
245 | // the rest are varargs. |
246 | let positional_args_to_consume = |
247 | num_positional_parameters.min(positional_args_provided); |
248 | let (positional_parameters, remaining) = |
249 | std::slice::from_raw_parts(args, positional_args_provided) |
250 | .split_at(positional_args_to_consume); |
251 | output[..positional_args_to_consume].copy_from_slice(positional_parameters); |
252 | remaining |
253 | }; |
254 | let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?; |
255 | |
256 | // Handle keyword arguments |
257 | let mut varkeywords = K::Varkeywords::default(); |
258 | if let Some(kwnames) = py.from_borrowed_ptr_or_opt::<PyTuple>(kwnames) { |
259 | // Safety: &PyAny has the same memory layout as `*mut ffi::PyObject` |
260 | let kwargs = |
261 | ::std::slice::from_raw_parts((args as *const &PyAny).offset(nargs), kwnames.len()); |
262 | |
263 | self.handle_kwargs::<K, _>( |
264 | kwnames.iter().zip(kwargs.iter().copied()), |
265 | &mut varkeywords, |
266 | num_positional_parameters, |
267 | output, |
268 | )? |
269 | } |
270 | |
271 | // Once all inputs have been processed, check that all required arguments have been provided. |
272 | |
273 | self.ensure_no_missing_required_positional_arguments(output, positional_args_provided)?; |
274 | self.ensure_no_missing_required_keyword_arguments(output)?; |
275 | |
276 | Ok((varargs, varkeywords)) |
277 | } |
278 | |
279 | /// Extracts the `args` and `kwargs` provided into `output`, according to this function |
280 | /// definition. |
281 | /// |
282 | /// `output` must have the same length as this function has positional and keyword-only |
283 | /// parameters (as per the `positional_parameter_names` and `keyword_only_parameters` |
284 | /// respectively). |
285 | /// |
286 | /// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`. |
287 | /// |
288 | /// # Safety |
289 | /// - `args` must be a pointer to a PyTuple. |
290 | /// - `kwargs` must be a pointer to a PyDict, or NULL. |
291 | pub unsafe fn extract_arguments_tuple_dict<'py, V, K>( |
292 | &self, |
293 | py: Python<'py>, |
294 | args: *mut ffi::PyObject, |
295 | kwargs: *mut ffi::PyObject, |
296 | output: &mut [Option<&'py PyAny>], |
297 | ) -> PyResult<(V::Varargs, K::Varkeywords)> |
298 | where |
299 | V: VarargsHandler<'py>, |
300 | K: VarkeywordsHandler<'py>, |
301 | { |
302 | let args = py.from_borrowed_ptr::<PyTuple>(args); |
303 | let kwargs: ::std::option::Option<&PyDict> = py.from_borrowed_ptr_or_opt(kwargs); |
304 | |
305 | let num_positional_parameters = self.positional_parameter_names.len(); |
306 | |
307 | debug_assert!(self.positional_only_parameters <= num_positional_parameters); |
308 | debug_assert!(self.required_positional_parameters <= num_positional_parameters); |
309 | debug_assert_eq!( |
310 | output.len(), |
311 | num_positional_parameters + self.keyword_only_parameters.len() |
312 | ); |
313 | |
314 | // Copy positional arguments into output |
315 | for (i, arg) in args.iter().take(num_positional_parameters).enumerate() { |
316 | output[i] = Some(arg); |
317 | } |
318 | |
319 | // If any arguments remain, push them to varargs (if possible) or error |
320 | let varargs = V::handle_varargs_tuple(args, self)?; |
321 | |
322 | // Handle keyword arguments |
323 | let mut varkeywords = K::Varkeywords::default(); |
324 | if let Some(kwargs) = kwargs { |
325 | self.handle_kwargs::<K, _>(kwargs, &mut varkeywords, num_positional_parameters, output)? |
326 | } |
327 | |
328 | // Once all inputs have been processed, check that all required arguments have been provided. |
329 | |
330 | self.ensure_no_missing_required_positional_arguments(output, args.len())?; |
331 | self.ensure_no_missing_required_keyword_arguments(output)?; |
332 | |
333 | Ok((varargs, varkeywords)) |
334 | } |
335 | |
336 | #[inline ] |
337 | fn handle_kwargs<'py, K, I>( |
338 | &self, |
339 | kwargs: I, |
340 | varkeywords: &mut K::Varkeywords, |
341 | num_positional_parameters: usize, |
342 | output: &mut [Option<&'py PyAny>], |
343 | ) -> PyResult<()> |
344 | where |
345 | K: VarkeywordsHandler<'py>, |
346 | I: IntoIterator<Item = (&'py PyAny, &'py PyAny)>, |
347 | { |
348 | debug_assert_eq!( |
349 | num_positional_parameters, |
350 | self.positional_parameter_names.len() |
351 | ); |
352 | debug_assert_eq!( |
353 | output.len(), |
354 | num_positional_parameters + self.keyword_only_parameters.len() |
355 | ); |
356 | let mut positional_only_keyword_arguments = Vec::new(); |
357 | for (kwarg_name_py, value) in kwargs { |
358 | // All keyword arguments should be UTF-8 strings, but we'll check, just in case. |
359 | // If it isn't, then it will be handled below as a varkeyword (which may raise an |
360 | // error if this function doesn't accept **kwargs). Rust source is always UTF-8 |
361 | // and so all argument names in `#[pyfunction]` signature must be UTF-8. |
362 | if let Ok(kwarg_name) = kwarg_name_py.downcast::<PyString>()?.to_str() { |
363 | // Try to place parameter in keyword only parameters |
364 | if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) { |
365 | if output[i + num_positional_parameters] |
366 | .replace(value) |
367 | .is_some() |
368 | { |
369 | return Err(self.multiple_values_for_argument(kwarg_name)); |
370 | } |
371 | continue; |
372 | } |
373 | |
374 | // Repeat for positional parameters |
375 | if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) { |
376 | if i < self.positional_only_parameters { |
377 | // If accepting **kwargs, then it's allowed for the name of the |
378 | // kwarg to conflict with a postional-only argument - the value |
379 | // will go into **kwargs anyway. |
380 | if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() { |
381 | positional_only_keyword_arguments.push(kwarg_name); |
382 | } |
383 | } else if output[i].replace(value).is_some() { |
384 | return Err(self.multiple_values_for_argument(kwarg_name)); |
385 | } |
386 | continue; |
387 | } |
388 | }; |
389 | |
390 | K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)? |
391 | } |
392 | |
393 | if !positional_only_keyword_arguments.is_empty() { |
394 | return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments)); |
395 | } |
396 | |
397 | Ok(()) |
398 | } |
399 | |
400 | #[inline ] |
401 | fn find_keyword_parameter_in_positional(&self, kwarg_name: &str) -> Option<usize> { |
402 | self.positional_parameter_names |
403 | .iter() |
404 | .position(|¶m_name| param_name == kwarg_name) |
405 | } |
406 | |
407 | #[inline ] |
408 | fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> { |
409 | // Compare the keyword name against each parameter in turn. This is exactly the same method |
410 | // which CPython uses to map keyword names. Although it's O(num_parameters), the number of |
411 | // parameters is expected to be small so it's not worth constructing a mapping. |
412 | self.keyword_only_parameters |
413 | .iter() |
414 | .position(|param_desc| param_desc.name == kwarg_name) |
415 | } |
416 | |
417 | #[inline ] |
418 | fn ensure_no_missing_required_positional_arguments( |
419 | &self, |
420 | output: &[Option<&PyAny>], |
421 | positional_args_provided: usize, |
422 | ) -> PyResult<()> { |
423 | if positional_args_provided < self.required_positional_parameters { |
424 | for out in &output[positional_args_provided..self.required_positional_parameters] { |
425 | if out.is_none() { |
426 | return Err(self.missing_required_positional_arguments(output)); |
427 | } |
428 | } |
429 | } |
430 | Ok(()) |
431 | } |
432 | |
433 | #[inline ] |
434 | fn ensure_no_missing_required_keyword_arguments( |
435 | &self, |
436 | output: &[Option<&PyAny>], |
437 | ) -> PyResult<()> { |
438 | let keyword_output = &output[self.positional_parameter_names.len()..]; |
439 | for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) { |
440 | if param.required && out.is_none() { |
441 | return Err(self.missing_required_keyword_arguments(keyword_output)); |
442 | } |
443 | } |
444 | Ok(()) |
445 | } |
446 | |
447 | #[cold ] |
448 | fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr { |
449 | let was = if args_provided == 1 { "was" } else { "were" }; |
450 | let msg = if self.required_positional_parameters != self.positional_parameter_names.len() { |
451 | format!( |
452 | " {} takes from {} to {} positional arguments but {} {} given" , |
453 | self.full_name(), |
454 | self.required_positional_parameters, |
455 | self.positional_parameter_names.len(), |
456 | args_provided, |
457 | was |
458 | ) |
459 | } else { |
460 | format!( |
461 | " {} takes {} positional arguments but {} {} given" , |
462 | self.full_name(), |
463 | self.positional_parameter_names.len(), |
464 | args_provided, |
465 | was |
466 | ) |
467 | }; |
468 | PyTypeError::new_err(msg) |
469 | } |
470 | |
471 | #[cold ] |
472 | fn multiple_values_for_argument(&self, argument: &str) -> PyErr { |
473 | PyTypeError::new_err(format!( |
474 | " {} got multiple values for argument ' {}'" , |
475 | self.full_name(), |
476 | argument |
477 | )) |
478 | } |
479 | |
480 | #[cold ] |
481 | fn unexpected_keyword_argument(&self, argument: &PyAny) -> PyErr { |
482 | PyTypeError::new_err(format!( |
483 | " {} got an unexpected keyword argument ' {}'" , |
484 | self.full_name(), |
485 | argument |
486 | )) |
487 | } |
488 | |
489 | #[cold ] |
490 | fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr { |
491 | let mut msg = format!( |
492 | " {} got some positional-only arguments passed as keyword arguments: " , |
493 | self.full_name() |
494 | ); |
495 | push_parameter_list(&mut msg, parameter_names); |
496 | PyTypeError::new_err(msg) |
497 | } |
498 | |
499 | #[cold ] |
500 | fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr { |
501 | let arguments = if parameter_names.len() == 1 { |
502 | "argument" |
503 | } else { |
504 | "arguments" |
505 | }; |
506 | let mut msg = format!( |
507 | " {} missing {} required {} {}: " , |
508 | self.full_name(), |
509 | parameter_names.len(), |
510 | argument_type, |
511 | arguments, |
512 | ); |
513 | push_parameter_list(&mut msg, parameter_names); |
514 | PyTypeError::new_err(msg) |
515 | } |
516 | |
517 | #[cold ] |
518 | fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<&PyAny>]) -> PyErr { |
519 | debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len()); |
520 | |
521 | let missing_keyword_only_arguments: Vec<_> = self |
522 | .keyword_only_parameters |
523 | .iter() |
524 | .zip(keyword_outputs) |
525 | .filter_map(|(keyword_desc, out)| { |
526 | if keyword_desc.required && out.is_none() { |
527 | Some(keyword_desc.name) |
528 | } else { |
529 | None |
530 | } |
531 | }) |
532 | .collect(); |
533 | |
534 | debug_assert!(!missing_keyword_only_arguments.is_empty()); |
535 | self.missing_required_arguments("keyword" , &missing_keyword_only_arguments) |
536 | } |
537 | |
538 | #[cold ] |
539 | fn missing_required_positional_arguments(&self, output: &[Option<&PyAny>]) -> PyErr { |
540 | let missing_positional_arguments: Vec<_> = self |
541 | .positional_parameter_names |
542 | .iter() |
543 | .take(self.required_positional_parameters) |
544 | .zip(output) |
545 | .filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None }) |
546 | .collect(); |
547 | |
548 | debug_assert!(!missing_positional_arguments.is_empty()); |
549 | self.missing_required_arguments("positional" , &missing_positional_arguments) |
550 | } |
551 | } |
552 | |
553 | /// A trait used to control whether to accept varargs in FunctionDescription::extract_argument_(method) functions. |
554 | pub trait VarargsHandler<'py> { |
555 | type Varargs; |
556 | /// Called by `FunctionDescription::extract_arguments_fastcall` with any additional arguments. |
557 | fn handle_varargs_fastcall( |
558 | py: Python<'py>, |
559 | varargs: &[Option<&PyAny>], |
560 | function_description: &FunctionDescription, |
561 | ) -> PyResult<Self::Varargs>; |
562 | /// Called by `FunctionDescription::extract_arguments_tuple_dict` with the original tuple. |
563 | /// |
564 | /// Additional arguments are those in the tuple slice starting from `function_description.positional_parameter_names.len()`. |
565 | fn handle_varargs_tuple( |
566 | args: &'py PyTuple, |
567 | function_description: &FunctionDescription, |
568 | ) -> PyResult<Self::Varargs>; |
569 | } |
570 | |
571 | /// Marker struct which indicates varargs are not allowed. |
572 | pub struct NoVarargs; |
573 | |
574 | impl<'py> VarargsHandler<'py> for NoVarargs { |
575 | type Varargs = (); |
576 | |
577 | #[inline ] |
578 | fn handle_varargs_fastcall( |
579 | _py: Python<'py>, |
580 | varargs: &[Option<&PyAny>], |
581 | function_description: &FunctionDescription, |
582 | ) -> PyResult<Self::Varargs> { |
583 | let extra_arguments = varargs.len(); |
584 | if extra_arguments > 0 { |
585 | return Err(function_description.too_many_positional_arguments( |
586 | function_description.positional_parameter_names.len() + extra_arguments, |
587 | )); |
588 | } |
589 | Ok(()) |
590 | } |
591 | |
592 | #[inline ] |
593 | fn handle_varargs_tuple( |
594 | args: &'py PyTuple, |
595 | function_description: &FunctionDescription, |
596 | ) -> PyResult<Self::Varargs> { |
597 | let positional_parameter_count = function_description.positional_parameter_names.len(); |
598 | let provided_args_count = args.len(); |
599 | if provided_args_count <= positional_parameter_count { |
600 | Ok(()) |
601 | } else { |
602 | Err(function_description.too_many_positional_arguments(provided_args_count)) |
603 | } |
604 | } |
605 | } |
606 | |
607 | /// Marker struct which indicates varargs should be collected into a `PyTuple`. |
608 | pub struct TupleVarargs; |
609 | |
610 | impl<'py> VarargsHandler<'py> for TupleVarargs { |
611 | type Varargs = &'py PyTuple; |
612 | #[inline ] |
613 | fn handle_varargs_fastcall( |
614 | py: Python<'py>, |
615 | varargs: &[Option<&PyAny>], |
616 | _function_description: &FunctionDescription, |
617 | ) -> PyResult<Self::Varargs> { |
618 | Ok(PyTuple::new(py, elements:varargs)) |
619 | } |
620 | |
621 | #[inline ] |
622 | fn handle_varargs_tuple( |
623 | args: &'py PyTuple, |
624 | function_description: &FunctionDescription, |
625 | ) -> PyResult<Self::Varargs> { |
626 | let positional_parameters: usize = function_description.positional_parameter_names.len(); |
627 | Ok(args.get_slice(low:positional_parameters, high:args.len())) |
628 | } |
629 | } |
630 | |
631 | /// A trait used to control whether to accept varkeywords in FunctionDescription::extract_argument_(method) functions. |
632 | pub trait VarkeywordsHandler<'py> { |
633 | type Varkeywords: Default; |
634 | fn handle_varkeyword( |
635 | varkeywords: &mut Self::Varkeywords, |
636 | name: &'py PyAny, |
637 | value: &'py PyAny, |
638 | function_description: &FunctionDescription, |
639 | ) -> PyResult<()>; |
640 | } |
641 | |
642 | /// Marker struct which indicates unknown keywords are not permitted. |
643 | pub struct NoVarkeywords; |
644 | |
645 | impl<'py> VarkeywordsHandler<'py> for NoVarkeywords { |
646 | type Varkeywords = (); |
647 | #[inline ] |
648 | fn handle_varkeyword( |
649 | _varkeywords: &mut Self::Varkeywords, |
650 | name: &'py PyAny, |
651 | _value: &'py PyAny, |
652 | function_description: &FunctionDescription, |
653 | ) -> PyResult<()> { |
654 | Err(function_description.unexpected_keyword_argument(name)) |
655 | } |
656 | } |
657 | |
658 | /// Marker struct which indicates unknown keywords should be collected into a `PyDict`. |
659 | pub struct DictVarkeywords; |
660 | |
661 | impl<'py> VarkeywordsHandler<'py> for DictVarkeywords { |
662 | type Varkeywords = Option<&'py PyDict>; |
663 | #[inline ] |
664 | fn handle_varkeyword( |
665 | varkeywords: &mut Self::Varkeywords, |
666 | name: &'py PyAny, |
667 | value: &'py PyAny, |
668 | _function_description: &FunctionDescription, |
669 | ) -> PyResult<()> { |
670 | varkeywords |
671 | .get_or_insert_with(|| PyDict::new(name.py())) |
672 | .set_item(key:name, value) |
673 | } |
674 | } |
675 | |
676 | fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) { |
677 | for (i: usize, parameter: &&str) in parameter_names.iter().enumerate() { |
678 | if i != 0 { |
679 | if parameter_names.len() > 2 { |
680 | msg.push(ch:',' ); |
681 | } |
682 | |
683 | if i == parameter_names.len() - 1 { |
684 | msg.push_str(string:" and " ) |
685 | } else { |
686 | msg.push(ch:' ' ) |
687 | } |
688 | } |
689 | |
690 | msg.push(ch:' \'' ); |
691 | msg.push_str(string:parameter); |
692 | msg.push(ch:' \'' ); |
693 | } |
694 | } |
695 | |
696 | #[cfg (test)] |
697 | mod tests { |
698 | use crate::{ |
699 | types::{IntoPyDict, PyTuple}, |
700 | PyAny, Python, ToPyObject, |
701 | }; |
702 | |
703 | use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords}; |
704 | |
705 | #[test ] |
706 | fn unexpected_keyword_argument() { |
707 | let function_description = FunctionDescription { |
708 | cls_name: None, |
709 | func_name: "example" , |
710 | positional_parameter_names: &[], |
711 | positional_only_parameters: 0, |
712 | required_positional_parameters: 0, |
713 | keyword_only_parameters: &[], |
714 | }; |
715 | |
716 | Python::with_gil(|py| { |
717 | let args = PyTuple::new(py, Vec::<&PyAny>::new()); |
718 | let kwargs = [("foo" .to_object(py).into_ref(py), 0u8)].into_py_dict(py); |
719 | let err = unsafe { |
720 | function_description |
721 | .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>( |
722 | py, |
723 | args.as_ptr(), |
724 | kwargs.as_ptr(), |
725 | &mut [], |
726 | ) |
727 | .unwrap_err() |
728 | }; |
729 | assert_eq!( |
730 | err.to_string(), |
731 | "TypeError: example() got an unexpected keyword argument 'foo'" |
732 | ); |
733 | }) |
734 | } |
735 | |
736 | #[test ] |
737 | fn keyword_not_string() { |
738 | let function_description = FunctionDescription { |
739 | cls_name: None, |
740 | func_name: "example" , |
741 | positional_parameter_names: &[], |
742 | positional_only_parameters: 0, |
743 | required_positional_parameters: 0, |
744 | keyword_only_parameters: &[], |
745 | }; |
746 | |
747 | Python::with_gil(|py| { |
748 | let args = PyTuple::new(py, Vec::<&PyAny>::new()); |
749 | let kwargs = [(1u8.to_object(py).into_ref(py), 1u8)].into_py_dict(py); |
750 | let err = unsafe { |
751 | function_description |
752 | .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>( |
753 | py, |
754 | args.as_ptr(), |
755 | kwargs.as_ptr(), |
756 | &mut [], |
757 | ) |
758 | .unwrap_err() |
759 | }; |
760 | assert_eq!( |
761 | err.to_string(), |
762 | "TypeError: 'int' object cannot be converted to 'PyString'" |
763 | ); |
764 | }) |
765 | } |
766 | |
767 | #[test ] |
768 | fn missing_required_arguments() { |
769 | let function_description = FunctionDescription { |
770 | cls_name: None, |
771 | func_name: "example" , |
772 | positional_parameter_names: &["foo" , "bar" ], |
773 | positional_only_parameters: 0, |
774 | required_positional_parameters: 2, |
775 | keyword_only_parameters: &[], |
776 | }; |
777 | |
778 | Python::with_gil(|py| { |
779 | let args = PyTuple::new(py, Vec::<&PyAny>::new()); |
780 | let mut output = [None, None]; |
781 | let err = unsafe { |
782 | function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>( |
783 | py, |
784 | args.as_ptr(), |
785 | std::ptr::null_mut(), |
786 | &mut output, |
787 | ) |
788 | } |
789 | .unwrap_err(); |
790 | assert_eq!( |
791 | err.to_string(), |
792 | "TypeError: example() missing 2 required positional arguments: 'foo' and 'bar'" |
793 | ); |
794 | }) |
795 | } |
796 | |
797 | #[test ] |
798 | fn push_parameter_list_empty() { |
799 | let mut s = String::new(); |
800 | push_parameter_list(&mut s, &[]); |
801 | assert_eq!(&s, "" ); |
802 | } |
803 | |
804 | #[test ] |
805 | fn push_parameter_list_one() { |
806 | let mut s = String::new(); |
807 | push_parameter_list(&mut s, &["a" ]); |
808 | assert_eq!(&s, "'a'" ); |
809 | } |
810 | |
811 | #[test ] |
812 | fn push_parameter_list_two() { |
813 | let mut s = String::new(); |
814 | push_parameter_list(&mut s, &["a" , "b" ]); |
815 | assert_eq!(&s, "'a' and 'b'" ); |
816 | } |
817 | |
818 | #[test ] |
819 | fn push_parameter_list_three() { |
820 | let mut s = String::new(); |
821 | push_parameter_list(&mut s, &["a" , "b" , "c" ]); |
822 | assert_eq!(&s, "'a', 'b', and 'c'" ); |
823 | } |
824 | |
825 | #[test ] |
826 | fn push_parameter_list_four() { |
827 | let mut s = String::new(); |
828 | push_parameter_list(&mut s, &["a" , "b" , "c" , "d" ]); |
829 | assert_eq!(&s, "'a', 'b', 'c', and 'd'" ); |
830 | } |
831 | } |
832 | |