8000 bpo-39467: allow user to deprecate CLI arguments by 4383 · Pull Request #18208 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-39467: allow user to deprecate CLI arguments #18208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
^^^^

Expand Down
75 changes: 75 additions & 0 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
import os as _os
import re as _re
import sys as _sys
import warnings

from gettext import gettext as _, ngettext

Expand Down Expand Up @@ -817,6 +818,9 @@ def __init__(self,
nargs=None,
const=None,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
type=None,
choices=None,
required=False,
Expand All @@ -827,6 +831,14 @@ def __init__(self,
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
Expand All @@ -840,6 +852,9 @@ def _get_kwargs(self):
'nargs',
'const',
'default',
'deprecated',
'deprecated_reason',
'deprecated_pending',
'type',
'choices',
'help',
Expand All @@ -859,6 +874,9 @@ def __init__(self,
dest,
const=None,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
type=None,
choices=None,
required=False,
Expand All @@ -881,6 +899,9 @@ def __init__(self,
dest=dest,
nargs=0,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
type=type,
choices=choices,
required=required,
Expand All @@ -903,6 +924,9 @@ def __init__(self,
nargs=None,
const=None,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
type=None,
choices=None,
required=False,
Expand All @@ -920,6 +944,9 @@ def __init__(self,
nargs=nargs,
const=const,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
type=type,
choices=choices,
required=required,
Expand All @@ -937,6 +964,9 @@ def __init__(self,
dest,
const,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
required=False,
help=None,
metavar=None):
Expand All @@ -946,6 +976,9 @@ def __init__(self,
nargs=0,
const=const,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
required=required,
help=help)

Expand All @@ -959,13 +992,19 @@ def __init__(self,
option_strings,
dest,
default=False,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
required=False,
help=None):
super(_StoreTrueAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=True,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
required=required,
help=help)

Expand All @@ -976,13 +1015,19 @@ def __init__(self,
option_strings,
dest,
default=True,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
required=False,
help=None):
super(_StoreFalseAction, self).__init__(
67E6 option_strings=option_strings,
dest=dest,
const=False,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
required=required,
help=help)

Expand All @@ -995,6 +1040,9 @@ def __init__(self,
nargs=None,
const=None,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
type=None,
choices=None,
required=False,
Expand All @@ -1012,6 +1060,9 @@ def __init__(self,
nargs=nargs,
const=const,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
type=type,
choices=choices,
required=required,
Expand All @@ -1032,6 +1083,9 @@ def __init__(self,
dest,
const,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
required=False,
help=None,
metavar=None):
Expand All @@ -1041,6 +1095,9 @@ def __init__(self,
nargs=0,
const=const,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
required=required,
help=help,
metavar=metavar)
Expand All @@ -1058,13 +1115,19 @@ def __init__(self,
option_strings,
dest,
default=None,
deprecated=False,
deprecated_reason=None,
deprecated_pending=False,
required=False,
help=None):
super(_CountAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
default=default,
deprecated=deprecated,
deprecated_reason=deprecated_reason,
deprecated_pending=deprecated_pending,
required=required,
help=help)

Expand Down Expand Up @@ -1901,6 +1964,18 @@ def _parse_known_args(self, arg_strings, namespace):
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)

Expand Down
48 changes: 46 additions & 2 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4660,12 +4660,17 @@ def test_optional(self):
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)

Expand All @@ -4676,12 +4681,18 @@ def test_argument(self):
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)

Expand Down Expand Up @@ -4874,6 +4885,39 @@ def spam(string_to_convert):
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
# ==================================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow to deprecate CLI arguments with argparse
0