Skip to content

Add stubs for the stdlib _colorize module - #16370

Open
anistark wants to merge 3 commits into
python:mainfrom
anistark:add-colorize-stubs
Open

Add stubs for the stdlib _colorize module#16370
anistark wants to merge 3 commits into
python:mainfrom
anistark:add-colorize-stubs

Conversation

@anistark

@anistark anistark commented Sep 9, 2026

Copy link
Copy Markdown

_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

`_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 python#16361
@github-actions

This comment has been minimized.

@srittau srittau left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a full review yet, but a few things I noticed. In general, the stub file should use the same ordering as the implementation, even if it means repeating version info checks.

Comment thread stdlib/_colorize.pyi Outdated
BOLD: str
GREY: str

NoColors: ANSIColors

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NoColors: ANSIColors
NoColors: Final[ANSIColors]

Comment thread stdlib/_colorize.pyi Outdated
Comment on lines +61 to +62
if sys.version_info >= (3, 14):
ColorCodes: set[str]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this directly above NoColors so it matches the source order. Also let's make it final.

Comment thread stdlib/_colorize.pyi Outdated
Comment on lines +58 to +59
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: ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IO should be avoided in argument positions. In this case it should be a protocol with one optional item fileno. Since optional protocol items are not yet supported (see python/typing#601), the best we can do is probably something like this:

Suggested change
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: ...
# A protocol with an optional `fileno(self) -> int: ...` member.
_MaySupportFileno: TypeAlias = Any
def get_colors(colorize: bool = False, *, file: _MaySupportFileno | None = None) -> ANSIColors: ...
def can_colorize(*, file: _MaySupportFileno | None = None) -> bool: ...

@dangotbanned dangotbanned left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one, @anistark you beat me to it on this 😉

Comment thread stdlib/_colorize.pyi
Comment on lines +96 to +103
@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 = ...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment applies to every ThemeSection

I didn't mention this in #16361, but I was partly motivated by wanting to be able to see the default colors more easily.
These stubs show the names, but compared to the source, they seem less helpful than they could be:

https://github.com/python/cpython/blob/8f847875d60c81841e18a20510257a6e1b45b146/Lib/_colorize.py#L168-L187

@dataclass(frozen=True, kw_only=True)
class Argparse(ThemeSection):
    usage: str = ANSIColors.BOLD_BLUE
    prog: str = ANSIColors.BOLD_MAGENTA
    prog_extra: str = ANSIColors.MAGENTA
    heading: str = ANSIColors.BOLD_BLUE
    summary_long_option: str = ANSIColors.CYAN
    summary_short_option: str = ANSIColors.GREEN

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't clear to me whether these values would be considered complex

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept these as ...: the style guide only allows literal defaults, and ruff PYI015 rewrites = ANSIColors.BOLD_BLUE back to ... under the repo config.

The values are now readable on ANSIColors itself since its members carry their literals.

Comment thread stdlib/_colorize.pyi Outdated
Comment on lines +10 to +17
class ANSIColors:
RESET: str
BLACK: str
BLUE: str
CYAN: str
GREEN: str
MAGENTA: str
RED: str

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/python/cpython/blob/8f847875d60c81841e18a20510257a6e1b45b146/Lib/_colorize.py#L15-L63

I think all of these could be Final or ClassVars?

class ANSIColors:
    RESET = "\x1b[0m"
    BLACK = "\x1b[30m"
    BLUE = "\x1b[34m"
    CYAN = "\x1b[36m"
    GREEN = "\x1b[32m"
    GREY = "\x1b[90m"
    MAGENTA = "\x1b[35m"
    RED = "\x1b[31m"

@github-actions

This comment has been minimized.

@anistark

Copy link
Copy Markdown
Author

Thanks for the reviews @srittau and @dangotbanned.

Fixed most of it.

@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add stubs for stdlib _colorize module

3 participants