diff --git a/Lib/argparse.py b/Lib/argparse.py index 398825508f5917..1d550264ae420f 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -89,8 +89,8 @@ import os as _os import re as _re import sys as _sys - -from gettext import gettext as _, ngettext +from gettext import gettext as _ +from gettext import ngettext SUPPRESS = '==SUPPRESS==' @@ -191,10 +191,10 @@ def __init__( self._set_color(False) - def _set_color(self, color): + def _set_color(self, color, *, file=None): from _colorize import can_colorize, decolor, get_theme - if color and can_colorize(): + if color and can_colorize(file=file): self._theme = get_theme(force_color=True).argparse self._decolor = decolor else: @@ -1675,7 +1675,7 @@ def _get_optional_kwargs(self, *args, **kwargs): option_strings = [] for option_string in args: # error on strings that don't start with an appropriate prefix - if not option_string[0] in self.prefix_chars: + if option_string[0] not in self.prefix_chars: raise ValueError( f'invalid option string {option_string!r}: ' f'must start with a character {self.prefix_chars!r}') @@ -2455,7 +2455,7 @@ def _parse_optional(self, arg_string): return None # if it doesn't start with a prefix, it was meant to be positional - if not arg_string[0] in self.prefix_chars: + if arg_string[0] not in self.prefix_chars: return None # if the option string is present in the parser, return the action @@ -2717,14 +2717,16 @@ def _check_value(self, action, value): # Help-formatting methods # ======================= - def format_usage(self): - formatter = self._get_formatter() + def format_usage(self, formatter=None): + if formatter is None: + formatter = self._get_formatter() formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups) return formatter.format_help() - def format_help(self): - formatter = self._get_formatter() + def format_help(self, formatter=None): + if formatter is None: + formatter = self._get_formatter() # usage formatter.add_usage(self.usage, self._actions, @@ -2746,9 +2748,9 @@ def format_help(self): # determine help from format above return formatter.format_help() - def _get_formatter(self): + def _get_formatter(self, file=None): formatter = self.formatter_class(prog=self.prog) - formatter._set_color(self.color) + formatter._set_color(self.color, file=file) return formatter def _get_validation_formatter(self): @@ -2765,12 +2767,26 @@ def _get_validation_formatter(self): def print_usage(self, file=None): if file is None: file = _sys.stdout - self._print_message(self.format_usage(), file) + formatter = self._get_formatter(file=file) + try: + usage_text = self.format_usage(formatter=formatter) + except TypeError: + # Backward compatibility for formatter classes that + # do not accept the 'formatter' keyword argument. + usage_text = self.format_usage() + self._print_message(usage_text, file) def print_help(self, file=None): if file is None: file = _sys.stdout - self._print_message(self.format_help(), file) + formatter = self._get_formatter(file=file) + try: + help_text = self.format_help(formatter=formatter) + except TypeError: + # Backward compatibility for formatter classes that + # do not accept the 'formatter' keyword argument. + help_text = self.format_help() + self._print_message(help_text, file) def _print_message(self, message, file=None): if message: diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css index c75f2324b6d499..18d2279da9b645 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css @@ -789,7 +789,7 @@ body.resizing-sidebar { .tooltip-location { font-family: var(--font-mono); font-size: 11px; - color: var(--text-muted); + color: var(--text-secondary); background: var(--bg-tertiary); padding: 4px 8px; border-radius: 4px; diff --git a/Lib/profiling/sampling/_shared_assets/base.css b/Lib/profiling/sampling/_shared_assets/base.css index d9223a98c0f756..54e3d78f8ebf34 100644 --- a/Lib/profiling/sampling/_shared_assets/base.css +++ b/Lib/profiling/sampling/_shared_assets/base.css @@ -44,7 +44,7 @@ --text-primary: #2e3338; --text-secondary: #5a6c7d; - --text-muted: #8b949e; + --text-muted: #6f767e; --accent: #3776ab; --accent-hover: #2d5aa0; @@ -91,7 +91,7 @@ --text-primary: #e6edf3; --text-secondary: #8b949e; - --text-muted: #6e7681; + --text-muted: #757e8a; --accent: #58a6ff; --accent-hover: #79b8ff; diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 7c5eed21219de0..ab5382e41e7871 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -7558,6 +7558,40 @@ def test_error_and_warning_not_colorized_when_disabled(self): self.assertNotIn('\x1b[', warn) self.assertIn('warning:', warn) + def test_print_help_uses_target_file_for_color_decision(self): + parser = argparse.ArgumentParser(prog='PROG', color=True) + parser.add_argument('--opt') + output = io.StringIO() + calls = [] + + def fake_can_colorize(*, file=None): + calls.append(file) + return file is None + + with swap_attr(_colorize, 'can_colorize', fake_can_colorize): + parser.print_help(file=output) + + self.assertIs(calls[-1], output) + self.assertIn(output, calls) + self.assertNotIn('\x1b[', output.getvalue()) + + def test_print_usage_uses_target_file_for_color_decision(self): + parser = argparse.ArgumentParser(prog='PROG', color=True) + parser.add_argument('--opt') + output = io.StringIO() + calls = [] + + def fake_can_colorize(*, file=None): + calls.append(file) + return file is None + + with swap_attr(_colorize, 'can_colorize', fake_can_colorize): + parser.print_usage(file=output) + + self.assertIs(calls[-1], output) + self.assertIn(output, calls) + self.assertNotIn('\x1b[', output.getvalue()) + class TestModule(unittest.TestCase): def test_deprecated__version__(self): diff --git a/Misc/NEWS.d/next/Library/2025-10-23-06-38-35.gh-issue-139946.HZa5hu.rst b/Misc/NEWS.d/next/Library/2025-10-23-06-38-35.gh-issue-139946.HZa5hu.rst new file mode 100644 index 00000000000000..fb47931728414d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-23-06-38-35.gh-issue-139946.HZa5hu.rst @@ -0,0 +1 @@ +Distinguish stdout and stderr when colorizing output in argparse module. diff --git a/Misc/NEWS.d/next/Library/2025-12-06-13-19-43.gh-issue-142207.x_X9oH.rst b/Misc/NEWS.d/next/Library/2025-12-06-13-19-43.gh-issue-142207.x_X9oH.rst new file mode 100644 index 00000000000000..69ca8c41ac9b8b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-06-13-19-43.gh-issue-142207.x_X9oH.rst @@ -0,0 +1,2 @@ +Fix: profiling.sampling may cause assertion ``!(has_gil && +gil_requested)`` diff --git a/Modules/_remote_debugging/threads.c b/Modules/_remote_debugging/threads.c index 69819eb8dcd645..774338f9dc241e 100644 --- a/Modules/_remote_debugging/threads.c +++ b/Modules/_remote_debugging/threads.c @@ -339,12 +339,10 @@ unwind_stack_for_thread( #endif if (has_gil) { status_flags |= THREAD_STATUS_HAS_GIL; + // gh-142207 for remote debugging. + gil_requested = 0; } - // Assert that we never have both HAS_GIL and GIL_REQUESTED set at the same time - // This would indicate a race condition in the GIL state tracking - assert(!(has_gil && gil_requested)); - // Check CPU status long pthread_id = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.thread_id);