cpython/
argparse.rs

1// Copyright (c) 2015 Daniel Grunwald
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19//! This module contains logic for parsing a python argument list.
20//! See also the macros `py_argparse!`, `py_fn!` and `py_method!`.
21
22use std::ptr;
23
24use crate::conversion::{RefFromPyObject, ToPyObject};
25use crate::err::{self, PyResult};
26use crate::ffi;
27use crate::objects::{exc, PyDict, PyObject, PyString, PyTuple};
28use crate::python::{Python, PythonObject};
29
30/// Description of a python parameter; used for `parse_args()`.
31pub struct ParamDescription<'a> {
32    /// The name of the parameter.
33    pub name: &'a str,
34    /// Whether the parameter is optional.
35    pub is_optional: bool,
36}
37
38impl<'a> ParamDescription<'a> {
39    /// Name, with leading `r#` stripped.
40    pub fn name(&self) -> &str {
41        crate::strip_raw!(self.name)
42    }
43}
44
45/// Parse argument list
46///
47///  * fname:  Name of the current function
48///  * params: Declared parameters of the function
49///  * args:   Positional arguments
50///  * kwargs: Keyword arguments
51///  * output: Output array that receives the arguments.
52///           Must have same length as `params` and must be initialized to `None`.
53pub fn parse_args(
54    py: Python,
55    fname: Option<&str>,
56    params: &[ParamDescription],
57    args: &PyTuple,
58    kwargs: Option<&PyDict>,
59    output: &mut [Option<PyObject>],
60) -> PyResult<()> {
61    assert!(params.len() == output.len());
62    let nargs = args.len(py);
63    let nkeywords = kwargs.map_or(0, |d| d.len(py));
64    if nargs + nkeywords > params.len() {
65        return Err(err::PyErr::new::<exc::TypeError, _>(
66            py,
67            format!(
68                "{}{} takes at most {} argument{} ({} given)",
69                fname.unwrap_or("function"),
70                if fname.is_some() { "()" } else { "" },
71                params.len(),
72                if params.len() != 1 { "s" } else { "" },
73                nargs + nkeywords
74            ),
75        ));
76    }
77    let mut used_keywords = 0;
78    // Iterate through the parameters and assign values to output:
79    for (i, (p, out)) in params.iter().zip(output).enumerate() {
80        match kwargs.and_then(|d| d.get_item(py, p.name())) {
81            Some(kwarg) => {
82                *out = Some(kwarg);
83                used_keywords += 1;
84                if i < nargs {
85                    return Err(err::PyErr::new::<exc::TypeError, _>(
86                        py,
87                        format!(
88                            "Argument given by name ('{}') and position ({})",
89                            p.name(),
90                            i + 1
91                        ),
92                    ));
93                }
94            }
95            None => {
96                if i < nargs {
97                    *out = Some(args.get_item(py, i));
98                } else {
99                    *out = None;
100                    if !p.is_optional {
101                        return Err(err::PyErr::new::<exc::TypeError, _>(
102                            py,
103                            format!(
104                                "Required argument ('{}') (pos {}) not found",
105                                p.name(),
106                                i + 1
107                            ),
108                        ));
109                    }
110                }
111            }
112        }
113    }
114    if used_keywords != nkeywords {
115        // check for extraneous keyword arguments
116        for (key, _value) in kwargs.unwrap().items(py) {
117            let key = key.cast_as::<PyString>(py)?.to_string(py)?;
118            if !params.iter().any(|p| p.name == key) {
119                return Err(err::PyErr::new::<exc::TypeError, _>(
120                    py,
121                    format!("'{}' is an invalid keyword argument for this function", key),
122                ));
123            }
124        }
125    }
126    Ok(())
127}
128
129/// This macro is used to parse a parameter list into a set of variables.
130///
131/// Syntax: `py_argparse!(py, fname, args, kwargs, (parameter-list) { body })`
132///
133///  * `py`: the `Python` token
134///  * `fname`: expression of type `Option<&str>`: Name of the function used in error messages.
135///  * `args`: expression of type `&PyTuple`: The position arguments
136///  * `kwargs`: expression of type `Option<&PyDict>`: The named arguments
137///  * `parameter-list`: a comma-separated list of parameter declarations.
138///   Parameter declarations have one of these formats:
139///    1. `name`
140///    2. `name: ty`
141///    3. `name: ty = default_value`
142///    4. `*name`
143///    5. `*name : ty`
144///    6. `**name`
145///    7. `**name : ty`
146///
147///   The types used must implement the `FromPyObject` trait.
148///   If no type is specified, the parameter implicitly uses
149///   `&PyObject` (format 1), `&PyTuple` (format 4) or `&PyDict` (format 6).
150///   If a default value is specified, it must be a compile-time constant
151///   of type `ty`.
152///  * `body`: expression of type `PyResult<_>`.
153///     The extracted argument values are available in this scope.
154///
155/// `py_argparse!()` expands to code that extracts values from `args` and `kwargs` and assigns
156/// them to the parameters. If the extraction is successful, `py_argparse!()` evaluates
157/// the body expression and returns of that evaluation.
158/// If extraction fails, `py_argparse!()` returns a failed `PyResult` without evaluating `body`.
159///
160/// The `py_argparse!()` macro special-cases reference types (when `ty` starts with a `&` token)
161/// and optional reference types (when `ty` is of the form `Option<&...>`).
162/// In these cases, the macro uses the `RefFromPyObject` trait instead of the `FromPyObject` trait.
163/// When using at least one reference parameter, the `body` block is placed within a closure,
164/// so `return` statements might behave unexpectedly in this case. (this only affects direct use
165/// of `py_argparse!`; `py_fn!` is unaffected as the body there is always in a separate function
166/// from the generated argument-parsing code).
167#[macro_export]
168macro_rules! py_argparse {
169    ($py:expr, $fname:expr, $args:expr, $kwargs:expr, $plist:tt {$($body:tt)*}) => {
170        $crate::py_argparse_parse_plist! { py_argparse_impl { $py, $fname, $args, $kwargs, {$($body)*}, } $plist }
171    };
172}
173
174#[macro_export]
175#[doc(hidden)]
176macro_rules! py_argparse_parse_plist {
177    // Parses a parameter-list into a format more suitable for consumption by Rust macros.
178    // py_argparse_parse_plist! { callback { initial_args } (plist) }
179    //  = callback! { initial_args [{ pname:ptype = [ {**} {default-value} ] } ...] }
180    // The braces around the *s and the default-value are used even if they are empty.
181
182    // Special-case entry-point for empty parameter list:
183    { $callback:ident { $($initial_arg:tt)* } ( ) } => {
184        $crate::$callback! { $($initial_arg)* [] }
185    };
186    // Regular entry point for non-empty parameter list:
187    { $callback:ident $initial_args:tt ( $( $p:tt )+ ) } => {
188        // add trailing comma to plist so that the parsing step can assume every
189        // parameter ends with a comma.
190        $crate::py_argparse_parse_plist_impl! { $callback $initial_args [] ( $($p)*, ) }
191    };
192}
193
194#[macro_export]
195#[doc(hidden)]
196macro_rules! py_argparse_parse_plist_impl {
197    // TT muncher macro that does the main work for py_argparse_parse_plist!.
198
199    // Base case: all parameters handled
200    { $callback:ident { $($initial_arg:tt)* } $output:tt ( $(,)? ) } => {
201        $crate::$callback! { $($initial_arg)* $output }
202    };
203    // Kwargs parameter with reference extraction
204    { $callback:ident $initial_args:tt [ $($output:tt)* ]
205        ( ** $name:ident : &$t:ty , $($tail:tt)* )
206    } => {
207        $crate::py_argparse_parse_plist_impl! {
208            $callback $initial_args
209            [ $($output)* { $name:&$t = [ {**} {} {$t} ] } ]
210            ($($tail)*)
211        }
212    };
213    // Kwargs parameter
214    { $callback:ident $initial_args:tt [ $($output:tt)* ]
215        ( ** $name:ident : $t:ty , $($tail:tt)* )
216    } => {
217        $crate::py_argparse_parse_plist_impl! {
218            $callback $initial_args
219            [ $($output)* { $name:$t = [ {**} {} {} ] } ]
220            ($($tail)*)
221        }
222    };
223    // Kwargs parameter with implicit type
224    { $callback:ident $initial_args:tt [ $($output:tt)* ]
225        ( ** $name:ident , $($tail:tt)* )
226    } => {
227        $crate::py_argparse_parse_plist_impl! {
228            $callback $initial_args
229            [ $($output)* { $name:Option<&$crate::PyDict> = [ {**} {} {} ] } ]
230            ($($tail)*)
231        }
232    };
233    // Varargs parameter with reference extraction
234    { $callback:ident $initial_args:tt [ $($output:tt)* ]
235        ( * $name:ident : &$t:ty , $($tail:tt)* )
236    } => {
237        $crate::py_argparse_parse_plist_impl! {
238            $callback $initial_args
239            [ $($output)* { $name:&$t = [ {*} {} {$t} ] } ]
240            ($($tail)*)
241        }
242    };
243    // Varargs parameter
244    { $callback:ident $initial_args:tt [ $($output:tt)* ]
245        ( * $name:ident : $t:ty , $($tail:tt)* )
246    } => {
247        $crate::py_argparse_parse_plist_impl! {
248            $callback $initial_args
249            [ $($output)* { $name:$t = [ {*} {} {} ] } ]
250            ($($tail)*)
251        }
252    };
253    // Varargs parameter with implicit type
254    { $callback:ident $initial_args:tt [ $($output:tt)* ]
255        ( * $name:ident , $($tail:tt)* )
256    } => {
257        $crate::py_argparse_parse_plist_impl! {
258            $callback $initial_args
259            [ $($output)* { $name:&$crate::PyTuple = [ {*} {} {} ] } ]
260            ($($tail)*)
261        }
262    };
263    // Simple parameter with reference extraction
264    { $callback:ident $initial_args:tt [ $($output:tt)* ]
265        ( $name:ident : &$t:ty , $($tail:tt)* )
266    } => {
267        $crate::py_argparse_parse_plist_impl! {
268            $callback $initial_args
269            [ $($output)* { $name:&$t = [ {} {} {$t} ] } ]
270            ($($tail)*)
271        }
272    };
273    // Maybe None simple parameter with reference extraction
274    { $callback:ident $initial_args:tt [ $($output:tt)* ]
275        ( $name:ident : Option<&$t:ty> , $($tail:tt)* )
276    } => {
277        $crate::py_argparse_parse_plist_impl! {
278            $callback $initial_args
279            [ $($output)* { $name: std::option::Option<&$t> = [ {opt} {} {$t} ] } ]
280            ($($tail)*)
281        }
282    };
283    // Simple parameter
284    { $callback:ident $initial_args:tt [ $($output:tt)* ]
285        ( $name:ident : $t:ty , $($tail:tt)* )
286    } => {
287        $crate::py_argparse_parse_plist_impl! {
288            $callback $initial_args
289            [ $($output)* { $name:$t = [ {} {} {} ] } ]
290            ($($tail)*)
291        }
292    };
293    // Simple parameter with implicit type
294    { $callback:ident $initial_args:tt [ $($output:tt)* ]
295        ( $name:ident , $($tail:tt)* )
296    } => {
297        $crate::py_argparse_parse_plist_impl! {
298            $callback $initial_args
299            [ $($output)* { $name:&$crate::PyObject = [ {} {} {} ] } ]
300            ($($tail)*)
301        }
302    };
303    // Maybe None optional parameter with reference extraction
304    { $callback:ident $initial_args:tt [ $($output:tt)* ]
305        ( $name:ident : Option<&$t:ty> = $default:expr , $($tail:tt)* )
306    } => {
307        $crate::py_argparse_parse_plist_impl! {
308            $callback $initial_args
309            [ $($output)* { $name: std::option::Option<&$t> = [ {opt} {$default} {$t} ] } ]
310            ($($tail)*)
311        }
312    };
313    // Optional parameter with reference extraction
314    { $callback:ident $initial_args:tt [ $($output:tt)* ]
315        ( $name:ident : &$t:ty = $default:expr, $($tail:tt)* )
316    } => {
317        $crate::py_argparse_parse_plist_impl! {
318            $callback $initial_args
319            [ $($output)* { $name:&$t = [ {} {$default} {$t} ] } ]
320            ($($tail)*)
321        }
322    };
323    // Optional parameter
324    { $callback:ident $initial_args:tt [ $($output:tt)* ]
325        ( $name:ident : $t:ty = $default:expr , $($tail:tt)* )
326    } => {
327        $crate::py_argparse_parse_plist_impl! {
328            $callback $initial_args
329            [ $($output)* { $name:$t = [ {} {$default} {} ] } ]
330            ($($tail)*)
331        }
332    };
333}
334
335// The main py_argparse!() macro, except that it expects the parameter-list
336// in the output format of py_argparse_parse_plist!().
337#[macro_export]
338#[doc(hidden)]
339macro_rules! py_argparse_impl {
340    // special case: function signature is (*args, **kwargs),
341    // so we can directly pass along our inputs without calling parse_args().
342    ($py:expr, $fname:expr, $args:expr, $kwargs:expr, {$($body:tt)*},
343        [
344            { $pargs:ident   : $pargs_type:ty   = [ {*}  {} {} ] }
345            { $pkwargs:ident : $pkwargs_type:ty = [ {**} {} {} ] }
346        ]
347    ) => {{
348        let _py: $crate::Python = $py;
349        // TODO: use extract() to be more flexible in which type is expected
350        let $pargs: $pargs_type = $args;
351        let $pkwargs: $pkwargs_type = $kwargs;
352        $($body)*
353    }};
354
355    // normal argparse logic
356    ($py:expr, $fname:expr, $args:expr, $kwargs:expr, {$($body:tt)*},
357        [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ]
358    ) => {{
359        const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[
360            $(
361                $crate::py_argparse_param_description! { $pname : $ptype = $detail }
362            ),*
363        ];
364        let py: $crate::Python = $py;
365        let mut output = [$( $crate::py_replace_expr!($pname None) ),*];
366        match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, &mut output) {
367            Ok(()) => {
368                // Experimental slice pattern syntax would be really nice here (#23121)
369                //let [$(ref $pname),*] = output;
370                // We'll use an iterator instead.
371                let mut _iter = output.iter();
372                // We'll have to generate a bunch of nested `match` statements
373                // (at least until we can use ? + catch, assuming that will be hygienic wrt. macros),
374                // so use a recursive helper macro for that:
375                let val = $crate::py_argparse_extract!( py, _iter, {$($body)*},
376                    [ $( { $pname : $ptype = $detail } )* ]);
377                val
378            },
379            Err(e) => Err(e)
380        }
381    }};
382}
383
384// Like py_argparse_impl!(), but accepts `*mut ffi::PyObject` for $args and $kwargs.
385#[macro_export]
386#[doc(hidden)]
387macro_rules! py_argparse_raw {
388    ($py:ident, $fname:expr, $args:expr, $kwargs:expr, $plist:tt {$($body:tt)*}) => {{
389        let args: $crate::PyTuple =
390            $crate::PyObject::from_borrowed_ptr($py, $args).unchecked_cast_into();
391        let kwargs: Option<$crate::PyDict> = $crate::argparse::get_kwargs($py, $kwargs);
392        let ret = $crate::py_argparse_impl!($py, $fname, &args, kwargs.as_ref(), {$($body)*}, $plist);
393        $crate::PyDrop::release_ref(args, $py);
394        $crate::PyDrop::release_ref(kwargs, $py);
395        ret
396    }};
397}
398
399#[inline]
400#[doc(hidden)]
401pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option<PyDict> {
402    if ptr.is_null() {
403        None
404    } else {
405        Some(PyObject::from_borrowed_ptr(py, ptr).unchecked_cast_into())
406    }
407}
408
409#[macro_export]
410#[doc(hidden)]
411macro_rules! py_argparse_param_description {
412    // normal parameter
413    { $pname:ident : $ptype:ty = [ $info:tt {} $rtype:tt ] } => (
414        $crate::argparse::ParamDescription {
415            name: stringify!($pname),
416            is_optional: false
417        }
418    );
419    // optional parameters
420    { $pname:ident : $ptype:ty = [ $info:tt {$default:expr} $rtype:tt ] } => (
421        $crate::argparse::ParamDescription {
422            name: stringify!($pname),
423            is_optional: true
424        }
425    );
426}
427
428#[macro_export]
429#[doc(hidden)]
430macro_rules! py_argparse_extract {
431    // base case
432    ( $py:expr, $iter:expr, {$($body:tt)*}, [] ) => {{ $($body)* }};
433    // normal parameter
434    ( $py:expr, $iter:expr, {$($body:tt)*},
435        [ { $pname:ident : $ptype:ty = [ {} {} {} ] } $($tail:tt)* ]
436    ) => {
437        // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed);
438        // second unwrap() asserts the parameter was not missing (which fn parse_args already checked for).
439        match <$ptype as $crate::FromPyObject>::extract($py, $iter.next().unwrap().as_ref().unwrap()) {
440            Ok($pname) => $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
441            Err(e) => Err(e)
442        }
443    };
444    // normal parameter with reference extraction
445    ( $py:expr, $iter:expr, {$($body:tt)*},
446        [ { $pname:ident : $ptype:ty = [ {} {} {$rtype:ty} ] } $($tail:tt)* ]
447    ) => {
448        // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed);
449        // second unwrap() asserts the parameter was not missing (which fn parse_args already checked for).
450        match <$rtype as $crate::RefFromPyObject>::with_extracted($py,
451            $iter.next().unwrap().as_ref().unwrap(),
452            |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*])
453        ) {
454            Ok(v) => v,
455            Err(e) => Err(e)
456        }
457    };
458    // maybe none parameter with reference extraction
459    ( $py:expr, $iter:expr, {$($body:tt)*},
460        [ { $pname:ident : $ptype:ty = [ {opt} {} {$rtype:ty} ] } $($tail:tt)* ]
461    ) => {{
462        // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed);
463        // second unwrap() asserts the parameter was not missing (which fn parse_args already checked for).
464        let v = $iter.next().unwrap().as_ref().unwrap();
465        let mut c = |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]);
466        let r = if v.is_none($py) {
467            Ok(c(None))
468        } else {
469            <$rtype as $crate::RefFromPyObject>::with_extracted($py, v, |r: &$rtype| c(Some(r)))
470        };
471        match r {
472            Ok(v) => v,
473            Err(e) => Err(e)
474        }
475    }};
476    // optional parameter
477    ( $py:expr, $iter:expr, {$($body:tt)*},
478        [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {} ] } $($tail:tt)* ]
479    ) => {
480        match $iter.next().unwrap().as_ref().map(|obj| obj.extract::<_>($py)).unwrap_or(Ok($default)) {
481            Ok($pname) => $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
482            Err(e) => Err(e)
483        }
484    };
485    // optional parameter with reference extraction
486    ( $py:expr, $iter:expr, {$($body:tt)*},
487        [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
488    ) => {
489        //unwrap() asserts the iterated sequence is long enough (which should be guaranteed);
490        $crate::argparse::with_extracted_or_default($py,
491            $iter.next().unwrap().as_ref(),
492            |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
493            $default)
494    };
495    // maybe none optional parameter with reference extraction
496    ( $py:expr, $iter:expr, {$($body:tt)*},
497        [ { $pname:ident : $ptype:ty = [ {opt} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ]
498    ) => {
499        //unwrap() asserts the iterated sequence is long enough (which should be guaranteed);
500        $crate::argparse::with_extracted_optional_or_default($py,
501            $iter.next().unwrap().as_ref(),
502            |$pname: $ptype| $crate::py_argparse_extract!($py, $iter, {$($body)*}, [$($tail)*]),
503            $default)
504    };
505}
506
507#[doc(hidden)] // used in py_argparse_extract!() macro
508pub fn with_extracted_or_default<P: ?Sized, R, F>(
509    py: Python,
510    obj: Option<&PyObject>,
511    f: F,
512    default: &'static P,
513) -> PyResult<R>
514where
515    F: FnOnce(&P) -> PyResult<R>,
516    P: RefFromPyObject,
517{
518    match obj {
519        Some(obj) => match P::with_extracted(py, obj, f) {
520            Ok(result) => result,
521            Err(e) => Err(e),
522        },
523        None => f(default),
524    }
525}
526
527#[doc(hidden)] // used in py_argparse_extract!() macro
528pub fn with_extracted_optional_or_default<P: ?Sized, R, F>(
529    py: Python,
530    obj: Option<&PyObject>,
531    f: F,
532    default: Option<&'static P>,
533) -> PyResult<R>
534where
535    F: FnOnce(Option<&P>) -> PyResult<R>,
536    P: RefFromPyObject,
537{
538    match obj {
539        Some(obj) => {
540            if obj.is_none(py) {
541                f(None)
542            } else {
543                match P::with_extracted(py, obj, |p| f(Some(p))) {
544                    Ok(result) => result,
545                    Err(e) => Err(e),
546                }
547            }
548        }
549        None => f(default),
550    }
551}
552
553#[cfg(test)]
554mod test {
555    use crate::conversion::ToPyObject;
556    use crate::objects::PyTuple;
557    use crate::python::{Python, PythonObject};
558
559    #[test]
560    pub fn test_parse() {
561        let gil_guard = Python::acquire_gil();
562        let py = gil_guard.python();
563        let mut called = false;
564        let tuple = ("abc", 42).to_py_object(py);
565        py_argparse!(py, None, &tuple, None, (x: &str, y: i32) {
566            assert_eq!(x, "abc");
567            assert_eq!(y, 42);
568            called = true;
569            Ok(())
570        })
571        .unwrap();
572        assert!(called);
573    }
574
575    #[test]
576    pub fn test_default_param_type() {
577        let gil_guard = Python::acquire_gil();
578        let py = gil_guard.python();
579        let mut called = false;
580        let tuple = ("abc",).to_py_object(py);
581        py_argparse!(py, None, &tuple, None, (x) {
582            assert_eq!(*x, tuple.get_item(py, 0));
583            called = true;
584            Ok(())
585        })
586        .unwrap();
587        assert!(called);
588    }
589
590    #[test]
591    pub fn test_default_value() {
592        let gil_guard = Python::acquire_gil();
593        let py = gil_guard.python();
594        let mut called = false;
595        let tuple = (0, "foo").to_py_object(py);
596        py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
597            assert_eq!(x, 0);
598            assert_eq!(y, "foo");
599            called = true;
600            Ok(())
601        })
602        .unwrap();
603        assert!(called);
604
605        let mut called = false;
606        let tuple = PyTuple::new(py, &[]);
607        py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") {
608            assert_eq!(x, 42);
609            assert_eq!(y, "abc");
610            called = true;
611            Ok(())
612        })
613        .unwrap();
614        assert!(called);
615    }
616}