From 5c4080a1083d5bf910d9981db59c27ec627c7332 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sat, 19 Jul 2025 14:38:21 +0200 Subject: [PATCH 1/7] Default to color help in argparse --- Lib/argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 83258cf3e0f37d..169b442e238a35 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: From f93920d098c24d6977ef0e71ca7dbc0bdfc17f8e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:09:38 +0200 Subject: [PATCH 2/7] Default to color help in argparse --- Lib/argparse.py | 4 ++-- Lib/test/test_argparse.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 169b442e238a35..2144c81886ad19 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -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 ddd48b1bc0c56f..7e356d3ae3fdd1 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') @@ -6829,6 +6847,7 @@ def setUp(self): metavar = '' 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('''\ From b1ef6402906b2d1ab670021554187c8f06170a26 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:21:01 +0200 Subject: [PATCH 3/7] Update docs --- Doc/library/argparse.rst | 20 ++++++------------- Doc/whatsnew/3.14.rst | 9 ++++----- ...-07-19-16-20-54.gh-issue-130645.O-dYcN.rst | 1 + 3 files changed, 11 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index f189f6b8fa8953..87993f88813d4a 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. @@ -620,27 +620,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. If you want to plain text +help messages, you can disable it 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 +Even if a CLI author has not disabled color, it can be :ref:`controlled using environment variables `. -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 c108a94692dca7..0ec3ed88874efc 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1204,11 +1204,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 - `. Color has also been enabled for help in the - :ref:`stdlib CLIs ` 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 + `. (Contributed by Hugo van Kemenade in :gh:`130645`.) 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..bc32482770597b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst @@ -0,0 +1 @@ +Default to color help in argparse. From dddcd65c04e26d2974e361253b8095e10254ab63 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:47:19 +0200 Subject: [PATCH 4/7] Update tests --- Lib/test/test_argparse.py | 2 ++ Lib/test/test_clinic.py | 1 + 2 files changed, 3 insertions(+) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 7e356d3ae3fdd1..fc73174d98cd6f 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -6049,6 +6049,7 @@ def test_argument_error(self): class TestArgumentTypeError(TestCase): + @force_not_colorized def test_argument_type_error(self): def spam(string): @@ -7013,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 e83114794519d5..b1a81cfc1b124f 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) From b5c0d881b497f4e6455cf3fdadcd100eae549077 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:19:12 +0300 Subject: [PATCH 5/7] Fix wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Langa --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 87993f88813d4a..b80c00e9eecba5 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -620,7 +620,7 @@ keyword argument:: color ^^^^^ -By default, the help message is printed in color. If you want to plain text +By default, the help message is printed in color. If you want plain text help messages, you can disable it by setting ``color`` to ``False``:: >>> parser = argparse.ArgumentParser(description='Process some integers.', From 82c7e2511c631ab9dfb7a031bd078a27296233fa Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:32:07 +0200 Subject: [PATCH 6/7] Improve description of disabling colour support locally vs in the ArgumentParser --- Doc/library/argparse.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index b80c00e9eecba5..ed603171102fe1 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -620,8 +620,11 @@ keyword argument:: color ^^^^^ -By default, the help message is printed in color. If you want plain text -help messages, you can disable it by setting ``color`` to ``False``:: +By default, the help message is printed in color using `ANSI escape sequences +`__. +If you want plain text help messages, you can disable this :ref:`in your local +environment `, or in the argument parser itself +by setting ``color`` to ``False``:: >>> parser = argparse.ArgumentParser(description='Process some integers.', ... color=False) @@ -630,9 +633,6 @@ help messages, you can disable it by setting ``color`` to ``False``:: ... help='an integer for the accumulator') >>> parser.parse_args(['--help']) -Even if a CLI author has not disabled color, it can be -:ref:`controlled using environment variables `. - .. versionadded:: 3.14 From 0abf68e3264d4953517edbd40856805761c5dc15 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:40:47 +0200 Subject: [PATCH 7/7] Add a cross-reference to NEWS --- .../next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index bc32482770597b..96e076dfe5bd12 100644 --- 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 @@ -1 +1 @@ -Default to color help in argparse. +Enable color help by default in :mod:`argparse`.