From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 6 May 2026 16:07:43 +0000 (+0300) Subject: gh-144384: Lazily import `_colorize` (#149318) X-Git-Tag: v3.15.0b1~13 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7cea70e14dac091cbd7e0601b96a59458f8c9bee;p=thirdparty%2FPython%2Fcpython.git gh-144384: Lazily import `_colorize` (#149318) --- diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 7f6571ef9545..490c32ecfc9a 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -71,7 +71,8 @@ jobs: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.13" + python-version: "3.15" + allow-prereleases: true cache: pip cache-dependency-path: Tools/requirements-dev.txt - run: pip install -r Tools/requirements-dev.txt diff --git a/Lib/difflib.py b/Lib/difflib.py index eb249e3e2889..7a4ff15c3426 100644 --- a/Lib/difflib.py +++ b/Lib/difflib.py @@ -30,10 +30,10 @@ __all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher', 'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff', 'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match'] -from _colorize import can_colorize, get_theme from heapq import nlargest as _nlargest from collections import namedtuple as _namedtuple from types import GenericAlias +lazy from _colorize import can_colorize, get_theme Match = _namedtuple('Match', 'a b size') diff --git a/Lib/doctest.py b/Lib/doctest.py index 05acac1745ac..be950079e396 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -106,8 +106,8 @@ import types import unittest from io import StringIO, TextIOWrapper, BytesIO from collections import namedtuple -import _colorize # Used in doctests -from _colorize import ANSIColors, can_colorize +lazy import _colorize # Used in doctests +lazy from _colorize import ANSIColors, can_colorize class TestResults(namedtuple('TestResults', 'failed attempted')): diff --git a/Lib/profiling/sampling/live_collector/collector.py b/Lib/profiling/sampling/live_collector/collector.py index c03df4075277..a53cfc6b719a 100644 --- a/Lib/profiling/sampling/live_collector/collector.py +++ b/Lib/profiling/sampling/live_collector/collector.py @@ -9,7 +9,7 @@ import site import sys import sysconfig import time -import _colorize +lazy import _colorize from ..collector import Collector, extract_lineno from ..constants import ( diff --git a/Lib/profiling/sampling/pstats_collector.py b/Lib/profiling/sampling/pstats_collector.py index 6be1d698ffaa..50500296c15a 100644 --- a/Lib/profiling/sampling/pstats_collector.py +++ b/Lib/profiling/sampling/pstats_collector.py @@ -1,8 +1,8 @@ import collections import marshal import pstats +lazy from _colorize import ANSIColors -from _colorize import ANSIColors from .collector import Collector, extract_lineno from .constants import MICROSECONDS_PER_SECOND, PROFILING_MODE_CPU diff --git a/Lib/profiling/sampling/sample.py b/Lib/profiling/sampling/sample.py index 9e315c080c35..5bbe24835813 100644 --- a/Lib/profiling/sampling/sample.py +++ b/Lib/profiling/sampling/sample.py @@ -6,7 +6,7 @@ import sys import sysconfig import time from collections import deque -from _colorize import ANSIColors +lazy from _colorize import ANSIColors from .pstats_collector import PstatsCollector from .stack_collector import CollapsedStackCollector, FlamegraphCollector diff --git a/Lib/test/test_difflib.py b/Lib/test/test_difflib.py index 771fd46e042a..46c9b2c1d8c9 100644 --- a/Lib/test/test_difflib.py +++ b/Lib/test/test_difflib.py @@ -1,5 +1,7 @@ import difflib +from test import support from test.support import findfile, force_colorized +from test.support.import_helper import ensure_lazy_imports import unittest import doctest import sys @@ -644,6 +646,12 @@ def setUpModule(): difflib.HtmlDiff._default_prefix = 0 +class LazyImportTest(unittest.TestCase): + @support.cpython_only + def test_lazy_import(self): + ensure_lazy_imports("difflib", {"_colorize"}) + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(difflib)) return tests diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 6624191f164b..bb64153b91c9 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -23,7 +23,7 @@ from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ, requires_subprocess, os_helper) from test.support.os_helper import TESTFN, temp_dir, unlink from test.support.script_helper import assert_python_ok, assert_python_failure, make_script -from test.support.import_helper import forget +from test.support.import_helper import ensure_lazy_imports, forget from test.support import force_not_colorized, force_not_colorized_test_class import json @@ -5632,5 +5632,11 @@ class TestLazyImportSuggestions(unittest.TestCase): self.assertNotIn(b"BAR_MODULE_LOADED", stdout) +class LazyImportTest(unittest.TestCase): + @support.cpython_only + def test_lazy_import(self): + ensure_lazy_imports("traceback", {"_colorize"}) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index 66e88d0a588a..88529e1c259a 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -16,9 +16,9 @@ import tokenize import io import importlib.util import pathlib -import _colorize from contextlib import suppress +lazy import _colorize try: from _missing_stdlib_info import _MISSING_STDLIB_MODULE_MESSAGES @@ -32,6 +32,36 @@ __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'FrameSummary', 'StackSummary', 'TracebackException', 'walk_stack', 'walk_tb', 'print_list'] + +class _ShutdownTheme: + """Empty stand-in if `_colorize` cannot be imported during late shutdown.""" + def __getattr__(self, _): return self + def __getitem__(self, _): return "" + def __format__(self, _): return "" + def __str__(self): return "" + def __add__(self, other): return other + __radd__ = __add__ + + +_shutdown_theme = _ShutdownTheme() + + +def _safe_get_theme(*, force_color=False, force_no_color=False): + try: + return _colorize.get_theme( + force_color=force_color, force_no_color=force_no_color + ) + except ImportError: + return _shutdown_theme + + +def _safe_can_colorize(*, file=None): + try: + return _colorize.can_colorize(file=file) + except ImportError: + return False + + # # Formatting and printing lists of traceback lines. # @@ -151,7 +181,7 @@ BUILTIN_EXCEPTION_LIMIT = object() def _print_exception_bltin(exc, file=None, /): if file is None: file = sys.stderr if sys.stderr is not None else sys.__stderr__ - colorize = _colorize.can_colorize(file=file) + colorize = _safe_can_colorize(file=file) return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) @@ -199,9 +229,9 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize= valuestr = _safe_string(value, 'exception') end_char = "\n" if insert_final_newline else "" if colorize: - theme = _colorize.get_theme(force_color=True).traceback + theme = _safe_get_theme(force_color=True).traceback else: - theme = _colorize.get_theme(force_no_color=True).traceback + theme = _safe_get_theme(force_no_color=True).traceback if value is None or not valuestr: line = f"{theme.type}{etype}{theme.reset}{end_char}" else: @@ -555,9 +585,9 @@ class StackSummary(list): if frame_summary.filename.startswith("'): filename = "" if colorize: - theme = _colorize.get_theme(force_color=True).traceback + theme = _safe_get_theme(force_color=True).traceback else: - theme = _colorize.get_theme(force_no_color=True).traceback + theme = _safe_get_theme(force_no_color=True).traceback row.append( ' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( theme.filename, @@ -1344,9 +1374,9 @@ class TracebackException: """ colorize = kwargs.get("colorize", False) if colorize: - theme = _colorize.get_theme(force_color=True).traceback + theme = _safe_get_theme(force_color=True).traceback else: - theme = _colorize.get_theme(force_no_color=True).traceback + theme = _safe_get_theme(force_no_color=True).traceback indent = 3 * _depth * ' ' if not self._have_exc_type: @@ -1494,9 +1524,9 @@ class TracebackException: # Show exactly where the problem was found. colorize = kwargs.get("colorize", False) if colorize: - theme = _colorize.get_theme(force_color=True).traceback + theme = _safe_get_theme(force_color=True).traceback else: - theme = _colorize.get_theme(force_no_color=True).traceback + theme = _safe_get_theme(force_no_color=True).traceback filename_suffix = '' if self.lineno is not None: yield ' File {}"{}"{}, line {}{}{}\n'.format( diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py index 5f22d91aebd0..893fcba968c3 100644 --- a/Lib/unittest/runner.py +++ b/Lib/unittest/runner.py @@ -4,11 +4,10 @@ import sys import time import warnings -from _colorize import get_theme - from . import result from .case import _SubTest from .signals import registerResult +lazy from _colorize import get_theme __unittest = True diff --git a/Misc/NEWS.d/next/Library/2026-05-03-17-32-24.gh-issue-144384.q-8jSr.rst b/Misc/NEWS.d/next/Library/2026-05-03-17-32-24.gh-issue-144384.q-8jSr.rst new file mode 100644 index 000000000000..aad4b716e053 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-03-17-32-24.gh-issue-144384.q-8jSr.rst @@ -0,0 +1 @@ +Lazily import :mod:`!_colorize`. Patch by Hugo van Kemenade.