From 041bd2a662af779abddc8d0bb4e6fa72a67b1533 Mon Sep 17 00:00:00 2001 From: Kumar Anirudha Date: Wed, 9 Sep 2026 21:41:18 +0530 Subject: [PATCH] Add stubs for the stdlib `_colorize` module `_colorize` has been available since Python 3.13 and gained the experimental theming API in 3.14, which was extended considerably in 3.15. The stub covers all three versions: - 3.13+: `COLORIZE`, `ANSIColors`, `NoColors`, `get_colors`, `can_colorize` - 3.14+: `ColorCodes`, `decolor`, `ThemeSection`, the `Argparse`, `Syntax`, `Traceback` and `Unittest` sections, `Theme`, `get_theme` and `set_theme` - 3.15+: `CursesColors`, `BackgroundStyle`, ten further theme sections, and the `kw_only` dataclass signatures that 3.15 switched to `attr` and `code` are loop variables that leak into the module namespace, and `BackgroundStyle` is declared with the `type` statement, which stubtest compares against the alias value; all three are allowlisted. Closes #16361 --- stdlib/@tests/stubtest_allowlists/py313.txt | 1 + stdlib/@tests/stubtest_allowlists/py314.txt | 3 + stdlib/@tests/stubtest_allowlists/py315.txt | 6 + stdlib/VERSIONS | 1 + stdlib/_colorize.pyi | 400 ++++++++++++++++++++ 5 files changed, 411 insertions(+) create mode 100644 stdlib/_colorize.pyi diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index f36328af3ef1..7b26a77f71e6 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -106,6 +106,7 @@ importlib.abc.FileLoader.load_module # method so code typing the loop as `AbstractEventLoop` can still pass `cleanup_socket=...`. See #15742. asyncio.events.AbstractEventLoop.create_unix_server +_colorize.attr # Loop variable that leaks into the module namespace _pyrepl\..+ # The internal implementation of the REPL on py313+; not for public consumption codecs.backslashreplace_errors # Runtime incorrectly has `self` codecs.ignore_errors # Runtime incorrectly has `self` diff --git a/stdlib/@tests/stubtest_allowlists/py314.txt b/stdlib/@tests/stubtest_allowlists/py314.txt index efc36490d4f6..dd9e714a29aa 100644 --- a/stdlib/@tests/stubtest_allowlists/py314.txt +++ b/stdlib/@tests/stubtest_allowlists/py314.txt @@ -95,6 +95,8 @@ importlib.abc.FileLoader.load_module # Allowlist entries that cannot or should not be fixed; >= 3.14 # ============================================================= +_colorize.code # Loop variable that leaks into the module namespace + # Undocumented private attributes .*\.ForwardRef\.__arg__ .*\.ForwardRef\.__ast_node__ @@ -134,6 +136,7 @@ multiprocessing.managers._BaseDictProxy.__reversed__ # method so code typing the loop as `AbstractEventLoop` can still pass `cleanup_socket=...`. See #15742. asyncio.events.AbstractEventLoop.create_unix_server +_colorize.attr # Loop variable that leaks into the module namespace _pyrepl\..+ # The internal implementation of the REPL on py313+; not for public consumption codecs.backslashreplace_errors # Runtime incorrectly has `self` codecs.ignore_errors # Runtime incorrectly has `self` diff --git a/stdlib/@tests/stubtest_allowlists/py315.txt b/stdlib/@tests/stubtest_allowlists/py315.txt index d4e59afad9f1..c3b7da13f1c3 100644 --- a/stdlib/@tests/stubtest_allowlists/py315.txt +++ b/stdlib/@tests/stubtest_allowlists/py315.txt @@ -11,6 +11,9 @@ dataclasses.field # Allowlist entries that cannot or should not be fixed; >= 3.15 # ============================================================= +# Declared with the `type` statement, so stubtest compares the stub against the alias value +_colorize.BackgroundStyle + # runtime default is a list object used as a sentinel base64.b64decode urllib.parse.urlunparse @@ -43,6 +46,8 @@ tkinter.Place.place_content # Allowlist entries that cannot or should not be fixed; >= 3.14 # ============================================================= +_colorize.code # Loop variable that leaks into the module namespace + # Undocumented private attributes .*\.ForwardRef\.__arg__ .*\.ForwardRef\.__ast_node__ @@ -108,6 +113,7 @@ types.UnionType.__qualname__ # method so code typing the loop as `AbstractEventLoop` can still pass `cleanup_socket=...`. See #15742. asyncio.events.AbstractEventLoop.create_unix_server +_colorize.attr # Loop variable that leaks into the module namespace _pyrepl\..+ # The internal implementation of the REPL on py313+; not for public consumption codecs.backslashreplace_errors # Runtime incorrectly has `self` codecs.ignore_errors # Runtime incorrectly has `self` diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 96eb98131db6..4d110df06657 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -26,6 +26,7 @@ _blake2: 3.6- _bz2: 3.3- _codecs: 3.0- _collections_abc: 3.3- +_colorize: 3.13- _compat_pickle: 3.1- _compression: 3.5-3.13 _contextvars: 3.7- diff --git a/stdlib/_colorize.pyi b/stdlib/_colorize.pyi new file mode 100644 index 000000000000..620d6a00c394 --- /dev/null +++ b/stdlib/_colorize.pyi @@ -0,0 +1,400 @@ +import builtins +import sys +from collections.abc import Iterator, Mapping +from dataclasses import dataclass +from typing import IO, Literal, TypeAlias +from typing_extensions import Self + +COLORIZE: bool + +class ANSIColors: + RESET: str + BLACK: str + BLUE: str + CYAN: str + GREEN: str + MAGENTA: str + RED: str + WHITE: str + YELLOW: str + BOLD_BLACK: str + BOLD_BLUE: str + BOLD_CYAN: str + BOLD_GREEN: str + BOLD_MAGENTA: str + BOLD_RED: str + BOLD_WHITE: str + BOLD_YELLOW: str + INTENSE_BLACK: str + INTENSE_BLUE: str + INTENSE_CYAN: str + INTENSE_GREEN: str + INTENSE_MAGENTA: str + INTENSE_RED: str + INTENSE_WHITE: str + INTENSE_YELLOW: str + BACKGROUND_BLACK: str + BACKGROUND_BLUE: str + BACKGROUND_CYAN: str + BACKGROUND_GREEN: str + BACKGROUND_MAGENTA: str + BACKGROUND_RED: str + BACKGROUND_WHITE: str + BACKGROUND_YELLOW: str + INTENSE_BACKGROUND_BLACK: str + INTENSE_BACKGROUND_BLUE: str + INTENSE_BACKGROUND_CYAN: str + INTENSE_BACKGROUND_GREEN: str + INTENSE_BACKGROUND_MAGENTA: str + INTENSE_BACKGROUND_RED: str + INTENSE_BACKGROUND_WHITE: str + INTENSE_BACKGROUND_YELLOW: str + if sys.version_info >= (3, 14): + BOLD: str + GREY: str + +NoColors: ANSIColors + +def get_colors(colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None) -> ANSIColors: ... +def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool: ... + +if sys.version_info >= (3, 14): + ColorCodes: set[str] + _theme: Theme # undocumented + default_theme: Theme + theme_no_color: Theme + + class ThemeSection(Mapping[str, str]): + def __post_init__(self) -> None: ... + def copy_with(self, **kwargs: str) -> Self: ... + @classmethod + def no_colors(cls) -> Self: ... + def __getitem__(self, key: str) -> str: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... + + def decolor(text: str) -> str: ... + def get_theme( + *, tty_file: IO[str] | IO[bytes] | None = None, force_color: bool = False, force_no_color: bool = False + ) -> Theme: ... + def set_theme(t: Theme) -> None: ... + +if sys.version_info >= (3, 15): + BackgroundStyle: TypeAlias = Literal["dark", "light"] + + class CursesColors: + BLACK: int + RED: int + GREEN: int + YELLOW: int + BLUE: int + MAGENTA: int + CYAN: int + WHITE: int + DEFAULT: int + + @dataclass(frozen=True, kw_only=True) + class Argparse(ThemeSection): + usage: str = ... + prog: str = ... + prog_extra: str = ... + heading: str = ... + summary_long_option: str = ... + summary_short_option: str = ... + summary_label: str = ... + summary_action: str = ... + long_option: str = ... + short_option: str = ... + label: str = ... + action: str = ... + default: str = ... + interpolated_value: str = ... + reset: str = ... + error: str = ... + warning: str = ... + message: str = ... + + @dataclass(frozen=True, kw_only=True) + class Ast(ThemeSection): + node: str = ... + field: str = ... + attribute: str = ... + string: str = ... + number: str = ... + keyword: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Calendar(ThemeSection): + header: str = ... + highlight: str = ... + weekday: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Difflib(ThemeSection): + added: str = ... + context: str = ... + header: str = ... + hunk: str = ... + removed: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class FancyCompleter(ThemeSection): + function: builtins.str = ... + builtin_function_or_method: builtins.str = ... + method: builtins.str = ... + method_wrapper: builtins.str = ... + wrapper_descriptor: builtins.str = ... + method_descriptor: builtins.str = ... + int: builtins.str = ... + float: builtins.str = ... + complex: builtins.str = ... + bool: builtins.str = ... + type: builtins.str = ... + module: builtins.str = ... + NoneType: builtins.str = ... + bytes: builtins.str = ... + str: builtins.str = ... + + @dataclass(frozen=True, kw_only=True) + class HttpServer(ThemeSection): + error: str = ... + path: str = ... + serving: str = ... + size: str = ... + status_informational: str = ... + status_ok: str = ... + status_redirect: str = ... + status_client_error: str = ... + status_server_error: str = ... + timestamp: str = ... + url: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class LiveProfiler(ThemeSection): + title_fg: int = ... + title_bg: int = ... + pid_fg: int = ... + uptime_fg: int = ... + time_fg: int = ... + interval_fg: int = ... + thread_all_fg: int = ... + thread_single_fg: int = ... + bar_good_fg: int = ... + bar_bad_fg: int = ... + on_gil_fg: int = ... + off_gil_fg: int = ... + waiting_gil_fg: int = ... + gc_fg: int = ... + func_total_fg: int = ... + func_exec_fg: int = ... + func_stack_fg: int = ... + func_shown_fg: int = ... + sorted_header_fg: int = ... + sorted_header_bg: int = ... + normal_header_fg: int = ... + normal_header_bg: int = ... + samples_fg: int = ... + file_fg: int = ... + func_fg: int = ... + trend_up_fg: int = ... + trend_down_fg: int = ... + medal_gold_fg: int = ... + medal_silver_fg: int = ... + medal_bronze_fg: int = ... + background_style: BackgroundStyle = ... + + LiveProfilerLight: LiveProfiler + + @dataclass(frozen=True, kw_only=True) + class ProfilerDump(ThemeSection): + header: str = ... + interpreter: str = ... + thread: str = ... + status: str = ... + frame_index: str = ... + frame: str = ... + filename: str = ... + line_no: str = ... + source: str = ... + source_highlight: str = ... + opcode: str = ... + warning: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Pickletools(ThemeSection): + annotation: str = ... + arg_number: str = ... + arg_string: str = ... + mark: str = ... + op_call: str = ... + op_container: str = ... + op_memo: str = ... + op_meta: str = ... + op_stack: str = ... + opcode_code: str = ... + position: str = ... + proto: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Syntax(ThemeSection): + prompt: str = ... + keyword: str = ... + keyword_constant: str = ... + builtin: str = ... + comment: str = ... + string: str = ... + number: str = ... + op: str = ... + definition: str = ... + soft_keyword: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Timeit(ThemeSection): + timing: str = ... + best: str = ... + per_loop: str = ... + punctuation: str = ... + warning: str = ... + warning_worst: str = ... + warning_best: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Tokenize(ThemeSection): + whitespace: str = ... + error: str = ... + position: str = ... + delimiter: str = ... + + @dataclass(frozen=True, kw_only=True) + class Traceback(ThemeSection): + type: str = ... + message: str = ... + note: str = ... + filename: str = ... + line_no: str = ... + frame: str = ... + error_highlight: str = ... + error_range: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Unittest(ThemeSection): + passed: str = ... + warn: str = ... + fail: str = ... + fail_info: str = ... + reset: str = ... + + @dataclass(frozen=True, kw_only=True) + class Theme: + argparse: Argparse = ... + ast: Ast = ... + calendar: Calendar = ... + difflib: Difflib = ... + fancycompleter: FancyCompleter = ... + http_server: HttpServer = ... + live_profiler: LiveProfiler = ... + pickletools: Pickletools = ... + profiler_dump: ProfilerDump = ... + syntax: Syntax = ... + timeit: Timeit = ... + tokenize: Tokenize = ... + traceback: Traceback = ... + unittest: Unittest = ... + def copy_with( + self, + *, + argparse: Argparse | None = None, + ast: Ast | None = None, + calendar: Calendar | None = None, + difflib: Difflib | None = None, + fancycompleter: FancyCompleter | None = None, + http_server: HttpServer | None = None, + live_profiler: LiveProfiler | None = None, + pickletools: Pickletools | None = None, + profiler_dump: ProfilerDump | None = None, + syntax: Syntax | None = None, + timeit: Timeit | None = None, + tokenize: Tokenize | None = None, + traceback: Traceback | None = None, + unittest: Unittest | None = None, + ) -> Self: ... + @classmethod + def no_colors(cls) -> Self: ... + + light_profiler_theme: Theme + +elif sys.version_info >= (3, 14): + @dataclass(frozen=True, kw_only=True) + class Argparse(ThemeSection): + usage: str = ... + prog: str = ... + prog_extra: str = ... + heading: str = ... + summary_long_option: str = ... + summary_short_option: str = ... + summary_label: str = ... + summary_action: str = ... + long_option: str = ... + short_option: str = ... + label: str = ... + action: str = ... + reset: str = ... + + @dataclass(frozen=True) + class Syntax(ThemeSection): + prompt: str = ... + keyword: str = ... + keyword_constant: str = ... + builtin: str = ... + comment: str = ... + string: str = ... + number: str = ... + op: str = ... + definition: str = ... + soft_keyword: str = ... + reset: str = ... + + @dataclass(frozen=True) + class Traceback(ThemeSection): + type: str = ... + message: str = ... + filename: str = ... + line_no: str = ... + frame: str = ... + error_highlight: str = ... + error_range: str = ... + reset: str = ... + + @dataclass(frozen=True) + class Unittest(ThemeSection): + passed: str = ... + warn: str = ... + fail: str = ... + fail_info: str = ... + reset: str = ... + + @dataclass(frozen=True) + class Theme: + argparse: Argparse = ... + syntax: Syntax = ... + traceback: Traceback = ... + unittest: Unittest = ... + def copy_with( + self, + *, + argparse: Argparse | None = None, + syntax: Syntax | None = None, + traceback: Traceback | None = None, + unittest: Unittest | None = None, + ) -> Self: ... + @classmethod + def no_colors(cls) -> Self: ...