8000 gh-83648: Support deprecation of options, arguments and subcommands i… · python/cpython@bb57ffd · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bb57ffd

Browse files
gh-83648: Support deprecation of options, arguments and subcommands in argparse (GH-114086)
1 parent c32bae5 commit bb57ffd

File tree

5 files changed

+262
-27
lines changed

5 files changed

+262
-27
lines changed

Doc/library/argparse.rst

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,8 @@ The add_argument() method
777777
* dest_ - The name of the attribute to be added to the object returned by
778778
:meth:`parse_args`.
779779

780+
* deprecated_ - Whether or not use of the argument is deprecated.
781+
780782
The following sections describe how each of these are used.
781783

782784

@@ -1439,6 +1441,34 @@ behavior::
14391441
>>> parser.parse_args('--foo XXX'.split())
14401442
Namespace(bar='XXX')
14411443

1444+
1445+
.. _deprecated:
1446+
1447+
deprecated
1448+
^^^^^^^^^^
1449+
1450+
During a project's lifetime, some arguments may need to be removed from the
1451+
command line. Before removing them, you should inform
1452+
your users that the arguments are deprecated and will be removed.
1453+
The ``deprecated`` keyword argument of
1454+
:meth:`~ArgumentParser.add_argument`, which defaults to ``False``,
1455+
specifies if the argument is deprecated and will be removed
1456+
in the future.
1457+
For arguments, if ``deprecated`` is ``True``, then a warning will be
1458+
printed to standard error when the argument is used::
1459+
1460+
>>> import argparse
1461+
>>> parser = argparse.ArgumentParser(prog='snake.py')
1462+
>>> parser.add_argument('--legs', default=0, type=int, deprecated=True)
1463+
>>> parser.parse_args([])
1464+
Namespace(legs=0)
1465+
>>> parser.parse_args(['--legs', '4']) # doctest: +SKIP
1466+
snake.py: warning: option '--legs' is deprecated
1467+
Namespace(legs=4)
1468+
1469+
.. versionchanged:: 3.13
1470+
1471+
14421472
Action classes
14431473
^^^^^^^^^^^^^^
14441474

@@ -1842,7 +1872,8 @@ Sub-commands
18421872

18431873
{foo,bar} additional help
18441874

1845-
Furthermore, ``add_parser`` supports an additional ``aliases`` argument,
1875+
Furthermore, :meth:`~_SubParsersAction.add_parser` supports an additional
1876+
*aliases* argument,
18461877
which allows multiple strings to refer to the same subparser. This example,
18471878
like ``svn``, aliases ``co`` as a shorthand for ``checkout``::
18481879

9E12
@@ -1853,6 +1884,20 @@ Sub-commands
18531884
>>> parser.parse_args(['co', 'bar'])
18541885
Namespace(foo='bar')
18551886

1887+
:meth:`~_SubParsersAction.add_parser` supports also an additional
1888+
*deprecated* argument, which allows to deprecate the subparser.
1889+
1890+
>>> import argparse
1891+
>>> parser = argparse.ArgumentParser(prog='chicken.py')
1892+
>>> subparsers = parser.add_subparsers()
1893+
>>> run = subparsers.add_parser('run')
1894+
>>> fly = subparsers.add_parser('fly', deprecated=True)
1895+
>>> parser.parse_args(['fly']) # doctest: +SKIP
1896+
chicken.py: warning: command 'fly' is deprecated
1897+
Namespace()
1898+
1899+
.. versionadded:: 3.13
1900+
18561901
One particularly effective way of handling sub-commands is to combine the use
18571902
of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so
18581903
that each subparser knows which Python function it should execute. For

Doc/whatsnew/3.13.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ New Modules
169169
Improved Modules
170170
================
171171

172+
argparse
173+
--------
174+
175+
* Add parameter *deprecated* in methods
176+
:meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser`
177+
which allows to deprecate command-line options, positional arguments and
178+
subcommands.
179+
(Contributed by Serhiy Storchaka in :gh:`83648`).
180+
172181
array
173182
-----
174183

Lib/argparse.py

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,8 @@ def __init__(self,
843843
choices=None,
844844
required=False,
845845
help=None,
846-
metavar=None):
846+
metavar=None,
847+
deprecated=False):
847848
self.option_strings = option_strings
848849
self.dest = dest
849850
self.nargs = nargs
@@ -854,6 +855,7 @@ def __init__(self,
854855
self.required = required
855856
self.help = help
856857
self.metavar = metavar
858+
self.deprecated = deprecated
857859

858860
def _get_kwargs(self):
859861
names = [
@@ -867,6 +869,7 @@ def _get_kwargs(self):
867869
'required',
868870
'help',
869871
'metavar',
872+
'deprecated',
870873
]
871874
return [(name, getattr(self, name)) for name in names]
872875

@@ -889,7 +892,8 @@ def __init__(self,
889892
choices=_deprecated_default,
890893
required=False,
891894
help=None,
892-
metavar=_deprecated_default):
895+
metavar=_deprecated_default,
896+
deprecated=False):
893897

894898
_option_strings = []
895899
for option_string in option_strings:
@@ -927,7 +931,8 @@ def __init__(self,
927931
choices=choices,
928932
required=required,
929933
help=help,
930-
metavar=metavar)
934+
metavar=metavar,
935+
deprecated=deprecated)
931936

932937

933938
def __call__(self, parser, namespace, values, option_string=None):
@@ -950,7 +955,8 @@ def __init__(self,
950955
choices=None,
951956
required=False,
952957
help=None,
953-
metavar=None):
958+
metavar=None,
959+
deprecated=False):
954960
if nargs == 0:
955961
raise ValueError('nargs for store actions must be != 0; if you '
956962
'have nothing to store, actions such as store '
@@ -967,7 +973,8 @@ def __init__(self,
967973
choices=choices,
968974
required=required,
969975
help=help,
970-
metavar=metavar)
976+
metavar=metavar,
977+
deprecated=deprecated)
971978

972979
def __call__(self, parser, namespace, values, option_string=None):
973980
setattr(namespace, self.dest, values)
@@ -982,15 +989,17 @@ def __init__(self,
982989
default=None,
983990
required=False,
984991
help=None,
985-
metavar=None):
992+
metavar=None,
993+
deprecated=False):
986994
super(_StoreConstAction, self).__init__(
987995
option_strings=option_strings,
988996
dest=dest,
989997
nargs=0,
990998
const=const,
991999
default=default,
9921000
required=required,
993-
help=help)
1001+
help=help,
1002+
deprecated=deprecated)
9941003

9951004
def __call__(self, parser, namespace, values, option_string=None):
9961005
setattr(namespace, self.dest, self.const)
@@ -1003,14 +1012,16 @@ def __init__(self,
10031012
dest,
10041013
default=False,
10051014
required=False,
1006-
help=None):
1015+
help=None,
1016+
deprecated=False):
10071017
super(_StoreTrueAction, self).__init__(
10081018
option_strings=option_strings,
10091019
dest=dest,
10101020
const=True,
1011-
default=default,
1021+
deprecated=deprecated,
10121022
required=required,
1013-
help=help)
1023+
help=help,
1024+
default=default)
10141025

10151026

10161027
class _StoreFalseAction(_StoreConstAction):
@@ -1020,14 +1031,16 @@ def __init__(self,
10201031
dest,
10211032
default=True,
10221033
required=False,
1023-
help=None):
1034+
help=None,
1035+
deprecated=False):
10241036
super(_StoreFalseAction, self).__init__(
10251037
option_strings=option_strings,
10261038
dest=dest,
10271039
const=False,
10281040
default=default,
10291041
required=required,
1030-
help=help)
1042+
help=help,
1043+
deprecated=deprecated)
10311044

10321045

10331046
class _AppendAction(Action):
@@ -1042,7 +1055,8 @@ def __init__(self,
10421055
choices=None,
10431056
required=False,
10441057
help=None,
1045-
metavar=None):
1058+
metavar=None,
1059+
deprecated=False):
10461060
if nargs == 0:
10471061
raise ValueError('nargs for append actions must be != 0; if arg '
10481062
'strings are not supplying the value to append, '
@@ -1059,7 +1073,8 @@ def __init__(self,
10591073
choices=choices,
10601074
required=required,
10611075
help=help,
1062-
metavar=metavar)
1076+
metavar=metavar,
1077+
deprecated=deprecated)
10631078

10641079
def __call__(self, parser, namespace, values, option_string=None):
10651080
items = getattr(namespace, self.dest, None)
@@ -1077,7 +1092,8 @@ def __init__(self,
10771092
default=None,
10781093
required=False,
10791094
help=None,
1080-
metavar=None):
1095+
metavar=None,
1096+
deprecated=False):
10811097
super(_AppendConstAction, self).__init__(
10821098
option_strings=option_strings,
10831099
dest=dest,
@@ -1086,7 +1102,8 @@ def __init__(self,
10861102
default=default,
10871103
required=required,
10881104
help=help,
1089-
metavar=metavar)
1105+
metavar=metavar,
1106+
deprecated=deprecated)
10901107

10911108
def __call__(self, parser, namespace, values, option_string=None):
10921109
items = getattr(namespace, self.dest, None)
@@ -1102,14 +1119,16 @@ def __init__(self,
11021119
dest,
11031120
default=None,
11041121
required=False,
1105-
help=None):
1122+
help=None,
1123+
deprecated=False):
11061124
super(_CountAction, self).__init__(
11071125
option_strings=option_strings,
11081126
dest=dest,
11091127
nargs=0,
11101128
default=default,
11111129
required=required,
1112-
help=help)
1130+
help=help,
1131+
deprecated=deprecated)
11131132

11141133
def __call__(self, parser, namespace, values, option_string=None):
11151134
count = getattr(namespace, self.dest, None)
@@ -1124,13 +1143,15 @@ def __init__(self,
11241143
option_strings,
11251144
dest=SUPPRESS,
11261145
default=SUPPRESS,
1127-
help=None):
1146+
help=None,
1147+
deprecated=False):
11281148
super(_HelpAction, self).__init__(
11291149
option_strings=option_strings,
11301150
dest=dest,
11311151
default=default,
11321152
nargs=0,
1133-
help=help)
1153+
help=help,
1154+
deprecated=deprecated)
11341155

11351156
def __call__(self, parser, namespace, values, option_string=None):
11361157
parser.print_help()
@@ -1144,7 +1165,8 @@ def __init__(self,
11441165
version=None,
11451166
dest=SUPPRESS,
11461167
default=SUPPRESS,
1147-
help="show program's version number and exit"):
1168+
help="show program's version number and exit",
1169+
deprecated=False):
11481170
super(_VersionAction, self).__init__(
11491171
option_strings=option_strings,
11501172
dest=dest,
@@ -1188,6 +1210,7 @@ def __init__(self,
11881210
self._parser_class = parser_class
11891211
self._name_parser_map = {}
11901212
self._choices_actions = []
1213+
self._deprecated = set()
11911214

11921215
super(_SubParsersAction, self).__init__(
11931216
option_strings=option_strings,
@@ -1198,7 +1221,7 @@ def __init__(self,
11981221
help=help,
11991222
metavar=metavar)
12001223

1201-
def add_parser(self, name, **kwargs):
1224+
def add_parser(self, name, *, deprecated=False, **kwargs):
12021225
# set prog from the existing prefix
12031226
if kwargs.get('prog') is None:
12041227
kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
@@ -1226,6 +1249,10 @@ def add_parser(self, name, **kwargs):
12261249
for alias in aliases:
12271250
self._name_parser_map[alias] = parser
12281251

1252+
if deprecated:
1253+
self._deprecated.add(name)
1254+
self._deprecated.update(aliases)
1255+
12291256
return parser
12301257

12311258
def _get_subactions(self):
@@ -1241,21 +1268,25 @@ def __call__(self, parser, namespace, values, option_string=None):
12411268

12421269
# select the parser
12431270
try:
1244-
parser = self._name_parser_map[parser_name]
1271+
subparser = self._name_parser_map[parser_name]
12451272
except KeyError:
12461273
args = {'parser_name': parser_name,
12471274
'choices': ', '.join(self._name_parser_map)}
12481275
msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args
12491276
raise ArgumentError(self, msg)
12501277

1278+
if parser_name in self._deprecated:
1279+
parser._warning(_("command '%(parser_name)s' is deprecated") %
1280+
{'parser_name': parser_name})
1281+
12511282
# parse all the remaining options into the namespace
12521283
# store any unrecognized options on the object, so that the top
12531284
# level parser can decide what to do with them
12541285

12551286
# In case this subparser defines new defaults, we parse them
12561287
# in a new namespace object and then update the original
12571288
# namespace for the relevant parts.
1258-
subnamespace, arg_strings = parser.parse_known_args(arg_strings, None)
1289+
subnamespace, arg_strings = subparser.parse_known_args(arg_strings, None)
12591290
for key, value in vars(subnamespace).items():
12601291
setattr(namespace, key, value)
12611292

@@ -1975,6 +2006,7 @@ def _parse_known_args(self, arg_strings, namespace):
19752006
# converts arg strings to the appropriate and then takes the action
19762007
seen_actions = set()
19772008
seen_non_default_actions = set()
2009+
warned = set()
19782010

19792011
def take_action(action, argument_strings, option_string=None):
19802012
seen_actions.add(action)
@@ -2070,6 +2102,10 @@ def consume_optional(start_index):
20702102
# the Optional's string args stopped
20712103
assert action_tuples
20722104
for action, args, option_string in action_tuples:
2105+
if action.deprecated and option_string not in warned:
2106+
self._warning(_("option '%(option)s' is deprecated") %
2107+
{'option': option_string})
2108+
warned.add(option_string)
20732109
take_action(action, args, option_string)
20742110
return stop
20752111

@@ -2089,6 +2125,10 @@ def consume_positionals(start_index):
20892125
for action, arg_count in zip(positionals, arg_counts):
20902126
args = arg_strings[start_index: start_index + arg_count]
20912127
start_index += arg_count
2128+
if args and action.deprecated and action.dest not in warned:
2129+
self._warning(_("argument '%(argument_name)s' is deprecated") %
2130+
{'argument_name': action.dest})
2131+
warned.add(action.dest)
20922132
take_action(action, args)
20932133

20942134
# slice off the Positionals that we just parsed and return the
@@ -2650,3 +2690,7 @@ def error(self, message):
26502690
self.print_usage(_sys.stderr)
26512691
args = {'prog': self.prog, 'message': message}
26522692
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
2693+
2694+
def _warning(self, message):
2695+
args = {'prog': self.prog, 'message': message}
2696+
self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr)

0 commit comments

Comments
 (0)
0