Skip to content

bpo-8538: Add support for boolean actions to argparse #11478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -798,9 +798,19 @@ how the command-line arguments should be handled. The supplied actions are:
PROG 2.0

You may also specify an arbitrary action by passing an Action subclass or
other object that implements the same interface. The recommended way to do
this is to extend :class:`Action`, overriding the ``__call__`` method
and optionally the ``__init__`` method.
other object that implements the same interface. The ``BooleanOptionalAction``
is available in ``argparse`` and adds support for boolean actions such as
``--foo`` and ``--no-foo``::

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
>>> parser.parse_args(['--no-foo'])
Namespace(foo=False)

The recommended way to create a custom action is to extend :class:`Action`,
overriding the ``__call__`` method and optionally the ``__init__`` and
``format_usage`` methods.

An example of a custom action::

Expand Down Expand Up @@ -1321,6 +1331,9 @@ Action instances should be callable, so subclasses must override the
The ``__call__`` method may perform arbitrary actions, but will typically set
attributes on the ``namespace`` based on ``dest`` and ``values``.

Action subclasses can define a ``format_usage`` method that takes no argument
and return a string which will be used when printing the usage of the program.
If such method is not provided, a sensible default will be used.

The parse_args() method
-----------------------
Expand Down
47 changes: 46 additions & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
'ArgumentParser',
'ArgumentError',
'ArgumentTypeError',
'BooleanOptionalAction',
'FileType',
'HelpFormatter',
'ArgumentDefaultsHelpFormatter',
Expand Down Expand Up @@ -447,7 +448,7 @@ def _format_actions_usage(self, actions, groups):
# if the Optional doesn't take a value, format is:
# -s or --long
if action.nargs == 0:
part = '%s' % option_string
part = action.format_usage()

# if the Optional takes a value, format is:
# -s ARGS or --long ARGS
Expand Down Expand Up @@ -832,9 +833,53 @@ def _get_kwargs(self):
]
return [(name, getattr(self, name)) for name in names]

def format_usage(self):
return self.option_strings[0]

def __call__(self, parser, namespace, values, option_string=None):
raise NotImplementedError(_('.__call__() not defined'))

class BooleanOptionalAction(Action):
def __init__(self,
option_strings,
dest,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None):

_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)

if option_string.startswith('--'):
option_string = '--no-' + option_string[2:]
_option_strings.append(option_string)

if help is not None and default is not None:
help += f" (default: {default})"

super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)

def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith('--no-'))

def format_usage(self):
return ' | '.join(self.option_strings)


class _StoreAction(Action):

Expand Down
56 changes: 45 additions & 11 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,30 @@ class TestOptionalsActionStoreTrue(ParserTestCase):
('--apple', NS(apple=True)),
]

class TestBooleanOptionalAction(ParserTestCase):
"""Tests BooleanOptionalAction"""

argument_signatures = [Sig('--foo', action=argparse.BooleanOptionalAction)]
failures = ['--foo bar', '--foo=bar']
successes = [
('', NS(foo=None)),
('--foo', NS(foo=True)),
('--no-foo', NS(foo=False)),
('--foo --no-foo', NS(foo=False)), # useful for aliases
('--no-foo --foo', NS(foo=True)),
]

class TestBooleanOptionalActionRequired(ParserTestCase):
"""Tests BooleanOptionalAction required"""

argument_signatures = [
Sig('--foo', required=True, action=argparse.BooleanOptionalAction)
]
failures = ['']
successes = [
('--foo', NS(foo=True)),
('--no-foo', NS(foo=False)),
]

class TestOptionalsActionAppend(ParserTestCase):
"""Tests the append action for an Optional"""
Expand Down Expand Up @@ -3375,6 +3399,10 @@ class TestHelpUsage(HelpTestCase):
Sig('a', help='a'),
Sig('b', help='b', nargs=2),
Sig('c', help='c', nargs='?'),
Sig('--foo', help='Whether to foo', action=argparse.BooleanOptionalAction),
Sig('--bar', help='Whether to bar', default=True,
action=argparse.BooleanOptionalAction),
Sig('-f', '--foobar', '--barfoo', action=argparse.BooleanOptionalAction),
]
argument_group_signatures = [
(Sig('group'), [
Expand All @@ -3385,26 +3413,32 @@ class TestHelpUsage(HelpTestCase):
])
]
usage = '''\
usage: PROG [-h] [-w W [W ...]] [-x [X [X ...]]] [-y [Y]] [-z Z Z Z]
usage: PROG [-h] [-w W [W ...]] [-x [X [X ...]]] [--foo | --no-foo]
[--bar | --no-bar]
[-f | --foobar | --no-foobar | --barfoo | --no-barfoo] [-y [Y]]
[-z Z Z Z]
a b b [c] [d [d ...]] e [e ...]
'''
help = usage + '''\

positional arguments:
a a
b b
c c
a a
b b
c c

optional arguments:
-h, --help show this help message and exit
-w W [W ...] w
-x [X [X ...]] x
-h, --help show this help message and exit
-w W [W ...] w
-x [X [X ...]] x
--foo, --no-foo Whether to foo
--bar, --no-bar Whether to bar (default: True)
-f, --foobar, --no-foobar, --barfoo, --no-barfoo

group:
-y [Y] y
-z Z Z Z z
d d
e e
-y [Y] y
-z Z Z Z z
d d
e e
'''
version = ''

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for boolean actions like ``--foo`` and ``--no-foo`` to argparse.
Patch contributed by Rémi Lapeyre.