1use crate::{
2 conversion::FromPyObjectBound,
3 exceptions::PyTypeError,
4 ffi,
5 pyclass::boolean_struct::False,
6 types::{any::PyAnyMethods, dict::PyDictMethods, tuple::PyTupleMethods, PyDict, PyTuple},
7 Borrowed, Bound, PyAny, PyClass, PyErr, PyRef, PyRefMut, PyResult, PyTypeCheck, Python,
8};
9
10type PyArg<'py> = Borrowed<'py, 'py, PyAny>;
14
15pub trait PyFunctionArgument<'a, 'py, const IS_OPTION: bool>: Sized + 'a {
24 type Holder: FunctionArgumentHolder;
25 fn extract(obj: &'a Bound<'py, PyAny>, holder: &'a mut Self::Holder) -> PyResult<Self>;
26}
27
28impl<'a, 'py, T> PyFunctionArgument<'a, 'py, false> for T
29where
30 T: FromPyObjectBound<'a, 'py> + 'a,
31{
32 type Holder = ();
33
34 #[inline]
35 fn extract(obj: &'a Bound<'py, PyAny>, _: &'a mut ()) -> PyResult<Self> {
36 obj.extract()
37 }
38}
39
40impl<'a, 'py, T: 'py> PyFunctionArgument<'a, 'py, false> for &'a Bound<'py, T>
41where
42 T: PyTypeCheck,
43{
44 type Holder = ();
45
46 #[inline]
47 fn extract(obj: &'a Bound<'py, PyAny>, _: &'a mut ()) -> PyResult<Self> {
48 obj.downcast().map_err(Into::into)
49 }
50}
51
52impl<'a, 'py, T> PyFunctionArgument<'a, 'py, true> for Option<T>
53where
54 T: PyFunctionArgument<'a, 'py, false>, {
56 type Holder = T::Holder;
57
58 #[inline]
59 fn extract(obj: &'a Bound<'py, PyAny>, holder: &'a mut T::Holder) -> PyResult<Self> {
60 if obj.is_none() {
61 Ok(None)
62 } else {
63 Ok(Some(T::extract(obj, holder)?))
64 }
65 }
66}
67
68#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
69impl<'a> PyFunctionArgument<'a, '_, false> for &'a str {
70 type Holder = Option<std::borrow::Cow<'a, str>>;
71
72 #[inline]
73 fn extract(
74 obj: &'a Bound<'_, PyAny>,
75 holder: &'a mut Option<std::borrow::Cow<'a, str>>,
76 ) -> PyResult<Self> {
77 Ok(holder.insert(obj.extract()?))
78 }
79}
80
81pub trait FunctionArgumentHolder: Sized {
84 const INIT: Self;
85}
86
87impl FunctionArgumentHolder for () {
88 const INIT: Self = ();
89}
90
91impl<T> FunctionArgumentHolder for Option<T> {
92 const INIT: Self = None;
93}
94
95#[inline]
96pub fn extract_pyclass_ref<'a, 'py: 'a, T: PyClass>(
97 obj: &'a Bound<'py, PyAny>,
98 holder: &'a mut Option<PyRef<'py, T>>,
99) -> PyResult<&'a T> {
100 Ok(&*holder.insert(obj.extract()?))
101}
102
103#[inline]
104pub fn extract_pyclass_ref_mut<'a, 'py: 'a, T: PyClass<Frozen = False>>(
105 obj: &'a Bound<'py, PyAny>,
106 holder: &'a mut Option<PyRefMut<'py, T>>,
107) -> PyResult<&'a mut T> {
108 Ok(&mut *holder.insert(obj.extract()?))
109}
110
111#[doc(hidden)]
113pub fn extract_argument<'a, 'py, T, const IS_OPTION: bool>(
114 obj: &'a Bound<'py, PyAny>,
115 holder: &'a mut T::Holder,
116 arg_name: &str,
117) -> PyResult<T>
118where
119 T: PyFunctionArgument<'a, 'py, IS_OPTION>,
120{
121 match PyFunctionArgument::extract(obj, holder) {
122 Ok(value) => Ok(value),
123 Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
124 }
125}
126
127#[doc(hidden)]
130pub fn extract_optional_argument<'a, 'py, T, const IS_OPTION: bool>(
131 obj: Option<&'a Bound<'py, PyAny>>,
132 holder: &'a mut T::Holder,
133 arg_name: &str,
134 default: fn() -> Option<T>,
135) -> PyResult<Option<T>>
136where
137 T: PyFunctionArgument<'a, 'py, IS_OPTION>,
138{
139 match obj {
140 Some(obj) => {
141 if obj.is_none() {
142 Ok(None)
144 } else {
145 extract_argument(obj, holder, arg_name).map(Some)
146 }
147 }
148 _ => Ok(default()),
149 }
150}
151
152#[doc(hidden)]
154pub fn extract_argument_with_default<'a, 'py, T, const IS_OPTION: bool>(
155 obj: Option<&'a Bound<'py, PyAny>>,
156 holder: &'a mut T::Holder,
157 arg_name: &str,
158 default: fn() -> T,
159) -> PyResult<T>
160where
161 T: PyFunctionArgument<'a, 'py, IS_OPTION>,
162{
163 match obj {
164 Some(obj) => extract_argument(obj, holder, arg_name),
165 None => Ok(default()),
166 }
167}
168
169#[doc(hidden)]
171pub fn from_py_with<'a, 'py, T>(
172 obj: &'a Bound<'py, PyAny>,
173 arg_name: &str,
174 extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
175) -> PyResult<T> {
176 match extractor(obj) {
177 Ok(value) => Ok(value),
178 Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
179 }
180}
181
182#[doc(hidden)]
184pub fn from_py_with_with_default<'a, 'py, T>(
185 obj: Option<&'a Bound<'py, PyAny>>,
186 arg_name: &str,
187 extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
188 default: fn() -> T,
189) -> PyResult<T> {
190 match obj {
191 Some(obj) => from_py_with(obj, arg_name, extractor),
192 None => Ok(default()),
193 }
194}
195
196#[doc(hidden)]
201#[cold]
202pub fn argument_extraction_error(py: Python<'_>, arg_name: &str, error: PyErr) -> PyErr {
203 if error.get_type(py).is(&py.get_type::<PyTypeError>()) {
204 let remapped_error =
205 PyTypeError::new_err(format!("argument '{}': {}", arg_name, error.value(py)));
206 remapped_error.set_cause(py, error.cause(py));
207 remapped_error
208 } else {
209 error
210 }
211}
212
213#[doc(hidden)]
219#[inline]
220pub unsafe fn unwrap_required_argument<'a, 'py>(
221 argument: Option<&'a Bound<'py, PyAny>>,
222) -> &'a Bound<'py, PyAny> {
223 match argument {
224 Some(value) => value,
225 #[cfg(debug_assertions)]
226 None => unreachable!("required method argument was not extracted"),
227 #[cfg(not(debug_assertions))]
228 None => std::hint::unreachable_unchecked(),
229 }
230}
231
232pub struct KeywordOnlyParameterDescription {
233 pub name: &'static str,
234 pub required: bool,
235}
236
237pub struct FunctionDescription {
239 pub cls_name: Option<&'static str>,
240 pub func_name: &'static str,
241 pub positional_parameter_names: &'static [&'static str],
242 pub positional_only_parameters: usize,
243 pub required_positional_parameters: usize,
244 pub keyword_only_parameters: &'static [KeywordOnlyParameterDescription],
245}
246
247impl FunctionDescription {
248 fn full_name(&self) -> String {
249 if let Some(cls_name) = self.cls_name {
250 format!("{}.{}()", cls_name, self.func_name)
251 } else {
252 format!("{}()", self.func_name)
253 }
254 }
255
256 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
263 pub unsafe fn extract_arguments_fastcall<'py, V, K>(
264 &self,
265 py: Python<'py>,
266 args: *const *mut ffi::PyObject,
267 nargs: ffi::Py_ssize_t,
268 kwnames: *mut ffi::PyObject,
269 output: &mut [Option<PyArg<'py>>],
270 ) -> PyResult<(V::Varargs, K::Varkeywords)>
271 where
272 V: VarargsHandler<'py>,
273 K: VarkeywordsHandler<'py>,
274 {
275 let num_positional_parameters = self.positional_parameter_names.len();
276
277 debug_assert!(nargs >= 0);
278 debug_assert!(self.positional_only_parameters <= num_positional_parameters);
279 debug_assert!(self.required_positional_parameters <= num_positional_parameters);
280 debug_assert_eq!(
281 output.len(),
282 num_positional_parameters + self.keyword_only_parameters.len()
283 );
284
285 let args: *const Option<PyArg<'py>> = args.cast();
290 let positional_args_provided = nargs as usize;
291 let remaining_positional_args = if args.is_null() {
292 debug_assert_eq!(positional_args_provided, 0);
293 &[]
294 } else {
295 let positional_args_to_consume =
298 num_positional_parameters.min(positional_args_provided);
299 let (positional_parameters, remaining) = unsafe {
300 std::slice::from_raw_parts(args, positional_args_provided)
301 .split_at(positional_args_to_consume)
302 };
303 output[..positional_args_to_consume].copy_from_slice(positional_parameters);
304 remaining
305 };
306 let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?;
307
308 let mut varkeywords = K::Varkeywords::default();
310
311 let kwnames: Option<Borrowed<'_, '_, PyTuple>> = unsafe {
314 Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.downcast_unchecked())
315 };
316 if let Some(kwnames) = kwnames {
317 let kwargs = unsafe {
318 ::std::slice::from_raw_parts(
319 args.offset(nargs).cast::<PyArg<'py>>(),
321 kwnames.len(),
322 )
323 };
324
325 self.handle_kwargs::<K, _>(
326 kwnames.iter_borrowed().zip(kwargs.iter().copied()),
327 &mut varkeywords,
328 num_positional_parameters,
329 output,
330 )?
331 }
332
333 self.ensure_no_missing_required_positional_arguments(output, positional_args_provided)?;
336 self.ensure_no_missing_required_keyword_arguments(output)?;
337
338 Ok((varargs, varkeywords))
339 }
340
341 pub unsafe fn extract_arguments_tuple_dict<'py, V, K>(
354 &self,
355 py: Python<'py>,
356 args: *mut ffi::PyObject,
357 kwargs: *mut ffi::PyObject,
358 output: &mut [Option<PyArg<'py>>],
359 ) -> PyResult<(V::Varargs, K::Varkeywords)>
360 where
361 V: VarargsHandler<'py>,
362 K: VarkeywordsHandler<'py>,
363 {
364 let args: Borrowed<'py, 'py, PyTuple> =
369 unsafe { Borrowed::from_ptr(py, args).downcast_unchecked::<PyTuple>() };
370 let kwargs: Option<Borrowed<'py, 'py, PyDict>> = unsafe {
371 Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.downcast_unchecked())
372 };
373
374 let num_positional_parameters = self.positional_parameter_names.len();
375
376 debug_assert!(self.positional_only_parameters <= num_positional_parameters);
377 debug_assert!(self.required_positional_parameters <= num_positional_parameters);
378 debug_assert_eq!(
379 output.len(),
380 num_positional_parameters + self.keyword_only_parameters.len()
381 );
382
383 for (i, arg) in args
385 .iter_borrowed()
386 .take(num_positional_parameters)
387 .enumerate()
388 {
389 output[i] = Some(arg);
390 }
391
392 let varargs = V::handle_varargs_tuple(&args, self)?;
394
395 let mut varkeywords = K::Varkeywords::default();
397 if let Some(kwargs) = kwargs {
398 self.handle_kwargs::<K, _>(
399 unsafe { kwargs.iter_borrowed() },
400 &mut varkeywords,
401 num_positional_parameters,
402 output,
403 )?
404 }
405
406 self.ensure_no_missing_required_positional_arguments(output, args.len())?;
409 self.ensure_no_missing_required_keyword_arguments(output)?;
410
411 Ok((varargs, varkeywords))
412 }
413
414 #[inline]
415 fn handle_kwargs<'py, K, I>(
416 &self,
417 kwargs: I,
418 varkeywords: &mut K::Varkeywords,
419 num_positional_parameters: usize,
420 output: &mut [Option<PyArg<'py>>],
421 ) -> PyResult<()>
422 where
423 K: VarkeywordsHandler<'py>,
424 I: IntoIterator<Item = (PyArg<'py>, PyArg<'py>)>,
425 {
426 debug_assert_eq!(
427 num_positional_parameters,
428 self.positional_parameter_names.len()
429 );
430 debug_assert_eq!(
431 output.len(),
432 num_positional_parameters + self.keyword_only_parameters.len()
433 );
434 let mut positional_only_keyword_arguments = Vec::new();
435 for (kwarg_name_py, value) in kwargs {
436 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
439 let kwarg_name =
440 unsafe { kwarg_name_py.downcast_unchecked::<crate::types::PyString>() }.to_str();
441
442 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
443 let kwarg_name = kwarg_name_py.extract::<crate::pybacked::PyBackedStr>();
444
445 if let Ok(kwarg_name_owned) = kwarg_name {
446 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
447 let kwarg_name = kwarg_name_owned;
448 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
449 let kwarg_name: &str = &kwarg_name_owned;
450
451 if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) {
453 if output[i + num_positional_parameters]
454 .replace(value)
455 .is_some()
456 {
457 return Err(self.multiple_values_for_argument(kwarg_name));
458 }
459 continue;
460 }
461
462 if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) {
464 if i < self.positional_only_parameters {
465 if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() {
469 positional_only_keyword_arguments.push(kwarg_name_owned);
470 }
471 } else if output[i].replace(value).is_some() {
472 return Err(self.multiple_values_for_argument(kwarg_name));
473 }
474 continue;
475 }
476 };
477
478 K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)?
479 }
480
481 if !positional_only_keyword_arguments.is_empty() {
482 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
483 let positional_only_keyword_arguments: Vec<_> = positional_only_keyword_arguments
484 .iter()
485 .map(std::ops::Deref::deref)
486 .collect();
487 return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments));
488 }
489
490 Ok(())
491 }
492
493 #[inline]
494 fn find_keyword_parameter_in_positional(&self, kwarg_name: &str) -> Option<usize> {
495 self.positional_parameter_names
496 .iter()
497 .position(|¶m_name| param_name == kwarg_name)
498 }
499
500 #[inline]
501 fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> {
502 self.keyword_only_parameters
506 .iter()
507 .position(|param_desc| param_desc.name == kwarg_name)
508 }
509
510 #[inline]
511 fn ensure_no_missing_required_positional_arguments(
512 &self,
513 output: &[Option<PyArg<'_>>],
514 positional_args_provided: usize,
515 ) -> PyResult<()> {
516 if positional_args_provided < self.required_positional_parameters {
517 for out in &output[positional_args_provided..self.required_positional_parameters] {
518 if out.is_none() {
519 return Err(self.missing_required_positional_arguments(output));
520 }
521 }
522 }
523 Ok(())
524 }
525
526 #[inline]
527 fn ensure_no_missing_required_keyword_arguments(
528 &self,
529 output: &[Option<PyArg<'_>>],
530 ) -> PyResult<()> {
531 let keyword_output = &output[self.positional_parameter_names.len()..];
532 for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) {
533 if param.required && out.is_none() {
534 return Err(self.missing_required_keyword_arguments(keyword_output));
535 }
536 }
537 Ok(())
538 }
539
540 #[cold]
541 fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
542 let was = if args_provided == 1 { "was" } else { "were" };
543 let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
544 format!(
545 "{} takes from {} to {} positional arguments but {} {} given",
546 self.full_name(),
547 self.required_positional_parameters,
548 self.positional_parameter_names.len(),
549 args_provided,
550 was
551 )
552 } else {
553 format!(
554 "{} takes {} positional arguments but {} {} given",
555 self.full_name(),
556 self.positional_parameter_names.len(),
557 args_provided,
558 was
559 )
560 };
561 PyTypeError::new_err(msg)
562 }
563
564 #[cold]
565 fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
566 PyTypeError::new_err(format!(
567 "{} got multiple values for argument '{}'",
568 self.full_name(),
569 argument
570 ))
571 }
572
573 #[cold]
574 fn unexpected_keyword_argument(&self, argument: PyArg<'_>) -> PyErr {
575 PyTypeError::new_err(format!(
576 "{} got an unexpected keyword argument '{}'",
577 self.full_name(),
578 argument.as_any()
579 ))
580 }
581
582 #[cold]
583 fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
584 let mut msg = format!(
585 "{} got some positional-only arguments passed as keyword arguments: ",
586 self.full_name()
587 );
588 push_parameter_list(&mut msg, parameter_names);
589 PyTypeError::new_err(msg)
590 }
591
592 #[cold]
593 fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
594 let arguments = if parameter_names.len() == 1 {
595 "argument"
596 } else {
597 "arguments"
598 };
599 let mut msg = format!(
600 "{} missing {} required {} {}: ",
601 self.full_name(),
602 parameter_names.len(),
603 argument_type,
604 arguments,
605 );
606 push_parameter_list(&mut msg, parameter_names);
607 PyTypeError::new_err(msg)
608 }
609
610 #[cold]
611 fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<PyArg<'_>>]) -> PyErr {
612 debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len());
613
614 let missing_keyword_only_arguments: Vec<_> = self
615 .keyword_only_parameters
616 .iter()
617 .zip(keyword_outputs)
618 .filter_map(|(keyword_desc, out)| {
619 if keyword_desc.required && out.is_none() {
620 Some(keyword_desc.name)
621 } else {
622 None
623 }
624 })
625 .collect();
626
627 debug_assert!(!missing_keyword_only_arguments.is_empty());
628 self.missing_required_arguments("keyword", &missing_keyword_only_arguments)
629 }
630
631 #[cold]
632 fn missing_required_positional_arguments(&self, output: &[Option<PyArg<'_>>]) -> PyErr {
633 let missing_positional_arguments: Vec<_> = self
634 .positional_parameter_names
635 .iter()
636 .take(self.required_positional_parameters)
637 .zip(output)
638 .filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
639 .collect();
640
641 debug_assert!(!missing_positional_arguments.is_empty());
642 self.missing_required_arguments("positional", &missing_positional_arguments)
643 }
644}
645
646pub trait VarargsHandler<'py> {
648 type Varargs;
649 fn handle_varargs_fastcall(
651 py: Python<'py>,
652 varargs: &[Option<PyArg<'py>>],
653 function_description: &FunctionDescription,
654 ) -> PyResult<Self::Varargs>;
655 fn handle_varargs_tuple(
659 args: &Bound<'py, PyTuple>,
660 function_description: &FunctionDescription,
661 ) -> PyResult<Self::Varargs>;
662}
663
664pub struct NoVarargs;
666
667impl<'py> VarargsHandler<'py> for NoVarargs {
668 type Varargs = ();
669
670 #[inline]
671 fn handle_varargs_fastcall(
672 _py: Python<'py>,
673 varargs: &[Option<PyArg<'py>>],
674 function_description: &FunctionDescription,
675 ) -> PyResult<Self::Varargs> {
676 let extra_arguments = varargs.len();
677 if extra_arguments > 0 {
678 return Err(function_description.too_many_positional_arguments(
679 function_description.positional_parameter_names.len() + extra_arguments,
680 ));
681 }
682 Ok(())
683 }
684
685 #[inline]
686 fn handle_varargs_tuple(
687 args: &Bound<'py, PyTuple>,
688 function_description: &FunctionDescription,
689 ) -> PyResult<Self::Varargs> {
690 let positional_parameter_count = function_description.positional_parameter_names.len();
691 let provided_args_count = args.len();
692 if provided_args_count <= positional_parameter_count {
693 Ok(())
694 } else {
695 Err(function_description.too_many_positional_arguments(provided_args_count))
696 }
697 }
698}
699
700pub struct TupleVarargs;
702
703impl<'py> VarargsHandler<'py> for TupleVarargs {
704 type Varargs = Bound<'py, PyTuple>;
705 #[inline]
706 fn handle_varargs_fastcall(
707 py: Python<'py>,
708 varargs: &[Option<PyArg<'py>>],
709 _function_description: &FunctionDescription,
710 ) -> PyResult<Self::Varargs> {
711 PyTuple::new(py, varargs)
712 }
713
714 #[inline]
715 fn handle_varargs_tuple(
716 args: &Bound<'py, PyTuple>,
717 function_description: &FunctionDescription,
718 ) -> PyResult<Self::Varargs> {
719 let positional_parameters = function_description.positional_parameter_names.len();
720 Ok(args.get_slice(positional_parameters, args.len()))
721 }
722}
723
724pub trait VarkeywordsHandler<'py> {
726 type Varkeywords: Default;
727 fn handle_varkeyword(
728 varkeywords: &mut Self::Varkeywords,
729 name: PyArg<'py>,
730 value: PyArg<'py>,
731 function_description: &FunctionDescription,
732 ) -> PyResult<()>;
733}
734
735pub struct NoVarkeywords;
737
738impl<'py> VarkeywordsHandler<'py> for NoVarkeywords {
739 type Varkeywords = ();
740 #[inline]
741 fn handle_varkeyword(
742 _varkeywords: &mut Self::Varkeywords,
743 name: PyArg<'py>,
744 _value: PyArg<'py>,
745 function_description: &FunctionDescription,
746 ) -> PyResult<()> {
747 Err(function_description.unexpected_keyword_argument(name))
748 }
749}
750
751pub struct DictVarkeywords;
753
754impl<'py> VarkeywordsHandler<'py> for DictVarkeywords {
755 type Varkeywords = Option<Bound<'py, PyDict>>;
756 #[inline]
757 fn handle_varkeyword(
758 varkeywords: &mut Self::Varkeywords,
759 name: PyArg<'py>,
760 value: PyArg<'py>,
761 _function_description: &FunctionDescription,
762 ) -> PyResult<()> {
763 varkeywords
764 .get_or_insert_with(|| PyDict::new(name.py()))
765 .set_item(name, value)
766 }
767}
768
769fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) {
770 let len = parameter_names.len();
771 for (i, parameter) in parameter_names.iter().enumerate() {
772 if i != 0 {
773 if len > 2 {
774 msg.push(',');
775 }
776
777 if i == len - 1 {
778 msg.push_str(" and ")
779 } else {
780 msg.push(' ')
781 }
782 }
783
784 msg.push('\'');
785 msg.push_str(parameter);
786 msg.push('\'');
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 use crate::types::{IntoPyDict, PyTuple};
793 use crate::Python;
794
795 use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords};
796
797 #[test]
798 fn unexpected_keyword_argument() {
799 let function_description = FunctionDescription {
800 cls_name: None,
801 func_name: "example",
802 positional_parameter_names: &[],
803 positional_only_parameters: 0,
804 required_positional_parameters: 0,
805 keyword_only_parameters: &[],
806 };
807
808 Python::with_gil(|py| {
809 let args = PyTuple::empty(py);
810 let kwargs = [("foo", 0u8)].into_py_dict(py).unwrap();
811 let err = unsafe {
812 function_description
813 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
814 py,
815 args.as_ptr(),
816 kwargs.as_ptr(),
817 &mut [],
818 )
819 .unwrap_err()
820 };
821 assert_eq!(
822 err.to_string(),
823 "TypeError: example() got an unexpected keyword argument 'foo'"
824 );
825 })
826 }
827
828 #[test]
829 fn keyword_not_string() {
830 let function_description = FunctionDescription {
831 cls_name: None,
832 func_name: "example",
833 positional_parameter_names: &[],
834 positional_only_parameters: 0,
835 required_positional_parameters: 0,
836 keyword_only_parameters: &[],
837 };
838
839 Python::with_gil(|py| {
840 let args = PyTuple::empty(py);
841 let kwargs = [(1u8, 1u8)].into_py_dict(py).unwrap();
842 let err = unsafe {
843 function_description
844 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
845 py,
846 args.as_ptr(),
847 kwargs.as_ptr(),
848 &mut [],
849 )
850 .unwrap_err()
851 };
852 assert_eq!(
853 err.to_string(),
854 "TypeError: example() got an unexpected keyword argument '1'"
855 );
856 })
857 }
858
859 #[test]
860 fn missing_required_arguments() {
861 let function_description = FunctionDescription {
862 cls_name: None,
863 func_name: "example",
864 positional_parameter_names: &["foo", "bar"],
865 positional_only_parameters: 0,
866 required_positional_parameters: 2,
867 keyword_only_parameters: &[],
868 };
869
870 Python::with_gil(|py| {
871 let args = PyTuple::empty(py);
872 let mut output = [None, None];
873 let err = unsafe {
874 function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
875 py,
876 args.as_ptr(),
877 std::ptr::null_mut(),
878 &mut output,
879 )
880 }
881 .unwrap_err();
882 assert_eq!(
883 err.to_string(),
884 "TypeError: example() missing 2 required positional arguments: 'foo' and 'bar'"
885 );
886 })
887 }
888
889 #[test]
890 fn push_parameter_list_empty() {
891 let mut s = String::new();
892 push_parameter_list(&mut s, &[]);
893 assert_eq!(&s, "");
894 }
895
896 #[test]
897 fn push_parameter_list_one() {
898 let mut s = String::new();
899 push_parameter_list(&mut s, &["a"]);
900 assert_eq!(&s, "'a'");
901 }
902
903 #[test]
904 fn push_parameter_list_two() {
905 let mut s = String::new();
906 push_parameter_list(&mut s, &["a", "b"]);
907 assert_eq!(&s, "'a' and 'b'");
908 }
909
910 #[test]
911 fn push_parameter_list_three() {
912 let mut s = String::new();
913 push_parameter_list(&mut s, &["a", "b", "c"]);
914 assert_eq!(&s, "'a', 'b', and 'c'");
915 }
916
917 #[test]
918 fn push_parameter_list_four() {
919 let mut s = String::new();
920 push_parameter_list(&mut s, &["a", "b", "c", "d"]);
921 assert_eq!(&s, "'a', 'b', 'c', and 'd'");
922 }
923}