bpo-39467: allow user to deprecate CLI arguments

This commit is contained in:
Hervé Beraud 2020-01-27 19:12:47 +01:00
parent b9783d2e03
commit 7c1d2bedda
4 changed files with 197 additions and 2 deletions

View File

@ -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
^^^^

View File

@ -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)

View File

@ -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
# ==================================================================

View File

@ -0,0 +1 @@
Allow to deprecate CLI arguments with argparse