Skip to content

Commit 6e75c17

Browse files
committed
Add convert_choices to add_argument
This allows users to specify choices in the same vocabulary as the user will enter them.
1 parent ad9b851 commit 6e75c17

File tree

3 files changed

+49
-59
lines changed

3 files changed

+49
-59
lines changed

Doc/library/argparse.rst

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ ArgumentParser objects
7474
prefix_chars='-', fromfile_prefix_chars=None, \
7575
argument_default=None, conflict_handler='error', \
7676
add_help=True, allow_abbrev=True, exit_on_error=True, \
77-
suggest_on_error=False, convert_choices=False)
77+
suggest_on_error=False)
7878

7979
Create a new :class:`ArgumentParser` object. All parameters should be passed
8080
as keyword arguments. Each parameter has its own more detailed description
@@ -119,9 +119,6 @@ ArgumentParser objects
119119
* suggest_on_error_ - Enables suggestions for mistyped argument choices
120120
and subparser names (default: ``False``)
121121

122-
* convert_choices_ - Runs the ``choices`` through the ``type`` callable
123-
during checking (default: ``False``)
124-
125122

126123
.. versionchanged:: 3.5
127124
*allow_abbrev* parameter was added.
@@ -615,38 +612,6 @@ keyword argument::
615612
.. versionadded:: 3.14
616613

617614

618-
convert_choices
619-
^^^^^^^^^^^^^^^
620-
621-
By default, when a user passes both a ``type`` and a ``choices`` argument, the
622-
``choices`` need to be specified in the target type, after conversion.
623-
This can cause confusing ``usage`` and ``help`` strings. If the user would like
624-
to specify ``choices`` in the same vocabulary as the end-user would enter them,
625-
this feature can be enabled by setting ``convert_choices`` to ``True``::
626-
627-
>>> parser = argparse.ArgumentParser(convert_choices=True)
628-
>>> parser.add_argument('when',
629-
... choices=['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'],
630-
... type=to_dow)
631-
>>> parser.print_help()
632-
usage: example_broken.py [-h] [--days {mo,tu,we,th,fr,sa,su}]
633-
634-
options:
635-
-h, --help show this help message and exit
636-
--days {mo,tu,we,th,fr,sa,su}
637-
638-
639-
If you're writing code that needs to be compatible with older Python versions
640-
and want to opportunistically use ``convert_choices`` when it's available, you
641-
can set it as an attribute after initializing the parser instead of using the
642-
keyword argument::
643-
644-
>>> parser = argparse.ArgumentParser()
645-
>>> parser.convert_choices = True
646-
647-
.. versionadded:: next
648-
649-
650615
The add_argument() method
651616
-------------------------
652617

@@ -674,6 +639,9 @@ The add_argument() method
674639

675640
* choices_ - A sequence of the allowable values for the argument.
676641

642+
* convert_choices_ - Whether to convert the choices_ using the type_ callable
643+
before checking.
644+
677645
* required_ - Whether or not the command-line option may be omitted
678646
(optionals only).
679647

@@ -1159,24 +1127,39 @@ if the argument was not one of the acceptable values::
11591127
game.py: error: argument move: invalid choice: 'fire' (choose from 'rock',
11601128
'paper', 'scissors')
11611129

1162-
Note that, by default, inclusion in the *choices* sequence is checked after
1163-
any type_ conversions have been performed, so the type of the objects in the
1164-
*choices* sequence should match the type_ specified. This can lead to
1165-
confusing ``usage`` messages. If you want to convert *choices* using type_
1166-
before checking, set the ``convert_choices`` flag on :class:`~ArgumentParser`.
1167-
1168-
11691130
Any sequence can be passed as the *choices* value, so :class:`list` objects,
11701131
:class:`tuple` objects, and custom sequences are all supported.
11711132

1172-
Use of :class:`enum.Enum` is not recommended because it is difficult to
1173-
control its appearance in usage, help, and error messages.
1174-
11751133
Formatted choices override the default *metavar* which is normally derived
11761134
from *dest*. This is usually what you want because the user never sees the
11771135
*dest* parameter. If this display isn't desirable (perhaps because there are
11781136
many choices), just specify an explicit metavar_.
11791137

1138+
.. _convert_choices:
1139+
1140+
convert_choices
1141+
^^^^^^^^^^^^^^^
1142+
1143+
By default, when a user passes both a ``type`` and a ``choices`` argument, the
1144+
``choices`` need to be specified in the target type, after conversion.
1145+
This can cause confusing ``usage`` and ``help`` strings. If the user would like
1146+
to specify ``choices`` in the same vocabulary as the end-user would enter them,
1147+
this feature can be enabled by setting ``convert_choices`` to ``True``::
1148+
1149+
>>> parser = argparse.ArgumentParser()
1150+
>>> parser.add_argument('when',
1151+
... choices=['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'],
1152+
... convert_choices=True,
1153+
... type=to_dow)
1154+
>>> parser.print_help()
1155+
usage: example_broken.py [-h] [--days {mo,tu,we,th,fr,sa,su}]
1156+
1157+
options:
1158+
-h, --help show this help message and exit
1159+
--days {mo,tu,we,th,fr,sa,su}
1160+
1161+
.. versionadded:: next
1162+
11801163

11811164
.. _required:
11821165

Lib/argparse.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,9 @@ class Action(_AttributeHolder):
792792
type, an exception will be raised if it is not a member of this
793793
collection.
794794
795+
- convert_choices - Runs the ``choices`` through the ``type`` callable
796+
during checking. (default: ``False``)
797+
795798
- required -- True if the action must always be specified at the
796799
command line. This is only meaningful for optional command-line
797800
arguments.
@@ -810,6 +813,7 @@ def __init__(self,
810813
default=None,
811814
type=None,
812815
choices=None,
816+
convert_choices=False,
813817
required=False,
814818
help=None,
815819
metavar=None,
@@ -821,6 +825,7 @@ def __init__(self,
821825
self.default = default
822826
self.type = type
823827
self.choices = choices
828+
self.convert_choices = convert_choices
824829
self.required = required
825830
self.help = help
826831
self.metavar = metavar
@@ -835,6 +840,7 @@ def _get_kwargs(self):
835840
'default',
836841
'type',
837842
'choices',
843+
'convert_choices',
838844
'required',
839845
'help',
840846
'metavar',
@@ -897,6 +903,7 @@ def __init__(self,
897903
default=None,
898904
type=None,
899905
choices=None,
906+
convert_choices=False,
900907
required=False,
901908
help=None,
902909
metavar=None,
@@ -915,6 +922,7 @@ def __init__(self,
915922
default=default,
916923
type=type,
917924
choices=choices,
925+
convert_choices=convert_choices,
918926
required=required,
919927
help=help,
920928
metavar=metavar,
@@ -1777,8 +1785,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
17771785
error info when an error occurs
17781786
- suggest_on_error - Enables suggestions for mistyped argument choices
17791787
and subparser names. (default: ``False``)
1780-
- convert_choices - Runs the ``choices`` through the ``type`` callable
1781-
during checking. (default: ``False``)
17821788
"""
17831789

17841790
def __init__(self,
@@ -1795,8 +1801,7 @@ def __init__(self,
17951801
add_help=True,
17961802
allow_abbrev=True,
17971803
exit_on_error=True,
1798-
suggest_on_error=False,
1799-
convert_choices=False):
1804+
suggest_on_error=False):
18001805

18011806
superinit = super(ArgumentParser, self).__init__
18021807
superinit(description=description,
@@ -1813,7 +1818,6 @@ def __init__(self,
18131818
self.allow_abbrev = allow_abbrev
18141819
self.exit_on_error = exit_on_error
18151820
self.suggest_on_error = suggest_on_error
1816-
self.convert_choices = convert_choices
18171821

18181822
add_group = self.add_argument_group
18191823
self._positionals = add_group(_('positional arguments'))
@@ -2589,9 +2593,8 @@ def _check_value(self, action, value, arg_string=None):
25892593
choices = iter(choices)
25902594

25912595
typed_choices = []
2592-
if (self.convert_choices and
2593-
action.type and
2594-
all(isinstance(choice, str) for choice in choices)
2596+
if (action.convert_choices and
2597+
action.type
25952598
):
25962599
typed_choices = [action.type(v) for v in choices]
25972600

Lib/test/test_argparse.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,10 +1957,11 @@ def to_dow(arg):
19571957
class TestTypedChoices(TestChoices):
19581958
"""Test a set of string choices that convert to weekdays"""
19591959

1960-
parser_signature = Sig(convert_choices=True)
19611960
argument_signatures = [
19621961
Sig('when',
1963-
type=TestChoices.to_dow, choices=["mo", "tu", "we" , "th", "fr", "sa", "su"],
1962+
type=TestChoices.to_dow,
1963+
choices=["mo", "tu", "we" , "th", "fr", "sa", "su"],
1964+
convert_choices=True,
19641965
)
19651966
]
19661967

@@ -5518,11 +5519,12 @@ def to_date(arg):
55185519
else:
55195520
return None
55205521

5521-
parser_signature = Sig(prog='PROG', convert_choices=True)
5522+
parser_signature = Sig(prog='PROG')
55225523
argument_signatures = [
55235524
Sig('when',
55245525
type=to_date,
5525-
choices=["today", "tomorrow"]
5526+
choices=["today", "tomorrow"],
5527+
convert_choices=True
55265528
),
55275529
]
55285530

@@ -5932,7 +5934,8 @@ def test_optional(self):
59325934
string = (
59335935
"Action(option_strings=['--foo', '-a', '-b'], dest='b', "
59345936
"nargs='+', const=None, default=42, type='int', "
5935-
"choices=[1, 2, 3], required=False, help='HELP', "
5937+
"choices=[1, 2, 3], convert_choices=False, "
5938+
"required=False, help='HELP', "
59365939
"metavar='METAVAR', deprecated=False)")
59375940
self.assertStringEqual(option, string)
59385941

@@ -5950,6 +5953,7 @@ def test_argument(self):
59505953
string = (
59515954
"Action(option_strings=[], dest='x', nargs='?', "
59525955
"const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], "
5956+
"convert_choices=False, "
59535957
"required=True, help='H HH H', metavar='MV MV MV', "
59545958
"deprecated=False)" % float)
59555959
self.assertStringEqual(argument, string)

0 commit comments

Comments
 (0)