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>: 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> 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> for &'a Bound<'py, T>
41where
42 T: PyTypeCheck,
43{
44 type Holder = Option<()>;
45
46 #[inline]
47 fn extract(obj: &'a Bound<'py, PyAny>, _: &'a mut Option<()>) -> PyResult<Self> {
48 obj.downcast().map_err(Into::into)
49 }
50}
51
52impl<'a, 'py, T: 'py> PyFunctionArgument<'a, 'py> for Option<&'a Bound<'py, T>>
53where
54 T: PyTypeCheck,
55{
56 type Holder = ();
57
58 #[inline]
59 fn extract(obj: &'a Bound<'py, PyAny>, _: &'a mut ()) -> PyResult<Self> {
60 if obj.is_none() {
61 Ok(None)
62 } else {
63 Ok(Some(obj.downcast()?))
64 }
65 }
66}
67
68#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
69impl<'a> PyFunctionArgument<'a, '_> 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>(
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>,
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>(
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>,
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>(
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>,
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) =
300 std::slice::from_raw_parts(args, positional_args_provided)
301 .split_at(positional_args_to_consume);
302 output[..positional_args_to_consume].copy_from_slice(positional_parameters);
303 remaining
304 };
305 let varargs = V::handle_varargs_fastcall(py, remaining_positional_args, self)?;
306
307 let mut varkeywords = K::Varkeywords::default();
309
310 let kwnames: Option<Borrowed<'_, '_, PyTuple>> =
313 Borrowed::from_ptr_or_opt(py, kwnames).map(|kwnames| kwnames.downcast_unchecked());
314 if let Some(kwnames) = kwnames {
315 let kwargs = ::std::slice::from_raw_parts(
316 args.offset(nargs).cast::<PyArg<'py>>(),
318 kwnames.len(),
319 );
320
321 self.handle_kwargs::<K, _>(
322 kwnames.iter_borrowed().zip(kwargs.iter().copied()),
323 &mut varkeywords,
324 num_positional_parameters,
325 output,
326 )?
327 }
328
329 self.ensure_no_missing_required_positional_arguments(output, positional_args_provided)?;
332 self.ensure_no_missing_required_keyword_arguments(output)?;
333
334 Ok((varargs, varkeywords))
335 }
336
337 pub unsafe fn extract_arguments_tuple_dict<'py, V, K>(
350 &self,
351 py: Python<'py>,
352 args: *mut ffi::PyObject,
353 kwargs: *mut ffi::PyObject,
354 output: &mut [Option<PyArg<'py>>],
355 ) -> PyResult<(V::Varargs, K::Varkeywords)>
356 where
357 V: VarargsHandler<'py>,
358 K: VarkeywordsHandler<'py>,
359 {
360 let args: Borrowed<'py, 'py, PyTuple> =
365 Borrowed::from_ptr(py, args).downcast_unchecked::<PyTuple>();
366 let kwargs: Option<Borrowed<'py, 'py, PyDict>> =
367 Borrowed::from_ptr_or_opt(py, kwargs).map(|kwargs| kwargs.downcast_unchecked());
368
369 let num_positional_parameters = self.positional_parameter_names.len();
370
371 debug_assert!(self.positional_only_parameters <= num_positional_parameters);
372 debug_assert!(self.required_positional_parameters <= num_positional_parameters);
373 debug_assert_eq!(
374 output.len(),
375 num_positional_parameters + self.keyword_only_parameters.len()
376 );
377
378 for (i, arg) in args
380 .iter_borrowed()
381 .take(num_positional_parameters)
382 .enumerate()
383 {
384 output[i] = Some(arg);
385 }
386
387 let varargs = V::handle_varargs_tuple(&args, self)?;
389
390 let mut varkeywords = K::Varkeywords::default();
392 if let Some(kwargs) = kwargs {
393 self.handle_kwargs::<K, _>(
394 kwargs.iter_borrowed(),
395 &mut varkeywords,
396 num_positional_parameters,
397 output,
398 )?
399 }
400
401 self.ensure_no_missing_required_positional_arguments(output, args.len())?;
404 self.ensure_no_missing_required_keyword_arguments(output)?;
405
406 Ok((varargs, varkeywords))
407 }
408
409 #[inline]
410 fn handle_kwargs<'py, K, I>(
411 &self,
412 kwargs: I,
413 varkeywords: &mut K::Varkeywords,
414 num_positional_parameters: usize,
415 output: &mut [Option<PyArg<'py>>],
416 ) -> PyResult<()>
417 where
418 K: VarkeywordsHandler<'py>,
419 I: IntoIterator<Item = (PyArg<'py>, PyArg<'py>)>,
420 {
421 debug_assert_eq!(
422 num_positional_parameters,
423 self.positional_parameter_names.len()
424 );
425 debug_assert_eq!(
426 output.len(),
427 num_positional_parameters + self.keyword_only_parameters.len()
428 );
429 let mut positional_only_keyword_arguments = Vec::new();
430 for (kwarg_name_py, value) in kwargs {
431 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
434 let kwarg_name =
435 unsafe { kwarg_name_py.downcast_unchecked::<crate::types::PyString>() }.to_str();
436
437 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
438 let kwarg_name = kwarg_name_py.extract::<crate::pybacked::PyBackedStr>();
439
440 if let Ok(kwarg_name_owned) = kwarg_name {
441 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
442 let kwarg_name = kwarg_name_owned;
443 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
444 let kwarg_name: &str = &kwarg_name_owned;
445
446 if let Some(i) = self.find_keyword_parameter_in_keyword_only(kwarg_name) {
448 if output[i + num_positional_parameters]
449 .replace(value)
450 .is_some()
451 {
452 return Err(self.multiple_values_for_argument(kwarg_name));
453 }
454 continue;
455 }
456
457 if let Some(i) = self.find_keyword_parameter_in_positional(kwarg_name) {
459 if i < self.positional_only_parameters {
460 if K::handle_varkeyword(varkeywords, kwarg_name_py, value, self).is_err() {
464 positional_only_keyword_arguments.push(kwarg_name_owned);
465 }
466 } else if output[i].replace(value).is_some() {
467 return Err(self.multiple_values_for_argument(kwarg_name));
468 }
469 continue;
470 }
471 };
472
473 K::handle_varkeyword(varkeywords, kwarg_name_py, value, self)?
474 }
475
476 if !positional_only_keyword_arguments.is_empty() {
477 #[cfg(all(not(Py_3_10), Py_LIMITED_API))]
478 let positional_only_keyword_arguments: Vec<_> = positional_only_keyword_arguments
479 .iter()
480 .map(std::ops::Deref::deref)
481 .collect();
482 return Err(self.positional_only_keyword_arguments(&positional_only_keyword_arguments));
483 }
484
485 Ok(())
486 }
487
488 #[inline]
489 fn find_keyword_parameter_in_positional(&self, kwarg_name: &str) -> Option<usize> {
490 self.positional_parameter_names
491 .iter()
492 .position(|¶m_name| param_name == kwarg_name)
493 }
494
495 #[inline]
496 fn find_keyword_parameter_in_keyword_only(&self, kwarg_name: &str) -> Option<usize> {
497 self.keyword_only_parameters
501 .iter()
502 .position(|param_desc| param_desc.name == kwarg_name)
503 }
504
505 #[inline]
506 fn ensure_no_missing_required_positional_arguments(
507 &self,
508 output: &[Option<PyArg<'_>>],
509 positional_args_provided: usize,
510 ) -> PyResult<()> {
511 if positional_args_provided < self.required_positional_parameters {
512 for out in &output[positional_args_provided..self.required_positional_parameters] {
513 if out.is_none() {
514 return Err(self.missing_required_positional_arguments(output));
515 }
516 }
517 }
518 Ok(())
519 }
520
521 #[inline]
522 fn ensure_no_missing_required_keyword_arguments(
523 &self,
524 output: &[Option<PyArg<'_>>],
525 ) -> PyResult<()> {
526 let keyword_output = &output[self.positional_parameter_names.len()..];
527 for (param, out) in self.keyword_only_parameters.iter().zip(keyword_output) {
528 if param.required && out.is_none() {
529 return Err(self.missing_required_keyword_arguments(keyword_output));
530 }
531 }
532 Ok(())
533 }
534
535 #[cold]
536 fn too_many_positional_arguments(&self, args_provided: usize) -> PyErr {
537 let was = if args_provided == 1 { "was" } else { "were" };
538 let msg = if self.required_positional_parameters != self.positional_parameter_names.len() {
539 format!(
540 "{} takes from {} to {} positional arguments but {} {} given",
541 self.full_name(),
542 self.required_positional_parameters,
543 self.positional_parameter_names.len(),
544 args_provided,
545 was
546 )
547 } else {
548 format!(
549 "{} takes {} positional arguments but {} {} given",
550 self.full_name(),
551 self.positional_parameter_names.len(),
552 args_provided,
553 was
554 )
555 };
556 PyTypeError::new_err(msg)
557 }
558
559 #[cold]
560 fn multiple_values_for_argument(&self, argument: &str) -> PyErr {
561 PyTypeError::new_err(format!(
562 "{} got multiple values for argument '{}'",
563 self.full_name(),
564 argument
565 ))
566 }
567
568 #[cold]
569 fn unexpected_keyword_argument(&self, argument: PyArg<'_>) -> PyErr {
570 PyTypeError::new_err(format!(
571 "{} got an unexpected keyword argument '{}'",
572 self.full_name(),
573 argument.as_any()
574 ))
575 }
576
577 #[cold]
578 fn positional_only_keyword_arguments(&self, parameter_names: &[&str]) -> PyErr {
579 let mut msg = format!(
580 "{} got some positional-only arguments passed as keyword arguments: ",
581 self.full_name()
582 );
583 push_parameter_list(&mut msg, parameter_names);
584 PyTypeError::new_err(msg)
585 }
586
587 #[cold]
588 fn missing_required_arguments(&self, argument_type: &str, parameter_names: &[&str]) -> PyErr {
589 let arguments = if parameter_names.len() == 1 {
590 "argument"
591 } else {
592 "arguments"
593 };
594 let mut msg = format!(
595 "{} missing {} required {} {}: ",
596 self.full_name(),
597 parameter_names.len(),
598 argument_type,
599 arguments,
600 );
601 push_parameter_list(&mut msg, parameter_names);
602 PyTypeError::new_err(msg)
603 }
604
605 #[cold]
606 fn missing_required_keyword_arguments(&self, keyword_outputs: &[Option<PyArg<'_>>]) -> PyErr {
607 debug_assert_eq!(self.keyword_only_parameters.len(), keyword_outputs.len());
608
609 let missing_keyword_only_arguments: Vec<_> = self
610 .keyword_only_parameters
611 .iter()
612 .zip(keyword_outputs)
613 .filter_map(|(keyword_desc, out)| {
614 if keyword_desc.required && out.is_none() {
615 Some(keyword_desc.name)
616 } else {
617 None
618 }
619 })
620 .collect();
621
622 debug_assert!(!missing_keyword_only_arguments.is_empty());
623 self.missing_required_arguments("keyword", &missing_keyword_only_arguments)
624 }
625
626 #[cold]
627 fn missing_required_positional_arguments(&self, output: &[Option<PyArg<'_>>]) -> PyErr {
628 let missing_positional_arguments: Vec<_> = self
629 .positional_parameter_names
630 .iter()
631 .take(self.required_positional_parameters)
632 .zip(output)
633 .filter_map(|(param, out)| if out.is_none() { Some(*param) } else { None })
634 .collect();
635
636 debug_assert!(!missing_positional_arguments.is_empty());
637 self.missing_required_arguments("positional", &missing_positional_arguments)
638 }
639}
640
641pub trait VarargsHandler<'py> {
643 type Varargs;
644 fn handle_varargs_fastcall(
646 py: Python<'py>,
647 varargs: &[Option<PyArg<'py>>],
648 function_description: &FunctionDescription,
649 ) -> PyResult<Self::Varargs>;
650 fn handle_varargs_tuple(
654 args: &Bound<'py, PyTuple>,
655 function_description: &FunctionDescription,
656 ) -> PyResult<Self::Varargs>;
657}
658
659pub struct NoVarargs;
661
662impl<'py> VarargsHandler<'py> for NoVarargs {
663 type Varargs = ();
664
665 #[inline]
666 fn handle_varargs_fastcall(
667 _py: Python<'py>,
668 varargs: &[Option<PyArg<'py>>],
669 function_description: &FunctionDescription,
670 ) -> PyResult<Self::Varargs> {
671 let extra_arguments = varargs.len();
672 if extra_arguments > 0 {
673 return Err(function_description.too_many_positional_arguments(
674 function_description.positional_parameter_names.len() + extra_arguments,
675 ));
676 }
677 Ok(())
678 }
679
680 #[inline]
681 fn handle_varargs_tuple(
682 args: &Bound<'py, PyTuple>,
683 function_description: &FunctionDescription,
684 ) -> PyResult<Self::Varargs> {
685 let positional_parameter_count = function_description.positional_parameter_names.len();
686 let provided_args_count = args.len();
687 if provided_args_count <= positional_parameter_count {
688 Ok(())
689 } else {
690 Err(function_description.too_many_positional_arguments(provided_args_count))
691 }
692 }
693}
694
695pub struct TupleVarargs;
697
698impl<'py> VarargsHandler<'py> for TupleVarargs {
699 type Varargs = Bound<'py, PyTuple>;
700 #[inline]
701 fn handle_varargs_fastcall(
702 py: Python<'py>,
703 varargs: &[Option<PyArg<'py>>],
704 _function_description: &FunctionDescription,
705 ) -> PyResult<Self::Varargs> {
706 PyTuple::new(py, varargs)
707 }
708
709 #[inline]
710 fn handle_varargs_tuple(
711 args: &Bound<'py, PyTuple>,
712 function_description: &FunctionDescription,
713 ) -> PyResult<Self::Varargs> {
714 let positional_parameters = function_description.positional_parameter_names.len();
715 Ok(args.get_slice(positional_parameters, args.len()))
716 }
717}
718
719pub trait VarkeywordsHandler<'py> {
721 type Varkeywords: Default;
722 fn handle_varkeyword(
723 varkeywords: &mut Self::Varkeywords,
724 name: PyArg<'py>,
725 value: PyArg<'py>,
726 function_description: &FunctionDescription,
727 ) -> PyResult<()>;
728}
729
730pub struct NoVarkeywords;
732
733impl<'py> VarkeywordsHandler<'py> for NoVarkeywords {
734 type Varkeywords = ();
735 #[inline]
736 fn handle_varkeyword(
737 _varkeywords: &mut Self::Varkeywords,
738 name: PyArg<'py>,
739 _value: PyArg<'py>,
740 function_description: &FunctionDescription,
741 ) -> PyResult<()> {
742 Err(function_description.unexpected_keyword_argument(name))
743 }
744}
745
746pub struct DictVarkeywords;
748
749impl<'py> VarkeywordsHandler<'py> for DictVarkeywords {
750 type Varkeywords = Option<Bound<'py, PyDict>>;
751 #[inline]
752 fn handle_varkeyword(
753 varkeywords: &mut Self::Varkeywords,
754 name: PyArg<'py>,
755 value: PyArg<'py>,
756 _function_description: &FunctionDescription,
757 ) -> PyResult<()> {
758 varkeywords
759 .get_or_insert_with(|| PyDict::new(name.py()))
760 .set_item(name, value)
761 }
762}
763
764fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) {
765 let len = parameter_names.len();
766 for (i, parameter) in parameter_names.iter().enumerate() {
767 if i != 0 {
768 if len > 2 {
769 msg.push(',');
770 }
771
772 if i == len - 1 {
773 msg.push_str(" and ")
774 } else {
775 msg.push(' ')
776 }
777 }
778
779 msg.push('\'');
780 msg.push_str(parameter);
781 msg.push('\'');
782 }
783}
784
785#[cfg(test)]
786mod tests {
787 use crate::types::{IntoPyDict, PyTuple};
788 use crate::Python;
789
790 use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords};
791
792 #[test]
793 fn unexpected_keyword_argument() {
794 let function_description = FunctionDescription {
795 cls_name: None,
796 func_name: "example",
797 positional_parameter_names: &[],
798 positional_only_parameters: 0,
799 required_positional_parameters: 0,
800 keyword_only_parameters: &[],
801 };
802
803 Python::with_gil(|py| {
804 let args = PyTuple::empty(py);
805 let kwargs = [("foo", 0u8)].into_py_dict(py).unwrap();
806 let err = unsafe {
807 function_description
808 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
809 py,
810 args.as_ptr(),
811 kwargs.as_ptr(),
812 &mut [],
813 )
814 .unwrap_err()
815 };
816 assert_eq!(
817 err.to_string(),
818 "TypeError: example() got an unexpected keyword argument 'foo'"
819 );
820 })
821 }
822
823 #[test]
824 fn keyword_not_string() {
825 let function_description = FunctionDescription {
826 cls_name: None,
827 func_name: "example",
828 positional_parameter_names: &[],
829 positional_only_parameters: 0,
830 required_positional_parameters: 0,
831 keyword_only_parameters: &[],
832 };
833
834 Python::with_gil(|py| {
835 let args = PyTuple::empty(py);
836 let kwargs = [(1u8, 1u8)].into_py_dict(py).unwrap();
837 let err = unsafe {
838 function_description
839 .extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
840 py,
841 args.as_ptr(),
842 kwargs.as_ptr(),
843 &mut [],
844 )
845 .unwrap_err()
846 };
847 assert_eq!(
848 err.to_string(),
849 "TypeError: example() got an unexpected keyword argument '1'"
850 );
851 })
852 }
853
854 #[test]
855 fn missing_required_arguments() {
856 let function_description = FunctionDescription {
857 cls_name: None,
858 func_name: "example",
859 positional_parameter_names: &["foo", "bar"],
860 positional_only_parameters: 0,
861 required_positional_parameters: 2,
862 keyword_only_parameters: &[],
863 };
864
865 Python::with_gil(|py| {
866 let args = PyTuple::empty(py);
867 let mut output = [None, None];
868 let err = unsafe {
869 function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
870 py,
871 args.as_ptr(),
872 std::ptr::null_mut(),
873 &mut output,
874 )
875 }
876 .unwrap_err();
877 assert_eq!(
878 err.to_string(),
879 "TypeError: example() missing 2 required positional arguments: 'foo' and 'bar'"
880 );
881 })
882 }
883
884 #[test]
885 fn push_parameter_list_empty() {
886 let mut s = String::new();
887 push_parameter_list(&mut s, &[]);
888 assert_eq!(&s, "");
889 }
890
891 #[test]
892 fn push_parameter_list_one() {
893 let mut s = String::new();
894 push_parameter_list(&mut s, &["a"]);
895 assert_eq!(&s, "'a'");
896 }
897
898 #[test]
899 fn push_parameter_list_two() {
900 let mut s = String::new();
901 push_parameter_list(&mut s, &["a", "b"]);
902 assert_eq!(&s, "'a' and 'b'");
903 }
904
905 #[test]
906 fn push_parameter_list_three() {
907 let mut s = String::new();
908 push_parameter_list(&mut s, &["a", "b", "c"]);
909 assert_eq!(&s, "'a', 'b', and 'c'");
910 }
911
912 #[test]
913 fn push_parameter_list_four() {
914 let mut s = String::new();
915 push_parameter_list(&mut s, &["a", "b", "c", "d"]);
916 assert_eq!(&s, "'a', 'b', 'c', and 'd'");
917 }
918}