From 0244cd8d758f0608b3a2257bc61ea1cf946aafe4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 15 Jan 2024 19:04:06 +0200 Subject: [PATCH 1/7] gh-18208: Support deprecation of options, arguments and subcommands in argparse --- Doc/library/argparse.rst | 48 +++++- Doc/whatsnew/3.13.rst | 8 + Lib/argparse.py | 93 +++++++++--- Lib/test/test_argparse.py | 139 +++++++++++++++++- ...4-01-15-20-21-33.gh-issue-18208.HzD_fY.rst | 2 + 5 files changed, 263 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index fbffa71d200735..2ebe45aac11aa6 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -777,6 +777,8 @@ The add_argument() method * dest_ - The name of the attribute to be added to the object returned by :meth:`parse_args`. + * deprecated_ - Whether or not the usage of the argument is deprecated. + The following sections describe how each of these are used. @@ -1439,6 +1441,35 @@ behavior:: >>> parser.parse_args('--foo XXX'.split()) Namespace(bar='XXX') + +.. _deprecated:: + +deprecated +^^^^^^^^^^ + +During projects lifecycle some arguments could be removed from the +command line, before removing these arguments definitively you would inform +your user that arguments are deprecated and will be removed. +The ``deprecated`` keyword argument of +:meth:`~ArgumentParser.add_argument`, whose value default to ``False``, +specifies if the argument is deprecated and will be removed +from the command-line available arguments in the future. +For arguments, if ``deprecated`` is ``True`` then a warning will be +printed to the standard error if the argument is given by user in the +command line parameters:: + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='snake.py') + >>> parser.add_argument('--legs', default=0, deprecated=True) + >>> parser.parse_args([]) + Namespace(legs='0') + >>> parser.parse_args(['--legs', '4']) + snake.py: warning: usage of option '--legs' is deprecated + Namespace(legs='4') + +.. versionchanged:: 3.13 + + Action classes ^^^^^^^^^^^^^^ @@ -1842,7 +1873,8 @@ Sub-commands {foo,bar} additional help - Furthermore, ``add_parser`` supports an additional ``aliases`` argument, + Furthermore, :meth:`~_SubParsersAction.add_parser` supports an additional + *aliases* argument, which allows multiple strings to refer to the same subparser. This example, like ``svn``, aliases ``co`` as a shorthand for ``checkout``:: @@ -1853,6 +1885,20 @@ Sub-commands >>> parser.parse_args(['co', 'bar']) Namespace(foo='bar') + :meth:`~_SubParsersAction.add_parser` supports also an additional + *deprecated* argument, which allows to deprecate the subparser. + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='chicken.py') + >>> subparsers = parser.add_subparsers() + >>> run = subparsers.add_parser('run') + >>> fly = subparsers.add_parser('fly', deprecated=True) + >>> parser.parse_args(['fly']) + chicken.py: warning: usage of command 'fly' is deprecated + Namespace() + + .. versionadded:: 3.13 + One particularly effective way of handling sub-commands is to combine the use of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so that each subparser knows which Python function it should execute. For diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 05b9b87a63252f..b7fd8bffd028a4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -154,6 +154,14 @@ ast possible to obtain an optimized ``AST``. (Contributed by Irit Katriel in :gh:`108113`). +argrapse +-------- + +* Add parameter *deprecated* in methods + :meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser` + which allows to deprecate command-line options, positional arguments and + subcommands. + array ----- diff --git a/Lib/argparse.py b/Lib/argparse.py index a32884db80d1ea..f159d9c5d2523d 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -88,6 +88,7 @@ import os as _os import re as _re import sys as _sys +import warnings from gettext import gettext as _, ngettext @@ -847,7 +848,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): self.option_strings = option_strings self.dest = dest self.nargs = nargs @@ -858,6 +860,7 @@ def __init__(self, self.required = required self.help = help self.metavar = metavar + self.deprecated = deprecated def _get_kwargs(self): names = [ @@ -871,6 +874,7 @@ def _get_kwargs(self): 'required', 'help', 'metavar', + 'deprecated', ] return [(name, getattr(self, name)) for name in names] @@ -893,7 +897,8 @@ def __init__(self, choices=_deprecated_default, required=False, help=None, - metavar=_deprecated_default): + metavar=_deprecated_default, + deprecated=False): _option_strings = [] for option_string in option_strings: @@ -931,7 +936,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): @@ -954,7 +960,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for store actions must be != 0; if you ' 'have nothing to store, actions such as store ' @@ -971,7 +978,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) @@ -986,7 +994,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_StoreConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -994,7 +1003,8 @@ def __init__(self, const=const, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) @@ -1007,14 +1017,16 @@ def __init__(self, dest, default=False, required=False, - help=None): + help=None, + deprecated=False): super(_StoreTrueAction, self).__init__( option_strings=option_strings, dest=dest, const=True, - default=default, + deprecated=deprecated, required=required, - help=help) + help=help, + default=default) class _StoreFalseAction(_StoreConstAction): @@ -1024,14 +1036,16 @@ def __init__(self, dest, default=True, required=False, - help=None): + help=None, + deprecated=False): super(_StoreFalseAction, self).__init__( option_strings=option_strings, dest=dest, const=False, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) class _AppendAction(Action): @@ -1046,7 +1060,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for append actions must be != 0; if arg ' 'strings are not supplying the value to append, ' @@ -1063,7 +1078,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1081,7 +1097,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_AppendConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1090,7 +1107,8 @@ def __init__(self, default=default, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1106,14 +1124,16 @@ def __init__(self, dest, default=None, required=False, - help=None): + help=None, + deprecated=False): super(_CountAction, self).__init__( option_strings=option_strings, dest=dest, nargs=0, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): count = getattr(namespace, self.dest, None) @@ -1128,13 +1148,15 @@ def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, - help=None): + help=None, + deprecated=False): super(_HelpAction, self).__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): parser.print_help() @@ -1148,7 +1170,8 @@ def __init__(self, version=None, dest=SUPPRESS, default=SUPPRESS, - help="show program's version number and exit"): + help="show program's version number and exit", + deprecated=False): super(_VersionAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1192,6 +1215,7 @@ def __init__(self, self._parser_class = parser_class self._name_parser_map = {} self._choices_actions = [] + self._deprecated = set() super(_SubParsersAction, self).__init__( option_strings=option_strings, @@ -1202,7 +1226,7 @@ def __init__(self, help=help, metavar=metavar) - def add_parser(self, name, **kwargs): + def add_parser(self, name, *, deprecated=False, **kwargs): # set prog from the existing prefix if kwargs.get('prog') is None: kwargs['prog'] = '%s %s' % (self._prog_prefix, name) @@ -1230,6 +1254,10 @@ def add_parser(self, name, **kwargs): for alias in aliases: self._name_parser_map[alias] = parser + if deprecated: + self._deprecated.add(name) + self._deprecated.update(aliases) + return parser def _get_subactions(self): @@ -1245,13 +1273,17 @@ def __call__(self, parser, namespace, values, option_string=None): # select the parser try: - parser = self._name_parser_map[parser_name] + subparser = self._name_parser_map[parser_name] except KeyError: args = {'parser_name': parser_name, 'choices': ', '.join(self._name_parser_map)} msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args raise ArgumentError(self, msg) + if parser_name in self._deprecated: + parser._warning(_("usage of command '%(parser_name)s' is deprecated") % + {'parser_name': parser_name}) + # parse all the remaining options into the namespace # store any unrecognized options on the object, so that the top # level parser can decide what to do with them @@ -1259,7 +1291,7 @@ def __call__(self, parser, namespace, values, option_string=None): # In case this subparser defines new defaults, we parse them # in a new namespace object and then update the original # namespace for the relevant parts. - subnamespace, arg_strings = parser.parse_known_args(arg_strings, None) + subnamespace, arg_strings = subparser.parse_known_args(arg_strings, None) for key, value in vars(subnamespace).items(): setattr(namespace, key, value) @@ -1979,6 +2011,7 @@ def _parse_known_args(self, arg_strings, namespace): # converts arg strings to the appropriate and then takes the action seen_actions = set() seen_non_default_actions = set() + warned = set() def take_action(action, argument_strings, option_string=None): seen_actions.add(action) @@ -2074,6 +2107,10 @@ def consume_optional(start_index): # the Optional's string args stopped assert action_tuples for action, args, option_string in action_tuples: + if action.deprecated and option_string not in warned: + self._warning(_("usage of option '%(option)s' is deprecated") % + {'option': option_string}) + warned.add(option_string) take_action(action, args, option_string) return stop @@ -2093,6 +2130,10 @@ def consume_positionals(start_index): for action, arg_count in zip(positionals, arg_counts): args = arg_strings[start_index: start_index + arg_count] start_index += arg_count + if args and action.deprecated and action.dest not in warned: + self._warning(_("usage of argument '%(argument_name)s' is deprecated") % + {'argument_name': action.dest}) + warned.add(action.dest) take_action(action, args) # slice off the Positionals that we just parsed and return the @@ -2654,3 +2695,7 @@ def error(self, message): self.print_usage(_sys.stderr) args = {'prog': self.prog, 'message': message} self.exit(2, _('%(prog)s: error: %(message)s\n') % args) + + def _warning(self, message): + args = {'prog': self.prog, 'message': message} + self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 7c1f5d36999a3d..ccf536a95a9c59 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5099,7 +5099,8 @@ def test_optional(self): string = ( "Action(option_strings=['--foo', '-a', '-b'], dest='b', " "nargs='+', const=None, default=42, type='int', " - "choices=[1, 2, 3], required=False, help='HELP', metavar='METAVAR')") + "choices=[1, 2, 3], required=False, help='HELP', " + "metavar='METAVAR', deprecated=False)") self.assertStringEqual(option, string) def test_argument(self): @@ -5116,7 +5117,8 @@ def test_argument(self): string = ( "Action(option_strings=[], dest='x', nargs='?', " "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " - "required=True, help='H HH H', metavar='MV MV MV')" % float) + "required=True, help='H HH H', metavar='MV MV MV', " + "deprecated=False)" % float) self.assertStringEqual(argument, string) def test_namespace(self): @@ -5308,6 +5310,139 @@ def spam(string_to_convert): args = parser.parse_args('--foo spam!'.split()) self.assertEqual(NS(foo='foo_converted'), args) + +# ============================================== +# Check that deprecated arguments output warning +# ============================================== + +class TestDeprecatedArguments(TestCase): + + def test_deprecated_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '-f', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '--foo', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_boolean_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', action=argparse.BooleanOptionalAction, deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', '--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertRegex(stderr, "warning: usage of option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_arguments(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='?', deprecated=True) + parser.add_argument('bar', nargs='?', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: usage of argument 'bar' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_varargument(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='*', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_subparser(self): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + subparsers.add_parser('foo', aliases=['baz'], deprecated=True) + subparsers.add_parser('bar') + + with captured_stderr() as stderr: + parser.parse_args(['bar']) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of command 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['baz']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: usage of command 'baz' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + # ================================================================== # Check semantics regarding the default argument and type conversion # ================================================================== diff --git a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst new file mode 100644 index 00000000000000..bd3e27b4be0cf5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst @@ -0,0 +1,2 @@ +Support deprecation of options, positional arguments and subcommands in +:mod:`argparse`. From 16edd195f2875207e501fba9de841b5b856d0ab8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 15 Jan 2024 21:44:37 +0200 Subject: [PATCH 2/7] Fix docs. --- Doc/library/argparse.rst | 2 +- Doc/whatsnew/3.13.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 2ebe45aac11aa6..9518b5259a19b6 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1442,7 +1442,7 @@ behavior:: Namespace(bar='XXX') -.. _deprecated:: +.. _deprecated: deprecated ^^^^^^^^^^ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b7fd8bffd028a4..e82a77138156e2 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -161,6 +161,7 @@ argrapse :meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser` which allows to deprecate command-line options, positional arguments and subcommands. + (Contributed by Serhiy Storchaka in :gh:`18208`). array ----- From 24396b925ce2615f3aceff686c4ad8c72127ecc4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 15 Jan 2024 22:11:30 +0200 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/argparse.rst | 17 ++++++++--------- Doc/whatsnew/3.13.rst | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 9518b5259a19b6..8211dc0f20eb77 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -777,7 +777,7 @@ The add_argument() method * dest_ - The name of the attribute to be added to the object returned by :meth:`parse_args`. - * deprecated_ - Whether or not the usage of the argument is deprecated. + * deprecated_ - Whether or not use of the argument is deprecated. The following sections describe how each of these are used. @@ -1447,16 +1447,15 @@ behavior:: deprecated ^^^^^^^^^^ -During projects lifecycle some arguments could be removed from the -command line, before removing these arguments definitively you would inform -your user that arguments are deprecated and will be removed. +During a project's lifetime, some arguments may need to be removed from the +command line. Before removing them, you should inform +your users that the arguments are deprecated and will be removed. The ``deprecated`` keyword argument of -:meth:`~ArgumentParser.add_argument`, whose value default to ``False``, +:meth:`~ArgumentParser.add_argument`, which defaults to ``False``, specifies if the argument is deprecated and will be removed -from the command-line available arguments in the future. -For arguments, if ``deprecated`` is ``True`` then a warning will be -printed to the standard error if the argument is given by user in the -command line parameters:: +in the future. +For arguments, if ``deprecated`` is ``True``, then a warning will be +printed to standard error when the argument is used:: >>> import argparse >>> parser = argparse.ArgumentParser(prog='snake.py') diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e82a77138156e2..6b5649b0c19190 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -161,7 +161,7 @@ argrapse :meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser` which allows to deprecate command-line options, positional arguments and subcommands. - (Contributed by Serhiy Storchaka in :gh:`18208`). + (Contributed by Serhiy Storchaka in :gh:`83648`). array ----- From 54d7bba9d625676c73e1e2ab303d900fbb06b320 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 15 Jan 2024 22:38:14 +0200 Subject: [PATCH 4/7] Rename a NEWS file name. --- ...8.HzD_fY.rst => 2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst => 2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-18208.HzD_fY.rst rename to Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst From 7aa0d882ec043d7b813fad3934e11c1dc3022899 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 15 Jan 2024 23:45:09 +0200 Subject: [PATCH 5/7] Skip doctests that print to stderr. --- Doc/library/argparse.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 8211dc0f20eb77..4df28af5496a5e 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1459,12 +1459,12 @@ printed to standard error when the argument is used:: >>> import argparse >>> parser = argparse.ArgumentParser(prog='snake.py') - >>> parser.add_argument('--legs', default=0, deprecated=True) + >>> parser.add_argument('--legs', default=0, type=int, deprecated=True) >>> parser.parse_args([]) - Namespace(legs='0') - >>> parser.parse_args(['--legs', '4']) + Namespace(legs=0) + >>> parser.parse_args(['--legs', '4']) # doctest: +SKIP snake.py: warning: usage of option '--legs' is deprecated - Namespace(legs='4') + Namespace(legs=4) .. versionchanged:: 3.13 @@ -1892,7 +1892,7 @@ Sub-commands >>> subparsers = parser.add_subparsers() >>> run = subparsers.add_parser('run') >>> fly = subparsers.add_parser('fly', deprecated=True) - >>> parser.parse_args(['fly']) + >>> parser.parse_args(['fly']) # doctest: +SKIP chicken.py: warning: usage of command 'fly' is deprecated Namespace() From 12d3bc96bece0bc2d4575bf6831b41a98624a95f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 16 Jan 2024 11:30:42 +0200 Subject: [PATCH 6/7] Remove unused import. --- Lib/argparse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index f159d9c5d2523d..fea0f46869b928 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -88,7 +88,6 @@ import os as _os import re as _re import sys as _sys -import warnings from gettext import gettext as _, ngettext From 5f72bc6098d06cfdee425fb1ef12ec8544f5a884 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 16 Jan 2024 18:17:43 +0200 Subject: [PATCH 7/7] Apply suggestions. --- Doc/library/argparse.rst | 4 ++-- Doc/whatsnew/3.13.rst | 2 +- Lib/argparse.py | 6 +++--- Lib/test/test_argparse.py | 34 +++++++++++++++++----------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 4df28af5496a5e..a3d5fbeeed25af 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1463,7 +1463,7 @@ printed to standard error when the argument is used:: >>> parser.parse_args([]) Namespace(legs=0) >>> parser.parse_args(['--legs', '4']) # doctest: +SKIP - snake.py: warning: usage of option '--legs' is deprecated + snake.py: warning: option '--legs' is deprecated Namespace(legs=4) .. versionchanged:: 3.13 @@ -1893,7 +1893,7 @@ Sub-commands >>> run = subparsers.add_parser('run') >>> fly = subparsers.add_parser('fly', deprecated=True) >>> parser.parse_args(['fly']) # doctest: +SKIP - chicken.py: warning: usage of command 'fly' is deprecated + chicken.py: warning: command 'fly' is deprecated Namespace() .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6b5649b0c19190..d227ee7361fa0c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -154,7 +154,7 @@ ast possible to obtain an optimized ``AST``. (Contributed by Irit Katriel in :gh:`108113`). -argrapse +argparse -------- * Add parameter *deprecated* in methods diff --git a/Lib/argparse.py b/Lib/argparse.py index fea0f46869b928..eaf6ca374eab17 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1280,7 +1280,7 @@ def __call__(self, parser, namespace, values, option_string=None): raise ArgumentError(self, msg) if parser_name in self._deprecated: - parser._warning(_("usage of command '%(parser_name)s' is deprecated") % + parser._warning(_("command '%(parser_name)s' is deprecated") % {'parser_name': parser_name}) # parse all the remaining options into the namespace @@ -2107,7 +2107,7 @@ def consume_optional(start_index): assert action_tuples for action, args, option_string in action_tuples: if action.deprecated and option_string not in warned: - self._warning(_("usage of option '%(option)s' is deprecated") % + self._warning(_("option '%(option)s' is deprecated") % {'option': option_string}) warned.add(option_string) take_action(action, args, option_string) @@ -2130,7 +2130,7 @@ def consume_positionals(start_index): args = arg_strings[start_index: start_index + arg_count] start_index += arg_count if args and action.deprecated and action.dest not in warned: - self._warning(_("usage of argument '%(argument_name)s' is deprecated") % + self._warning(_("argument '%(argument_name)s' is deprecated") % {'argument_name': action.dest}) warned.add(action.dest) take_action(action, args) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index ccf536a95a9c59..1afa7076c41d02 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5324,26 +5324,26 @@ def test_deprecated_option(self): with captured_stderr() as stderr: parser.parse_args(['--foo', 'spam']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['-f', 'spam']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertRegex(stderr, "warning: option '-f' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['--foo', 'spam', '-f', 'ham']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") - self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '-f' is deprecated") self.assertEqual(stderr.count('is deprecated'), 2) with captured_stderr() as stderr: parser.parse_args(['--foo', 'spam', '--foo', 'ham']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) def test_deprecated_boolean_option(self): @@ -5353,26 +5353,26 @@ def test_deprecated_boolean_option(self): with captured_stderr() as stderr: parser.parse_args(['--foo']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['-f']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '-f' is deprecated") + self.assertRegex(stderr, "warning: option '-f' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['--no-foo']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--no-foo' is deprecated") + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['--foo', '--no-foo']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of option '--foo' is deprecated") - self.assertRegex(stderr, "warning: usage of option '--no-foo' is deprecated") + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 2) def test_deprecated_arguments(self): @@ -5388,14 +5388,14 @@ def test_deprecated_arguments(self): with captured_stderr() as stderr: parser.parse_args(['spam']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['spam', 'ham']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") - self.assertRegex(stderr, "warning: usage of argument 'bar' is deprecated") + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'bar' is deprecated") self.assertEqual(stderr.count('is deprecated'), 2) def test_deprecated_varargument(self): @@ -5410,13 +5410,13 @@ def test_deprecated_varargument(self): with captured_stderr() as stderr: parser.parse_args(['spam']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['spam', 'ham']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) def test_deprecated_subparser(self): @@ -5433,13 +5433,13 @@ def test_deprecated_subparser(self): with captured_stderr() as stderr: parser.parse_args(['foo']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of command 'foo' is deprecated") + self.assertRegex(stderr, "warning: command 'foo' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1) with captured_stderr() as stderr: parser.parse_args(['baz']) stderr = stderr.getvalue() - self.assertRegex(stderr, "warning: usage of command 'baz' is deprecated") + self.assertRegex(stderr, "warning: command 'baz' is deprecated") self.assertEqual(stderr.count('is deprecated'), 1)