]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-130645: Default to color help in argparse (#136809)
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Sun, 20 Jul 2025 21:55:44 +0000 (23:55 +0200)
committerGitHub <noreply@github.com>
Sun, 20 Jul 2025 21:55:44 +0000 (14:55 -0700)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
Doc/library/argparse.rst
Doc/whatsnew/3.14.rst
Lib/argparse.py
Lib/test/test_argparse.py
Lib/test/test_clinic.py
Misc/NEWS.d/next/Library/2025-07-19-16-20-54.gh-issue-130645.O-dYcN.rst [new file with mode: 0644]

index a08f713ab56ba39075fc60a230652617151b41bf..79e15994491eff884082d1af5e57a50dafa7dd1a 100644 (file)
@@ -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
 
 
index 6ddc77d8f950384339264bc60bfaffad5ad67f2b..ba06930cf14eec7b25c9a68eeeb067fa237fc4f5 100644 (file)
@@ -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`.)
 
 
index 83258cf3e0f37d0e0b3e4a6d0cadf021199d705d..2144c81886ad19fbe707b0f857b7fbdedc3a7d0f 100644 (file)
@@ -167,7 +167,7 @@ class HelpFormatter(object):
         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 @@ class _SubParsersAction(Action):
         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 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
                  exit_on_error=True,
                  *,
                  suggest_on_error=False,
-                 color=False,
+                 color=True,
                  ):
         superinit = super(ArgumentParser, self).__init__
         superinit(description=description,
index ddd48b1bc0c56fe75009705ce571c6b582c12f44..fc73174d98cd6f506ddefe6303fa97f40f6e3883 100644 (file)
@@ -18,7 +18,11 @@ import argparse
 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 @@ class TestStrEnumChoices(TestCase):
         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 @@ class TestInvalidAction(TestCase):
 # Subparsers tests
 # ================
 
+@force_not_colorized_test_class
 class TestAddSubparsers(TestCase):
     """Test the add_subparsers method"""
 
@@ -3009,6 +3015,7 @@ class TestGroupConstructor(TestCase):
 # Parent parser tests
 # ===================
 
+@force_not_colorized_test_class
 class TestParentParsers(TestCase):
     """Tests that parsers can be created with parent parsers"""
 
@@ -3216,6 +3223,7 @@ class TestParentParsers(TestCase):
 # Mutually exclusive group tests
 # ==============================
 
+@force_not_colorized_test_class
 class TestMutuallyExclusiveGroupErrors(TestCase):
 
     def test_invalid_add_argument_group(self):
@@ -3344,21 +3352,25 @@ class MEMixin(object):
                 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 @@ class TestHelpFormattingMetaclass(type):
                 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 @@ class TestHelpFormattingMetaclass(type):
                     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 @@ class TestHelpMetavarTypeFormatter(HelpTestCase):
     version = ''
 
 
+@force_not_colorized_test_class
 class TestHelpCustomHelpFormatter(TestCase):
     maxDiff = None
 
@@ -5765,6 +5782,7 @@ class TestConflictHandling(TestCase):
         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 @@ class TestArgumentError(TestCase):
 
 class TestArgumentTypeError(TestCase):
 
+    @force_not_colorized
     def test_argument_type_error(self):
 
         def spam(string):
@@ -6829,6 +6848,7 @@ class TestWrappingMetavar(TestCase):
         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 @@ class TestExitOnError(TestCase):
                                self.parser.parse_args, ['@no-such-file'])
 
 
+@force_not_colorized_test_class
 class TestProgName(TestCase):
     source = textwrap.dedent('''\
         import argparse
index e83114794519d5c6e1d071f17013f4434af47988..b1a81cfc1b124f2433b81afe548800358c97abda 100644 (file)
@@ -2792,6 +2792,7 @@ class ClinicExternalTest(TestCase):
             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 (file)
index 0000000..96e076d
--- /dev/null
@@ -0,0 +1 @@
+Enable color help by default in :mod:`argparse`.