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
|
||||
the option strings. :class:`ArgumentParser` generates the value of ``dest`` by
|
||||
taking the first long option string and stripping away the initial ``--``
|
||||
string. If no long option strings were supplied, ``dest`` will be derived from
|
||||
taking the first double-dash long option string and stripping away the initial
|
||||
``-`` 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
|
||||
internal ``-`` characters will be converted to ``_`` characters to make sure
|
||||
the string is a valid attribute name. The examples below illustrate this
|
||||
|
|
@ -1331,11 +1335,12 @@ behavior::
|
|||
|
||||
>>> parser = argparse.ArgumentParser()
|
||||
>>> parser.add_argument('-f', '--foo-bar', '--foo')
|
||||
>>> parser.add_argument('-q', '-quz')
|
||||
>>> parser.add_argument('-x', '-y')
|
||||
>>> parser.parse_args('-f 1 -x 2'.split())
|
||||
Namespace(foo_bar='1', x='2')
|
||||
>>> parser.parse_args('--foo 1 -y 2'.split())
|
||||
Namespace(foo_bar='1', x='2')
|
||||
>>> parser.parse_args('-f 1 -q 2 -x 3'.split())
|
||||
Namespace(foo_bar='1', quz='2', x='3')
|
||||
>>> parser.parse_args('--foo 1 -quz 2 -y 3'.split())
|
||||
Namespace(foo_bar='1', quz='2', x='2')
|
||||
|
||||
``dest`` allows a custom attribute name to be provided::
|
||||
|
||||
|
|
@ -1344,6 +1349,9 @@ behavior::
|
|||
>>> parser.parse_args('--foo XXX'.split())
|
||||
Namespace(bar='XXX')
|
||||
|
||||
.. versionchanged:: next
|
||||
Single-dash long option now takes precedence over short options.
|
||||
|
||||
|
||||
.. _deprecated:
|
||||
|
||||
|
|
|
|||
|
|
@ -1275,3 +1275,10 @@ that may require changes to your code.
|
|||
Use its :meth:`!close` method or the :func:`contextlib.closing` context
|
||||
manager to close it.
|
||||
(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):
|
||||
# determine short and long option strings
|
||||
option_strings = []
|
||||
long_option_strings = []
|
||||
for option_string in args:
|
||||
# error on strings that don't start with an appropriate prefix
|
||||
if not option_string[0] in self.prefix_chars:
|
||||
raise ValueError(
|
||||
f'invalid option string {option_string!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)
|
||||
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'
|
||||
dest = kwargs.pop('dest', None)
|
||||
if dest is None:
|
||||
if long_option_strings:
|
||||
dest_option_string = long_option_strings[0]
|
||||
else:
|
||||
dest_option_string = option_strings[0]
|
||||
dest = dest_option_string.lstrip(self.prefix_chars)
|
||||
priority = 0
|
||||
for option_string in option_strings:
|
||||
if len(option_string) <= 2:
|
||||
# short option: '-x' -> 'x'
|
||||
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:
|
||||
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)
|
||||
dest = dest.replace('-', '_')
|
||||
|
||||
|
|
|
|||
|
|
@ -581,13 +581,22 @@ class TestOptionalsShortLong(ParserTestCase):
|
|||
class TestOptionalsDest(ParserTestCase):
|
||||
"""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']
|
||||
successes = [
|
||||
('--foo-bar f', NS(foo_bar='f', zabbaz=None)),
|
||||
('--baz g', NS(foo_bar=None, zabbaz='g')),
|
||||
('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i')),
|
||||
('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j')),
|
||||
('--foo-bar f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
|
||||
('-x f', NS(foo_bar='f', zabbaz=None, qux=None, z=None)),
|
||||
('--baz g', NS(foo_bar=None, zabbaz='g', qux=None, z=None)),
|
||||
('--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 for options like '-', '--', '---'")
|
||||
|
||||
def test_invalid_prefix(self):
|
||||
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