Skip to content
Open
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
10 changes: 10 additions & 0 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1428,8 +1428,18 @@ this API may be passed as the ``action`` parameter to
>>> parser.parse_args(['--no-foo'])
Namespace(foo=False)

Single-dash long options are also supported.
For example, negative option ``-no-foo`` is automatically added for
positive option ``-foo``.
But no additional options are added for short options such as ``-f``.

.. versionadded:: 3.9

.. versionchanged:: next
Added support for single-dash options.

Added support for alternate prefix_chars_.


The parse_args() method
-----------------------
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ New modules
Improved modules
================

argparse
--------

* The :class:`~argparse.BooleanOptionalAction` action supports now single-dash
long options and alternate prefix characters.
(Contributed by Serhiy Storchaka in :gh:`138525`.)


dbm
---

Expand Down
18 changes: 14 additions & 4 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,15 +933,24 @@ def __init__(self,
deprecated=False):

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

if option_string.startswith('--'):
if option_string.startswith('--no-'):
if len(option_string) > 2 and option_string[0] == option_string[1]:
if option_string.startswith('no-', 2):
raise ValueError(f'invalid option name {option_string!r} '
f'for BooleanOptionalAction')
option_string = '--no-' + option_string[2:]
option_string = option_string[:2] + 'no-' + option_string[2:]
_option_strings.append(option_string)
neg_option_strings.append(option_string)
elif len(option_string) > 2 and option_string[0] != option_string[1]:
if option_string.startswith('no-', 1):
raise ValueError(f'invalid option name {option_string!r} '
f'for BooleanOptionalAction')
option_string = option_string[:1] + 'no-' + option_string[1:]
_option_strings.append(option_string)
neg_option_strings.append(option_string)

super().__init__(
option_strings=_option_strings,
Expand All @@ -951,11 +960,12 @@ def __init__(self,
required=required,
help=help,
deprecated=deprecated)
self.neg_option_strings = neg_option_strings


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-'))
setattr(namespace, self.dest, option_string not in self.neg_option_strings)

def format_usage(self):
return ' | '.join(self.option_strings)
Expand Down
68 changes: 68 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,74 @@ def test_invalid_name(self):
self.assertEqual(str(cm.exception),
"invalid option name '--no-foo' for BooleanOptionalAction")

class TestBooleanOptionalActionSingleDash(ParserTestCase):
"""Tests BooleanOptionalAction with single dash"""

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

def test_invalid_name(self):
parser = argparse.ArgumentParser()
with self.assertRaises(ValueError) as cm:
parser.add_argument('-no-foo', action=argparse.BooleanOptionalAction)
self.assertEqual(str(cm.exception),
"invalid option name '-no-foo' for BooleanOptionalAction")

class TestBooleanOptionalActionAlternatePrefixChars(ParserTestCase):
"""Tests BooleanOptionalAction with custom prefixes"""

parser_signature = Sig(prefix_chars='+-', add_help=False)
argument_signatures = [Sig('++foo', action=argparse.BooleanOptionalAction)]
failures = ['--foo', '--no-foo']
successes = [
('', NS(foo=None)),
('++foo', NS(foo=True)),
('++no-foo', NS(foo=False)),
]

def test_invalid_name(self):
parser = argparse.ArgumentParser(prefix_chars='+/')
with self.assertRaisesRegex(ValueError,
'BooleanOptionalAction.*is not valid for positional arguments'):
parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
with self.assertRaises(ValueError) as cm:
parser.add_argument('++no-foo', action=argparse.BooleanOptionalAction)
self.assertEqual(str(cm.exception),
"invalid option name '++no-foo' for BooleanOptionalAction")

class TestBooleanOptionalActionSingleAlternatePrefixChar(ParserTestCase):
"""Tests BooleanOptionalAction with single alternate prefix char"""

parser_signature = Sig(prefix_chars='+/', add_help=False)
argument_signatures = [
Sig('+foo', '+x', action=argparse.BooleanOptionalAction),
]
failures = ['++foo', '++no-foo', '-no-foo', '+no-x', '-no-x']
successes = [
('', NS(foo=None)),
('+foo', NS(foo=True)),
('+no-foo', NS(foo=False)),
('+x', NS(foo=True)),
]

def test_invalid_name(self):
parser = argparse.ArgumentParser(prefix_chars='+/')
with self.assertRaisesRegex(ValueError,
'BooleanOptionalAction.*is not valid for positional arguments'):
parser.add_argument('-foo', action=argparse.BooleanOptionalAction)
with self.assertRaises(ValueError) as cm:
parser.add_argument('+no-foo', action=argparse.BooleanOptionalAction)
self.assertEqual(str(cm.exception),
"invalid option name '+no-foo' for BooleanOptionalAction")

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for single-dash long options and alternate prefix characters in
:class:`argparse.BooleanOptionalAction`.
Loading