mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-138697: Fix inferring dest from a single-dash long option in argparse (#138699)
* gh-138697: Fix inferring dest from a single-dash long option in argparse If a short option and a single-dash long option are passed to add_argument(), dest is now inferred from the single-dash long option. * Make double-dash options taking priority over single-dash long options. --------- Co-authored-by: Savannah Ostrowski <savannah@python.org>
This commit is contained in:
parent
b3383085f9
commit
77cb39e0c7
5 changed files with 58 additions and 22 deletions
|
|
@ -1322,8 +1322,12 @@ attribute is determined by the ``dest`` keyword argument of
|
||||||
|
|
||||||
For optional argument actions, the value of ``dest`` is normally inferred from
|
For optional argument actions, the value of ``dest`` is normally inferred from
|
||||||
the option strings. :class:`ArgumentParser` generates the value of ``dest`` by
|
the option strings. :class:`ArgumentParser` generates the value of ``dest`` by
|
||||||
taking the first long option string and stripping away the initial ``--``
|
taking the first double-dash long option string and stripping away the initial
|
||||||
string. If no long option strings were supplied, ``dest`` will be derived from
|
``-`` characters.
|
||||||
|
If no double-dash long option strings were supplied, ``dest`` will be derived
|
||||||
|
from the first single-dash long option string by stripping the initial ``-``
|
||||||
|
character.
|
||||||
|
If no long option strings were supplied, ``dest`` will be derived from
|
||||||
the first short option string by stripping the initial ``-`` character. Any
|
the first short option string by stripping the initial ``-`` character. Any
|
||||||
internal ``-`` characters will be converted to ``_`` characters to make sure
|
internal ``-`` characters will be converted to ``_`` characters to make sure
|
||||||
the string is a valid attribute name. The examples below illustrate this
|
the string is a valid attribute name. The examples below illustrate this
|
||||||
|
|
@ -1331,11 +1335,12 @@ behavior::
|
||||||
|
|
||||||
>>> parser = argparse.ArgumentParser()
|
>>> parser = argparse.ArgumentParser()
|
||||||
>>> parser.add_argument('-f', '--foo-bar', '--foo')
|
>>> parser.add_argument('-f', '--foo-bar', '--foo')
|
||||||
|
>>> parser.add_argument('-q', '-quz')
|
||||||
>>> parser.add_argument('-x', '-y')
|
>>> parser.add_argument('-x', '-y')
|
||||||
>>> parser.parse_args('-f 1 -x 2'.split())
|
>>> parser.parse_args('-f 1 -q 2 -x 3'.split())
|
||||||
Namespace(foo_bar='1', x='2')
|
Namespace(foo_bar='1', quz='2', x='3')
|
||||||
>>> parser.parse_args('--foo 1 -y 2'.split())
|
>>> parser.parse_args('--foo 1 -quz 2 -y 3'.split())
|
||||||
Namespace(foo_bar='1', x='2')
|
Namespace(foo_bar='1', quz='2', x='2')
|
||||||
|
|
||||||
``dest`` allows a custom attribute name to be provided::
|
``dest`` allows a custom attribute name to be provided::
|
||||||
|
|
||||||
|
|
@ -1344,6 +1349,9 @@ behavior::
|
||||||
>>> parser.parse_args('--foo XXX'.split())
|
>>> parser.parse_args('--foo XXX'.split())
|
||||||
Namespace(bar='XXX')
|
Namespace(bar='XXX')
|
||||||
|
|
||||||
|
.. versionchanged:: next
|
||||||
|
Single-dash long option now takes precedence over short options.
|
||||||
|
|
||||||
|
|
||||||
.. _deprecated:
|
.. _deprecated:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1275,3 +1275,10 @@ that may require changes to your code.
|
||||||
Use its :meth:`!close` method or the :func:`contextlib.closing` context
|
Use its :meth:`!close` method or the :func:`contextlib.closing` context
|
||||||
manager to close it.
|
manager to close it.
|
||||||
(Contributed by Osama Abdelkader and Serhiy Storchaka in :gh:`140601`.)
|
(Contributed by Osama Abdelkader and Serhiy Storchaka in :gh:`140601`.)
|
||||||
|
|
||||||
|
* If a short option and a single-dash long option are passed to
|
||||||
|
:meth:`argparse.ArgumentParser.add_argument`, *dest* is now inferred from
|
||||||
|
the single-dash long option. For example, in ``add_argument('-f', '-foo')``,
|
||||||
|
*dest* is now ``'foo'`` instead of ``'f'``.
|
||||||
|
Pass an explicit *dest* argument to preserve the old behavior.
|
||||||
|
(Contributed by Serhiy Storchaka in :gh:`138697`.)
|
||||||
|
|
|
||||||
|
|
@ -1660,29 +1660,35 @@ def _get_positional_kwargs(self, dest, **kwargs):
|
||||||
def _get_optional_kwargs(self, *args, **kwargs):
|
def _get_optional_kwargs(self, *args, **kwargs):
|
||||||
# determine short and long option strings
|
# determine short and long option strings
|
||||||
option_strings = []
|
option_strings = []
|
||||||
long_option_strings = []
|
|
||||||
for option_string in args:
|
for option_string in args:
|
||||||
# error on strings that don't start with an appropriate prefix
|
# error on strings that don't start with an appropriate prefix
|
||||||
if not option_string[0] in self.prefix_chars:
|
if not option_string[0] in self.prefix_chars:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'invalid option string {option_string!r}: '
|
f'invalid option string {option_string!r}: '
|
||||||
f'must start with a character {self.prefix_chars!r}')
|
f'must start with a character {self.prefix_chars!r}')
|
||||||
|
|
||||||
# strings starting with two prefix characters are long options
|
|
||||||
option_strings.append(option_string)
|
option_strings.append(option_string)
|
||||||
if len(option_string) > 1 and option_string[1] in self.prefix_chars:
|
|
||||||
long_option_strings.append(option_string)
|
|
||||||
|
|
||||||
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
||||||
dest = kwargs.pop('dest', None)
|
dest = kwargs.pop('dest', None)
|
||||||
if dest is None:
|
if dest is None:
|
||||||
if long_option_strings:
|
priority = 0
|
||||||
dest_option_string = long_option_strings[0]
|
for option_string in option_strings:
|
||||||
else:
|
if len(option_string) <= 2:
|
||||||
dest_option_string = option_strings[0]
|
# short option: '-x' -> 'x'
|
||||||
dest = dest_option_string.lstrip(self.prefix_chars)
|
if priority < 1:
|
||||||
|
dest = option_string.lstrip(self.prefix_chars)
|
||||||
|
priority = 1
|
||||||
|
elif option_string[1] not in self.prefix_chars:
|
||||||
|
# single-dash long option: '-foo' -> 'foo'
|
||||||
|
if priority < 2:
|
||||||
|
dest = option_string.lstrip(self.prefix_chars)
|
||||||
|
priority = 2
|
||||||
|
else:
|
||||||
|
# two-dash long option: '--foo' -> 'foo'
|
||||||
|
dest = option_string.lstrip(self.prefix_chars)
|
||||||
|
break
|
||||||
if not dest:
|
if not dest:
|
||||||
msg = f'dest= is required for options like {option_string!r}'
|
msg = f'dest= is required for options like {repr(option_strings)[1:-1]}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
dest = dest.replace('-', '_')
|
dest = dest.replace('-', '_')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -581,13 +581,22 @@ class TestOptionalsShortLong(ParserTestCase):
|
||||||
class TestOptionalsDest(ParserTestCase):
|
class TestOptionalsDest(ParserTestCase):
|
||||||
"""Tests various means of setting destination"""
|
"""Tests various means of setting destination"""
|
||||||
|
|
||||||
argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')]
|
argument_signatures = [
|
||||||
|
Sig('-x', '-foobar', '--foo-bar', '-barfoo', '-X'),
|
||||||
|
Sig('--baz', dest='zabbaz'),
|
||||||
|
Sig('-y', '-qux', '-Y'),
|
||||||
|
Sig('-z'),
|
||||||
|
]
|
||||||
failures = ['a']
|
failures = ['a']
|
||||||
successes = [
|
successes = [
|
||||||
('--foo-bar f', NS(foo_bar='f', zabbaz=None)),
|
('--foo-bar f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
|
||||||
('--baz g', NS(foo_bar=None, zabbaz='g')),
|
('-x f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
|
||||||
('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i')),
|
('--baz g', NS(foo_bar=None, zabbaz='g', qux=None, z=None)),
|
||||||
('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j')),
|
('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i', qux=None, z=None)),
|
||||||
|
('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j', qux=None, z=None)),
|
||||||
|
('-qux l', NS(foo_bar=None, zabbaz=None, qux='l', z=None)),
|
||||||
|
('-y l', NS(foo_bar=None, zabbaz=None, qux='l', z=None)),
|
||||||
|
('-z m', NS(foo_bar=None, zabbaz=None, qux=None, z='m')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5611,6 +5620,8 @@ def test_invalid_option_strings(self):
|
||||||
self.assertTypeError('-', errmsg='dest= is required')
|
self.assertTypeError('-', errmsg='dest= is required')
|
||||||
self.assertTypeError('--', errmsg='dest= is required')
|
self.assertTypeError('--', errmsg='dest= is required')
|
||||||
self.assertTypeError('---', errmsg='dest= is required')
|
self.assertTypeError('---', errmsg='dest= is required')
|
||||||
|
self.assertTypeError('-', '--', '---',
|
||||||
|
errmsg="dest= is required for options like '-', '--', '---'")
|
||||||
|
|
||||||
def test_invalid_prefix(self):
|
def test_invalid_prefix(self):
|
||||||
self.assertValueError('--foo', '+foo',
|
self.assertValueError('--foo', '+foo',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fix inferring *dest* from a single-dash long option in :mod:`argparse`. If a
|
||||||
|
short option and a single-dash long option are passed to
|
||||||
|
:meth:`!add_argument`, *dest* is now inferred from the single-dash long
|
||||||
|
option.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue