mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-141570: can_colorize: Expect fileno() to raise OSError, as documented (#141716) In Fedora, we've been given a slightly incomplete reproducer for a problematic Python 3.14 color-related change in argparse that leads to an exception when Python is used from mod_wsgi: https://bugzilla.redhat.com/2414940 mod_wsgi replaces sys.stdout with a custom object that raises OSError on .fileno():8460dbfcd5/src/server/wsgi_logger.c (L434-L440)This should be supported, as the documentation of fileno explicitly says: > An OSError is raised if the IO object does not use a file descriptor. https://docs.python.org/3.14/library/io.html#io.IOBase.fileno The previously expected exception inherits from OSError, so it is still expected. Fixes https://github.com/python/cpython/issues/141570 (cherry picked from commit96f496a949) Co-authored-by: Miro HronĨok <miro@hroncok.cz> Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
119 lines
3 KiB
Python
119 lines
3 KiB
Python
from __future__ import annotations
|
|
import os
|
|
import sys
|
|
|
|
COLORIZE = True
|
|
|
|
# types
|
|
if False:
|
|
from typing import IO
|
|
|
|
|
|
class ANSIColors:
|
|
RESET = "\x1b[0m"
|
|
|
|
BLACK = "\x1b[30m"
|
|
BLUE = "\x1b[34m"
|
|
CYAN = "\x1b[36m"
|
|
GREEN = "\x1b[32m"
|
|
MAGENTA = "\x1b[35m"
|
|
RED = "\x1b[31m"
|
|
WHITE = "\x1b[37m" # more like LIGHT GRAY
|
|
YELLOW = "\x1b[33m"
|
|
|
|
BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
|
|
BOLD_BLUE = "\x1b[1;34m"
|
|
BOLD_CYAN = "\x1b[1;36m"
|
|
BOLD_GREEN = "\x1b[1;32m"
|
|
BOLD_MAGENTA = "\x1b[1;35m"
|
|
BOLD_RED = "\x1b[1;31m"
|
|
BOLD_WHITE = "\x1b[1;37m" # actual WHITE
|
|
BOLD_YELLOW = "\x1b[1;33m"
|
|
|
|
# intense = like bold but without being bold
|
|
INTENSE_BLACK = "\x1b[90m"
|
|
INTENSE_BLUE = "\x1b[94m"
|
|
INTENSE_CYAN = "\x1b[96m"
|
|
INTENSE_GREEN = "\x1b[92m"
|
|
INTENSE_MAGENTA = "\x1b[95m"
|
|
INTENSE_RED = "\x1b[91m"
|
|
INTENSE_WHITE = "\x1b[97m"
|
|
INTENSE_YELLOW = "\x1b[93m"
|
|
|
|
BACKGROUND_BLACK = "\x1b[40m"
|
|
BACKGROUND_BLUE = "\x1b[44m"
|
|
BACKGROUND_CYAN = "\x1b[46m"
|
|
BACKGROUND_GREEN = "\x1b[42m"
|
|
BACKGROUND_MAGENTA = "\x1b[45m"
|
|
BACKGROUND_RED = "\x1b[41m"
|
|
BACKGROUND_WHITE = "\x1b[47m"
|
|
BACKGROUND_YELLOW = "\x1b[43m"
|
|
|
|
INTENSE_BACKGROUND_BLACK = "\x1b[100m"
|
|
INTENSE_BACKGROUND_BLUE = "\x1b[104m"
|
|
INTENSE_BACKGROUND_CYAN = "\x1b[106m"
|
|
INTENSE_BACKGROUND_GREEN = "\x1b[102m"
|
|
INTENSE_BACKGROUND_MAGENTA = "\x1b[105m"
|
|
INTENSE_BACKGROUND_RED = "\x1b[101m"
|
|
INTENSE_BACKGROUND_WHITE = "\x1b[107m"
|
|
INTENSE_BACKGROUND_YELLOW = "\x1b[103m"
|
|
|
|
|
|
NoColors = ANSIColors()
|
|
|
|
for attr in dir(NoColors):
|
|
if not attr.startswith("__"):
|
|
setattr(NoColors, attr, "")
|
|
|
|
|
|
def get_colors(
|
|
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
|
|
) -> ANSIColors:
|
|
if colorize or can_colorize(file=file):
|
|
return ANSIColors()
|
|
else:
|
|
return NoColors
|
|
|
|
|
|
def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
|
|
|
|
def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
|
|
"""Exception-safe environment retrieval. See gh-128636."""
|
|
try:
|
|
return os.environ.get(k, fallback)
|
|
except Exception:
|
|
return fallback
|
|
|
|
if file is None:
|
|
file = sys.stdout
|
|
|
|
if not sys.flags.ignore_environment:
|
|
if _safe_getenv("PYTHON_COLORS") == "0":
|
|
return False
|
|
if _safe_getenv("PYTHON_COLORS") == "1":
|
|
return True
|
|
if _safe_getenv("NO_COLOR"):
|
|
return False
|
|
if not COLORIZE:
|
|
return False
|
|
if _safe_getenv("FORCE_COLOR"):
|
|
return True
|
|
if _safe_getenv("TERM") == "dumb":
|
|
return False
|
|
|
|
if not hasattr(file, "fileno"):
|
|
return False
|
|
|
|
if sys.platform == "win32":
|
|
try:
|
|
import nt
|
|
|
|
if not nt._supports_virtual_terminal():
|
|
return False
|
|
except (ImportError, AttributeError):
|
|
return False
|
|
|
|
try:
|
|
return os.isatty(file.fileno())
|
|
except OSError:
|
|
return hasattr(file, "isatty") and file.isatty()
|