[Python-checkins] (no subject)

Stéphane Wirtel webhook-mailer at python.org
Fri Sep 13 06:17:47 EDT 2019




To: python-checkins at python.org
Subject: bpo-8538: Add support for boolean actions to argparse (GH-11478)
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0

https://github.com/python/cpython/commit/6a517c674907c195660fa9178a7b561de49c=
c721
commit: 6a517c674907c195660fa9178a7b561de49cc721
branch: master
author: R=C3=A9mi Lapeyre <remi.lapeyre at henki.fr>
committer: St=C3=A9phane Wirtel <stephane at wirtel.be>
date: 2019-09-13T11:17:43+01:00
summary:

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

Co-Authored-By: remilapeyre <remi.lapeyre at henki.fr>

files:
A Misc/NEWS.d/next/Library/2019-01-09-16-18-52.bpo-8538.PfVZia.rst
M Doc/library/argparse.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 6dffd2e9325e..a8aeca41a70a 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -839,9 +839,19 @@ how the command-line arguments should be handled. The su=
pplied actions are:
     Namespace(foo=3D['f1', 'f2', 'f3', 'f4'])
=20
 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 =3D argparse.ArgumentParser()
+    >>> parser.add_argument('--foo', action=3Dargparse.BooleanOptionalAction)
+    >>> parser.parse_args(['--no-foo'])
+    Namespace(foo=3DFalse)
+
+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.
=20
 An example of a custom action::
=20
@@ -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
 attributes on the ``namespace`` based on ``dest`` and ``values``.
=20
+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 progra=
m.
+If such method is not provided, a sensible default will be used.
=20
 The parse_args() method
 -----------------------
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 370692bd1bf4..13af7ac23921 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -67,6 +67,7 @@
     'ArgumentParser',
     'ArgumentError',
     'ArgumentTypeError',
+    'BooleanOptionalAction',
     'FileType',
     'HelpFormatter',
     'ArgumentDefaultsHelpFormatter',
@@ -454,7 +455,7 @@ def _format_actions_usage(self, actions, groups):
                 # if the Optional doesn't take a value, format is:
                 #    -s or --long
                 if action.nargs =3D=3D 0:
-                    part =3D '%s' % option_string
+                    part =3D action.format_usage()
=20
                 # if the Optional takes a value, format is:
                 #    -s ARGS or --long ARGS
@@ -842,9 +843,53 @@ def _get_kwargs(self):
         ]
         return [(name, getattr(self, name)) for name in names]
=20
+    def format_usage(self):
+        return self.option_strings[0]
+
     def __call__(self, parser, namespace, values, option_string=3DNone):
         raise NotImplementedError(_('.__call__() not defined'))
=20
+class BooleanOptionalAction(Action):
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const=3DNone,
+                 default=3DNone,
+                 type=3DNone,
+                 choices=3DNone,
+                 required=3DFalse,
+                 help=3DNone,
+                 metavar=3DNone):
+
+        _option_strings =3D []
+        for option_string in option_strings:
+            _option_strings.append(option_string)
+
+            if option_string.startswith('--'):
+                option_string =3D '--no-' + option_string[2:]
+                _option_strings.append(option_string)
+
+        if help is not None and default is not None:
+            help +=3D f" (default: {default})"
+
+        super().__init__(
+            option_strings=3D_option_strings,
+            dest=3Ddest,
+            nargs=3D0,
+            default=3Ddefault,
+            type=3Dtype,
+            choices=3Dchoices,
+            required=3Drequired,
+            help=3Dhelp,
+            metavar=3Dmetavar)
+
+    def __call__(self, parser, namespace, values, option_string=3DNone):
+        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)
+
=20
 class _StoreAction(Action):
=20
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 464db2906998..a97c921852c7 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -686,6 +686,30 @@ class TestOptionalsActionStoreTrue(ParserTestCase):
         ('--apple', NS(apple=3DTrue)),
     ]
=20
+class TestBooleanOptionalAction(ParserTestCase):
+    """Tests BooleanOptionalAction"""
+
+    argument_signatures =3D [Sig('--foo', action=3Dargparse.BooleanOptionalA=
ction)]
+    failures =3D ['--foo bar', '--foo=3Dbar']
+    successes =3D [
+        ('', NS(foo=3DNone)),
+        ('--foo', NS(foo=3DTrue)),
+        ('--no-foo', NS(foo=3DFalse)),
+        ('--foo --no-foo', NS(foo=3DFalse)),  # useful for aliases
+        ('--no-foo --foo', NS(foo=3DTrue)),
+    ]
+
+class TestBooleanOptionalActionRequired(ParserTestCase):
+    """Tests BooleanOptionalAction required"""
+
+    argument_signatures =3D [
+        Sig('--foo', required=3DTrue, action=3Dargparse.BooleanOptionalActio=
n)
+    ]
+    failures =3D ['']
+    successes =3D [
+        ('--foo', NS(foo=3DTrue)),
+        ('--no-foo', NS(foo=3DFalse)),
+    ]
=20
 class TestOptionalsActionAppend(ParserTestCase):
     """Tests the append action for an Optional"""
@@ -3456,6 +3480,10 @@ class TestHelpUsage(HelpTestCase):
         Sig('a', help=3D'a'),
         Sig('b', help=3D'b', nargs=3D2),
         Sig('c', help=3D'c', nargs=3D'?'),
+        Sig('--foo', help=3D'Whether to foo', action=3Dargparse.BooleanOptio=
nalAction),
+        Sig('--bar', help=3D'Whether to bar', default=3DTrue,
+                     action=3Dargparse.BooleanOptionalAction),
+        Sig('-f', '--foobar', '--barfoo', action=3Dargparse.BooleanOptionalA=
ction),
     ]
     argument_group_signatures =3D [
         (Sig('group'), [
@@ -3466,26 +3494,32 @@ class TestHelpUsage(HelpTestCase):
         ])
     ]
     usage =3D '''\
-        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 =3D usage + '''\
=20
         positional arguments:
-          a               a
-          b               b
-          c               c
+          a                     a
+          b                     b
+          c                     c
=20
         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
=20
         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 =3D ''
=20
diff --git a/Misc/NEWS.d/next/Library/2019-01-09-16-18-52.bpo-8538.PfVZia.rst=
 b/Misc/NEWS.d/next/Library/2019-01-09-16-18-52.bpo-8538.PfVZia.rst
new file mode 100644
index 000000000000..94249ab1e434
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-09-16-18-52.bpo-8538.PfVZia.rst
@@ -0,0 +1,2 @@
+Add support for boolean actions like ``--foo`` and ``--no-foo`` to argparse.
+Patch contributed by R=C3=A9mi Lapeyre.



More information about the Python-checkins mailing list