Commit 6a517c67 authored by Rémi Lapeyre's avatar Rémi Lapeyre Committed by Stéphane Wirtel

bpo-8538: Add support for boolean actions to argparse (GH-11478)

Co-Authored-By: default avatarremilapeyre <remi.lapeyre@henki.fr>
parent 04f0bbfb
...@@ -839,9 +839,19 @@ how the command-line arguments should be handled. The supplied actions are: ...@@ -839,9 +839,19 @@ how the command-line arguments should be handled. The supplied actions are:
Namespace(foo=['f1', 'f2', 'f3', 'f4']) Namespace(foo=['f1', 'f2', 'f3', 'f4'])
You may also specify an arbitrary action by passing an Action subclass or 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 other object that implements the same interface. The ``BooleanOptionalAction``
this is to extend :class:`Action`, overriding the ``__call__`` method is available in ``argparse`` and adds support for boolean actions such as
and optionally the ``__init__`` method. ``--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:: An example of a custom action::
...@@ -1361,6 +1371,9 @@ Action instances should be callable, so subclasses must override the ...@@ -1361,6 +1371,9 @@ Action instances should be callable, so subclasses must override the
The ``__call__`` method may perform arbitrary actions, but will typically set The ``__call__`` method may perform arbitrary actions, but will typically set
attributes on the ``namespace`` based on ``dest`` and ``values``. 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 The parse_args() method
----------------------- -----------------------
......
...@@ -67,6 +67,7 @@ __all__ = [ ...@@ -67,6 +67,7 @@ __all__ = [
'ArgumentParser', 'ArgumentParser',
'ArgumentError', 'ArgumentError',
'ArgumentTypeError', 'ArgumentTypeError',
'BooleanOptionalAction',
'FileType', 'FileType',
'HelpFormatter', 'HelpFormatter',
'ArgumentDefaultsHelpFormatter', 'ArgumentDefaultsHelpFormatter',
...@@ -454,7 +455,7 @@ class HelpFormatter(object): ...@@ -454,7 +455,7 @@ class HelpFormatter(object):
# if the Optional doesn't take a value, format is: # if the Optional doesn't take a value, format is:
# -s or --long # -s or --long
if action.nargs == 0: if action.nargs == 0:
part = '%s' % option_string part = action.format_usage()
# if the Optional takes a value, format is: # if the Optional takes a value, format is:
# -s ARGS or --long ARGS # -s ARGS or --long ARGS
...@@ -842,9 +843,53 @@ class Action(_AttributeHolder): ...@@ -842,9 +843,53 @@ class Action(_AttributeHolder):
] ]
return [(name, getattr(self, name)) for name in names] 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): def __call__(self, parser, namespace, values, option_string=None):
raise NotImplementedError(_('.__call__() not defined')) 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): class _StoreAction(Action):
......
...@@ -686,6 +686,30 @@ class TestOptionalsActionStoreTrue(ParserTestCase): ...@@ -686,6 +686,30 @@ class TestOptionalsActionStoreTrue(ParserTestCase):
('--apple', NS(apple=True)), ('--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): class TestOptionalsActionAppend(ParserTestCase):
"""Tests the append action for an Optional""" """Tests the append action for an Optional"""
...@@ -3456,6 +3480,10 @@ class TestHelpUsage(HelpTestCase): ...@@ -3456,6 +3480,10 @@ class TestHelpUsage(HelpTestCase):
Sig('a', help='a'), Sig('a', help='a'),
Sig('b', help='b', nargs=2), Sig('b', help='b', nargs=2),
Sig('c', help='c', nargs='?'), 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 = [ argument_group_signatures = [
(Sig('group'), [ (Sig('group'), [
...@@ -3466,26 +3494,32 @@ class TestHelpUsage(HelpTestCase): ...@@ -3466,26 +3494,32 @@ class TestHelpUsage(HelpTestCase):
]) ])
] ]
usage = '''\ 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 ...] a b b [c] [d [d ...]] e [e ...]
''' '''
help = usage + '''\ help = usage + '''\
positional arguments: positional arguments:
a a a a
b b b b
c c c c
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-w W [W ...] w -w W [W ...] w
-x [X [X ...]] x -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: group:
-y [Y] y -y [Y] y
-z Z Z Z z -z Z Z Z z
d d d d
e e e e
''' '''
version = '' version = ''
......
Add support for boolean actions like ``--foo`` and ``--no-foo`` to argparse.
Patch contributed by Rémi Lapeyre.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment