diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index f8e39189686..76be9f620bd 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -698,6 +698,15 @@ The add_argument() method * default_ - The value produced if the argument is absent from the command line. + * deprecated_ - Define if the argument is deprecated. + + * deprecated_reason_ - Custome deprecation warning message to display if + the argument is deprecated. + + * deprecated_pending_ - Define if the deprecation is pending. The argument + is obsolete and expected to be deprecated in the future, but is not + deprecated at the moment. + * type_ - The type to which the command-line argument should be converted. * choices_ - A container of the allowable values for the argument. @@ -1052,6 +1061,72 @@ command-line argument was not present:: Namespace(foo='1') +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 (``DeprecationWarning``) will be +emitted if the argument is given by user in the command line parameters:: + + >>> import argparse + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('bar', default=1) + >>> parser.add_argument('--foo', default=2, deprecated=True) + >>> parser.parse_args(['test']) + Namespace(bar='test', foo='2') + >>> parser.parse_args(['test', '--foo', '4']) + /home/cpython/Lib/argparse.py:1979: DeprecationWarning: Usage of parameter foo are deprecated + Namespace(bar='test', foo='4') + + +deprecated_reason +^^^^^^^^^^^^^^^^^ + +Custome deprecation warning message to display if the argument is deprecated. +If not given then a standard message will be displayed. +The ``deprecated_reason`` keyword argument of +:meth:`~ArgumentParser.add_argument`, allow to define a custome message to +display if the argument is deprecated and given by user in the command line parameters:: + + >>> import argparse + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('bar', default=1) + >>> parser.add_argument('--foo', default=2, deprecated=True, deprecated_reason='my custom message') + >>> parser.parse_args(['test']) + Namespace(bar='test', foo='2') + >>> parser.parse_args(['test', '--foo', '4']) + /home/cpython/Lib/argparse.py:1979: DeprecationWarning: my custome message + Namespace(bar='test', foo='4') + +deprecated_pending +^^^^^^^^^^^^^^^^^^ + +Define if the deprecation is pending. Could be used to define that an argument +is obsolete and expected to be deprecated in the future, but is not +deprecated at the moment. +The ``deprecated_pending`` keyword argument of +:meth:`~ArgumentParser.add_argument`, whose value default to ``False``, +specifies if the argument is obsolete and expected to be deprecated in the future. +For arguments, if ``deprecated_pending`` is ``True`` then a warning +(``PendingDeprecationWarning``) will be emitted if the argument is given by +user in the command line parameters:: + + >>> import argparse + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('bar', default=1) + >>> parser.add_argument('--foo', default=2, deprecated=True, deprecated_pending=True) + >>> parser.parse_args(['test']) + Namespace(bar='test', foo='2') + >>> parser.parse_args(['test', '--foo', '4']) + /home/cpython/Lib/argparse.py:1979: PendingDeprecationWarning: The argument foo is obsolete and expected to be deprecated in the future + Namespace(bar='test', foo='4') + type ^^^^ diff --git a/Lib/argparse.py b/Lib/argparse.py index 5d3ce2ad709..31031cf7df2 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -88,6 +88,7 @@ __all__ = [ import os as _os import re as _re import sys as _sys +import warnings from gettext import gettext as _, ngettext @@ -817,6 +818,9 @@ class Action(_AttributeHolder): nargs=None, const=None, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, type=None, choices=None, required=False, @@ -827,6 +831,14 @@ class Action(_AttributeHolder): self.nargs = nargs self.const = const self.default = default + self.deprecated = deprecated + if not deprecated_reason: + deprecated_reason = f"Usage of parameter {dest} are deprecated" + if deprecated_pending: + deprecated_reason = f"The argument {dest} is obsolete and " \ + "expected to be deprecated in the future" + self.deprecated_reason = deprecated_reason + self.deprecated_pending=deprecated_pending self.type = type self.choices = choices self.required = required @@ -840,6 +852,9 @@ class Action(_AttributeHolder): 'nargs', 'const', 'default', + 'deprecated', + 'deprecated_reason', + 'deprecated_pending', 'type', 'choices', 'help', @@ -859,6 +874,9 @@ class BooleanOptionalAction(Action): dest, const=None, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, type=None, choices=None, required=False, @@ -881,6 +899,9 @@ class BooleanOptionalAction(Action): dest=dest, nargs=0, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, type=type, choices=choices, required=required, @@ -903,6 +924,9 @@ class _StoreAction(Action): nargs=None, const=None, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, type=None, choices=None, required=False, @@ -920,6 +944,9 @@ class _StoreAction(Action): nargs=nargs, const=const, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, type=type, choices=choices, required=required, @@ -937,6 +964,9 @@ class _StoreConstAction(Action): dest, const, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, required=False, help=None, metavar=None): @@ -946,6 +976,9 @@ class _StoreConstAction(Action): nargs=0, const=const, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, required=required, help=help) @@ -959,6 +992,9 @@ class _StoreTrueAction(_StoreConstAction): option_strings, dest, default=False, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, required=False, help=None): super(_StoreTrueAction, self).__init__( @@ -966,6 +1002,9 @@ class _StoreTrueAction(_StoreConstAction): dest=dest, const=True, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, required=required, help=help) @@ -976,6 +1015,9 @@ class _StoreFalseAction(_StoreConstAction): option_strings, dest, default=True, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, required=False, help=None): super(_StoreFalseAction, self).__init__( @@ -983,6 +1025,9 @@ class _StoreFalseAction(_StoreConstAction): dest=dest, const=False, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, required=required, help=help) @@ -995,6 +1040,9 @@ class _AppendAction(Action): nargs=None, const=None, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, type=None, choices=None, required=False, @@ -1012,6 +1060,9 @@ class _AppendAction(Action): nargs=nargs, const=const, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, type=type, choices=choices, required=required, @@ -1032,6 +1083,9 @@ class _AppendConstAction(Action): dest, const, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, required=False, help=None, metavar=None): @@ -1041,6 +1095,9 @@ class _AppendConstAction(Action): nargs=0, const=const, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, required=required, help=help, metavar=metavar) @@ -1058,6 +1115,9 @@ class _CountAction(Action): option_strings, dest, default=None, + deprecated=False, + deprecated_reason=None, + deprecated_pending=False, required=False, help=None): super(_CountAction, self).__init__( @@ -1065,6 +1125,9 @@ class _CountAction(Action): dest=dest, nargs=0, default=default, + deprecated=deprecated, + deprecated_reason=deprecated_reason, + deprecated_pending=deprecated_pending, required=required, help=help) @@ -1901,6 +1964,18 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): pattern = 'O' arg_string_pattern_parts.append(pattern) + # check if the arg is deprecated + # and warn only if it's given in the CLI paramters + for action in self._actions: + if arg_string.replace("-", "") == action.dest: + if not action.deprecated: + continue + warnings.warn( + action.deprecated_reason, + PendingDeprecationWarning \ + if action.deprecated_pending \ + else DeprecationWarning) + # join the pieces together to form the pattern arg_strings_pattern = ''.join(arg_string_pattern_parts) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 60bf19918b7..e1efa5110ee 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -4660,12 +4660,17 @@ class TestStrings(TestCase): type='int', nargs='+', default=42, + deprecated=False, + deprecated_reason='foo bar', + deprecated_pending=False, choices=[1, 2, 3], help='HELP', metavar='METAVAR') string = ( "Action(option_strings=['--foo', '-a', '-b'], dest='b', " - "nargs='+', const=None, default=42, type='int', " + "nargs='+', const=None, default=42, deprecated=False, " + "deprecated_reason='foo bar', " + "deprecated_pending=False, type='int', " "choices=[1, 2, 3], help='HELP', metavar='METAVAR')") self.assertStringEqual(option, string) @@ -4676,12 +4681,18 @@ class TestStrings(TestCase): type=float, nargs='?', default=2.5, + deprecated=False, + deprecated_reason='foo bar', + deprecated_pending=False, choices=[0.5, 1.5, 2.5], help='H HH H', metavar='MV MV MV') string = ( "Action(option_strings=[], dest='x', nargs='?', " - "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " + "const=None, default=2.5, deprecated=False, " + "deprecated_reason='foo bar', " + "deprecated_pending=False, " + "type=%r, choices=[0.5, 1.5, 2.5], " "help='H HH H', metavar='MV MV MV')" % float) self.assertStringEqual(argument, string) @@ -4874,6 +4885,39 @@ class TestTypeFunctionCallOnlyOnce(TestCase): args = parser.parse_args('--foo spam!'.split()) self.assertEqual(NS(foo='foo_converted'), args) + +# ============================================= +# Check that deprecated arguments raise warning +# ============================================= + +class TestTypeFunctionCallWithDeprecated(TestCase): + + def test_type_function_call_with_deprecated(self): + parser = argparse.ArgumentParser() + parser.add_argument('--foo', deprecated=True, default='bar') + with support.captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertIn("DeprecationWarning", stderr) + + def test_type_function_call_with_pending_deprecated(self): + parser = argparse.ArgumentParser() + parser.add_argument('--foo', deprecated=True, + deprecated_pending=True, default='bar') + with support.captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertIn("PendingDeprecationWarning", stderr) + + def test_type_function_call_with_deprecated_custome_msg(self): + parser = argparse.ArgumentParser() + parser.add_argument('--foo', deprecated=True, + deprecated_reason="foo bar", default='bar') + with support.captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertIn("DeprecationWarning: foo bar", stderr) + # ================================================================== # Check semantics regarding the default argument and type conversion # ================================================================== diff --git a/Misc/NEWS.d/next/Library/2020-01-28-08-58-50.bpo-39467.yWrrL_.rst b/Misc/NEWS.d/next/Library/2020-01-28-08-58-50.bpo-39467.yWrrL_.rst new file mode 100644 index 00000000000..d664b2ff7d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-28-08-58-50.bpo-39467.yWrrL_.rst @@ -0,0 +1 @@ +Allow to deprecate CLI arguments with argparse \ No newline at end of file