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