]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-144384: Lazily import `_colorize` (#149318)
authorHugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Wed, 6 May 2026 16:07:43 +0000 (19:07 +0300)
committerGitHub <noreply@github.com>
Wed, 6 May 2026 16:07:43 +0000 (16:07 +0000)
.github/workflows/mypy.yml
Lib/difflib.py
Lib/doctest.py
Lib/profiling/sampling/live_collector/collector.py
Lib/profiling/sampling/pstats_collector.py
Lib/profiling/sampling/sample.py
Lib/test/test_difflib.py
Lib/test/test_traceback.py
Lib/traceback.py
Lib/unittest/runner.py
Misc/NEWS.d/next/Library/2026-05-03-17-32-24.gh-issue-144384.q-8jSr.rst [new file with mode: 0644]

index 7f6571ef9545765b91ef4c6f5bcad38841df80cc..490c32ecfc9a6298dd6272c8517bdfe9aa7d5607 100644 (file)
@@ -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
index eb249e3e288923032fd2712e855d1e0456a4d369..7a4ff15c34267b379e3548f0e27e74442c59289c 100644 (file)
@@ -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')
 
index 05acac1745ace9432c91a17395a0acef9ca03e75..be950079e396de1e7f617c57d1cb16faf6505547 100644 (file)
@@ -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')):
index c03df4075277cdb8862ebea4bf784669bbe4d61a..a53cfc6b719a105ef9b464cfcd23d6d03ceb0773 100644 (file)
@@ -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 (
index 6be1d698ffaa9a5c9c20b0105aca685438550ffc..50500296c15acc9ce3b82bee1b8723c622efb296 100644 (file)
@@ -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
 
index 9e315c080c353db7e0fb144ce856b2711e956981..5bbe24835813332592d6d7200221ee701527bb90 100644 (file)
@@ -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
index 771fd46e042a41606d9055ba4409c3e53f4a0e0e..46c9b2c1d8c9fc4f67abff948a4956cfdb8dcd95 100644 (file)
@@ -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
index 6624191f164bc189204c56fc0dee77ac43e28c4d..bb64153b91c92cc8b9f63accaac245de3234a796 100644 (file)
@@ -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()
index 66e88d0a588af33601a77bff07957c2acd7b0de5..88529e1c259a29fac643a31ac2411051114fd93d 100644 (file)
@@ -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("<stdin-") and frame_summary.filename.endswith('>'):
             filename = "<stdin>"
         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(
index 5f22d91aebd05f44ec1b9c2f1321bda0ec237c93..893fcba968c3ef8830a9d4a4ace5257d00089611 100644 (file)
@@ -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 (file)
index 0000000..aad4b71
--- /dev/null
@@ -0,0 +1 @@
+Lazily import :mod:`!_colorize`. Patch by Hugo van Kemenade.