https://github.com/python/cpython/commit/17c5959aa3daee4b63d78e944a1d355daa651e52 commit: 17c5959aa3daee4b63d78e944a1d355daa651e52 branch: 3.14 author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com> committer: savannahostrowski <savannahostrow...@gmail.com> date: 2025-07-20T15:22:53-07:00 summary:
[3.14] GH-130645: Default to color help in argparse (GH-136809) (#136886) GH-130645: Default to color help in argparse (GH-136809) (cherry picked from commit acbe896cb12d6a92e6150fff22921756f6928035) Co-authored-by: Pablo Galindo Salgado <pablog...@gmail.com> Co-authored-by: Hugo van Kemenade <1324225+hug...@users.noreply.github.com> Co-authored-by: Ćukasz Langa <luk...@langa.pl> Co-authored-by: Adam Turner <9087854+aa-tur...@users.noreply.github.com> files: A Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst M Doc/library/argparse.rst M Doc/whatsnew/3.14.rst M Lib/argparse.py M Lib/test/test_argparse.py M Lib/test/test_clinic.py diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index a08f713ab56ba3..79e15994491eff 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -74,7 +74,7 @@ ArgumentParser objects prefix_chars='-', fromfile_prefix_chars=None, \ argument_default=None, conflict_handler='error', \ add_help=True, allow_abbrev=True, exit_on_error=True, \ - *, suggest_on_error=False, color=False) + *, suggest_on_error=False, color=True) Create a new :class:`ArgumentParser` object. All parameters should be passed as keyword arguments. Each parameter has its own more detailed description @@ -119,7 +119,7 @@ ArgumentParser objects * suggest_on_error_ - Enables suggestions for mistyped argument choices and subparser names (default: ``False``) - * color_ - Allow color output (default: ``False``) + * color_ - Allow color output (default: ``True``) .. versionchanged:: 3.5 *allow_abbrev* parameter was added. @@ -626,27 +626,19 @@ keyword argument:: color ^^^^^ -By default, the help message is printed in plain text. If you want to allow -color in help messages, you can enable it by setting ``color`` to ``True``:: +By default, the help message is printed in color using `ANSI escape sequences +<https://en.wikipedia.org/wiki/ANSI_escape_code>`__. +If you want plain text help messages, you can disable this :ref:`in your local +environment <using-on-controlling-color>`, or in the argument parser itself +by setting ``color`` to ``False``:: >>> parser = argparse.ArgumentParser(description='Process some integers.', - ... color=True) + ... color=False) >>> parser.add_argument('--action', choices=['sum', 'max']) >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', ... help='an integer for the accumulator') >>> parser.parse_args(['--help']) -Even if a CLI author has enabled color, it can be -:ref:`controlled using environment variables <using-on-controlling-color>`. - -If you're writing code that needs to be compatible with older Python versions -and want to opportunistically use ``color`` when it's available, you -can set it as an attribute after initializing the parser instead of using the -keyword argument:: - - >>> parser = argparse.ArgumentParser(description='Process some integers.') - >>> parser.color = True - .. versionadded:: 3.14 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ca9aa0b55605f1..b85440736963cb 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1228,11 +1228,10 @@ argparse .. _whatsnew314-color-argparse: -* Introduced the optional *color* parameter to - :class:`argparse.ArgumentParser`, enabling color for help text. - This can be controlled by :ref:`environment variables - <using-on-controlling-color>`. Color has also been enabled for help in the - :ref:`stdlib CLIs <library-cmdline>` which use :mod:`!argparse`. +* Enable color for help text, which can be disabled with the optional *color* + parameter to :class:`argparse.ArgumentParser`. + This can also be controlled by :ref:`environment variables + <using-on-controlling-color>`. (Contributed by Hugo van Kemenade in :gh:`130645`.) diff --git a/Lib/argparse.py b/Lib/argparse.py index 83258cf3e0f37d..2144c81886ad19 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -167,7 +167,7 @@ def __init__( indent_increment=2, max_help_position=24, width=None, - color=False, + color=True, ): # default setting for width if width is None: @@ -1231,7 +1231,7 @@ def __init__(self, self._name_parser_map = {} self._choices_actions = [] self._deprecated = set() - self._color = False + self._color = True super(_SubParsersAction, self).__init__( option_strings=option_strings, @@ -1878,7 +1878,7 @@ def __init__(self, exit_on_error=True, *, suggest_on_error=False, - color=False, + color=True, ): superinit = super(ArgumentParser, self).__init__ superinit(description=description, diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 08ff41368d9bb0..2f39b42ab74299 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -18,7 +18,11 @@ import warnings from enum import StrEnum -from test.support import captured_stderr +from test.support import ( + captured_stderr, + force_not_colorized, + force_not_colorized_test_class, +) from test.support import import_helper from test.support import os_helper from test.support import script_helper @@ -1007,6 +1011,7 @@ def test_parse_enum_value(self): args = parser.parse_args(['--color', 'red']) self.assertEqual(args.color, self.Color.RED) + @force_not_colorized def test_help_message_contains_enum_choices(self): parser = argparse.ArgumentParser() parser.add_argument('--color', choices=self.Color, help='Choose a color') @@ -2403,6 +2408,7 @@ def test_modified_invalid_action(self): # Subparsers tests # ================ +@force_not_colorized_test_class class TestAddSubparsers(TestCase): """Test the add_subparsers method""" @@ -3009,6 +3015,7 @@ def test_nested_argument_group(self): # Parent parser tests # =================== +@force_not_colorized_test_class class TestParentParsers(TestCase): """Tests that parsers can be created with parent parsers""" @@ -3216,6 +3223,7 @@ def test_mutex_groups_parents(self): # Mutually exclusive group tests # ============================== +@force_not_colorized_test_class class TestMutuallyExclusiveGroupErrors(TestCase): def test_invalid_add_argument_group(self): @@ -3344,21 +3352,25 @@ def test_successes_when_required(self): actual_ns = parse_args(args_string.split()) self.assertEqual(actual_ns, expected_ns) + @force_not_colorized def test_usage_when_not_required(self): format_usage = self.get_parser(required=False).format_usage expected_usage = self.usage_when_not_required self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) + @force_not_colorized def test_usage_when_required(self): format_usage = self.get_parser(required=True).format_usage expected_usage = self.usage_when_required self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) + @force_not_colorized def test_help_when_not_required(self): format_help = self.get_parser(required=False).format_help help = self.usage_when_not_required + self.help self.assertEqual(format_help(), textwrap.dedent(help)) + @force_not_colorized def test_help_when_required(self): format_help = self.get_parser(required=True).format_help help = self.usage_when_required + self.help @@ -4030,11 +4042,13 @@ def _test(self, tester, parser_text): tester.maxDiff = None tester.assertEqual(expected_text, parser_text) + @force_not_colorized def test_format(self, tester): parser = self._get_parser(tester) format = getattr(parser, 'format_%s' % self.func_suffix) self._test(tester, format()) + @force_not_colorized def test_print(self, tester): parser = self._get_parser(tester) print_ = getattr(parser, 'print_%s' % self.func_suffix) @@ -4047,6 +4061,7 @@ def test_print(self, tester): setattr(sys, self.std_name, old_stream) self._test(tester, parser_text) + @force_not_colorized def test_print_file(self, tester): parser = self._get_parser(tester) print_ = getattr(parser, 'print_%s' % self.func_suffix) @@ -4788,6 +4803,7 @@ class TestHelpUsageMetavarsSpacesParentheses(HelpTestCase): version = '' +@force_not_colorized_test_class class TestHelpUsageNoWhitespaceCrash(TestCase): def test_all_suppressed_mutex_followed_by_long_arg(self): @@ -5469,6 +5485,7 @@ def custom_type(string): version = '' +@force_not_colorized_test_class class TestHelpCustomHelpFormatter(TestCase): maxDiff = None @@ -5765,6 +5782,7 @@ def test_conflict_error(self): self.assertRaises(argparse.ArgumentError, parser.add_argument, '--spam') + @force_not_colorized def test_resolve_error(self): get_parser = argparse.ArgumentParser parser = get_parser(prog='PROG', conflict_handler='resolve') @@ -6031,6 +6049,7 @@ def test_argument_error(self): class TestArgumentTypeError(TestCase): + @force_not_colorized def test_argument_type_error(self): def spam(string): @@ -6829,6 +6848,7 @@ def setUp(self): metavar = '<http[s]://example:1234>' self.parser.add_argument('--proxy', metavar=metavar) + @force_not_colorized def test_help_with_metavar(self): help_text = self.parser.format_help() self.assertEqual(help_text, textwrap.dedent('''\ @@ -6994,6 +7014,7 @@ def test_os_error(self): self.parser.parse_args, ['@no-such-file']) +@force_not_colorized_test_class class TestProgName(TestCase): source = textwrap.dedent('''\ import argparse diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 4b1f5991a39ee8..d5b751eafc5bbb 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2792,6 +2792,7 @@ def test_cli_verbose(self): out = self.expect_success("-v", fn) self.assertEqual(out.strip(), fn) + @support.force_not_colorized def test_cli_help(self): out = self.expect_success("-h") self.assertIn("usage: clinic.py", out) diff --git a/Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst b/Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst new file mode 100644 index 00000000000000..96e076dfe5bd12 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst @@ -0,0 +1 @@ +Enable color help by default in :mod:`argparse`. _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: arch...@mail-archive.com