Skip to content

Commit 7cea70e

Browse files
authored
gh-144384: Lazily import _colorize (#149318)
1 parent d13fc36 commit 7cea70e

11 files changed

Lines changed: 65 additions & 20 deletions

File tree

.github/workflows/mypy.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ jobs:
7171
persist-credentials: false
7272
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
7373
with:
74-
python-version: "3.13"
74+
python-version: "3.15"
75+
allow-prereleases: true
7576
cache: pip
7677
cache-dependency-path: Tools/requirements-dev.txt
7778
- run: pip install -r Tools/requirements-dev.txt

Lib/difflib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff',
3131
'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match']
3232

33-
from _colorize import can_colorize, get_theme
3433
from heapq import nlargest as _nlargest
3534
from collections import namedtuple as _namedtuple
3635
from types import GenericAlias
36+
lazy from _colorize import can_colorize, get_theme
3737

3838
Match = _namedtuple('Match', 'a b size')
3939

Lib/doctest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ def _test():
106106
import unittest
107107
from io import StringIO, TextIOWrapper, BytesIO
108108
from collections import namedtuple
109-
import _colorize # Used in doctests
110-
from _colorize import ANSIColors, can_colorize
109+
lazy import _colorize # Used in doctests
110+
lazy from _colorize import ANSIColors, can_colorize
111111

112112

113113
class TestResults(namedtuple('TestResults', 'failed attempted')):

Lib/profiling/sampling/live_collector/collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import sys
1010
import sysconfig
1111
import time
12-
import _colorize
12+
lazy import _colorize
1313

1414
from ..collector import Collector, extract_lineno
1515
from ..constants import (

Lib/profiling/sampling/pstats_collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import collections
22
import marshal
33
import pstats
4+
lazy from _colorize import ANSIColors
45

5-
from _colorize import ANSIColors
66
from .collector import Collector, extract_lineno
77
from .constants import MICROSECONDS_PER_SECOND, PROFILING_MODE_CPU
88

Lib/profiling/sampling/sample.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sysconfig
77
import time
88
from collections import deque
9-
from _colorize import ANSIColors
9+
lazy from _colorize import ANSIColors
1010

1111
from .pstats_collector import PstatsCollector
1212
from .stack_collector import CollapsedStackCollector, FlamegraphCollector

Lib/test/test_difflib.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import difflib
2+
from test import support
23
from test.support import findfile, force_colorized
4+
from test.support.import_helper import ensure_lazy_imports
35
import unittest
46
import doctest
57
import sys
@@ -644,6 +646,12 @@ def setUpModule():
644646
difflib.HtmlDiff._default_prefix = 0
645647

646648

649+
class LazyImportTest(unittest.TestCase):
650+
@support.cpython_only
651+
def test_lazy_import(self):
652+
ensure_lazy_imports("difflib", {"_colorize"})
653+
654+
647655
def load_tests(loader, tests, pattern):
648656
tests.addTest(doctest.DocTestSuite(difflib))
649657
return tests

Lib/test/test_traceback.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
requires_subprocess, os_helper)
2424
from test.support.os_helper import TESTFN, temp_dir, unlink
2525
from test.support.script_helper import assert_python_ok, assert_python_failure, make_script
26-
from test.support.import_helper import forget
26+
from test.support.import_helper import ensure_lazy_imports, forget
2727
from test.support import force_not_colorized, force_not_colorized_test_class
2828

2929
import json
@@ -5632,5 +5632,11 @@ def test_suggestion_still_works_for_non_lazy_attributes(self):
56325632
self.assertNotIn(b"BAR_MODULE_LOADED", stdout)
56335633

56345634

5635+
class LazyImportTest(unittest.TestCase):
5636+
@support.cpython_only
5637+
def test_lazy_import(self):
5638+
ensure_lazy_imports("traceback", {"_colorize"})
5639+
5640+
56355641
if __name__ == "__main__":
56365642
unittest.main()

Lib/traceback.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import io
1717
import importlib.util
1818
import pathlib
19-
import _colorize
2019

2120
from contextlib import suppress
21+
lazy import _colorize
2222

2323
try:
2424
from _missing_stdlib_info import _MISSING_STDLIB_MODULE_MESSAGES
@@ -32,6 +32,36 @@
3232
'FrameSummary', 'StackSummary', 'TracebackException',
3333
'walk_stack', 'walk_tb', 'print_list']
3434

35+
36+
class _ShutdownTheme:
37+
"""Empty stand-in if `_colorize` cannot be imported during late shutdown."""
38+
def __getattr__(self, _): return self
39+
def __getitem__(self, _): return ""
40+
def __format__(self, _): return ""
41+
def __str__(self): return ""
42+
def __add__(self, other): return other
43+
__radd__ = __add__
44+
45+
46+
_shutdown_theme = _ShutdownTheme()
47+
48+
49+
def _safe_get_theme(*, force_color=False, force_no_color=False):
50+
try:
51+
return _colorize.get_theme(
52+
force_color=force_color, force_no_color=force_no_color
53+
)
54+
except ImportError:
55+
return _shutdown_theme
56+
57+
58+
def _safe_can_colorize(*, file=None):
59+
try:
60+
return _colorize.can_colorize(file=file)
61+
except ImportError:
62+
return False
63+
64+
3565
#
3666
# Formatting and printing lists of traceback lines.
3767
#
@@ -151,7 +181,7 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
151181
def _print_exception_bltin(exc, file=None, /):
152182
if file is None:
153183
file = sys.stderr if sys.stderr is not None else sys.__stderr__
154-
colorize = _colorize.can_colorize(file=file)
184+
colorize = _safe_can_colorize(file=file)
155185
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
156186

157187

@@ -199,9 +229,9 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
199229
valuestr = _safe_string(value, 'exception')
200230
end_char = "\n" if insert_final_newline else ""
201231
if colorize:
202-
theme = _colorize.get_theme(force_color=True).traceback
232+
theme = _safe_get_theme(force_color=True).traceback
203233
else:
204-
theme = _colorize.get_theme(force_no_color=True).traceback
234+
theme = _safe_get_theme(force_no_color=True).traceback
205235
if value is None or not valuestr:
206236
line = f"{theme.type}{etype}{theme.reset}{end_char}"
207237
else:
@@ -555,9 +585,9 @@ def format_frame_summary(self, frame_summary, **kwargs):
555585
if frame_summary.filename.startswith("<stdin-") and frame_summary.filename.endswith('>'):
556586
filename = "<stdin>"
557587
if colorize:
558-
theme = _colorize.get_theme(force_color=True).traceback
588+
theme = _safe_get_theme(force_color=True).traceback
559589
else:
560-
theme = _colorize.get_theme(force_no_color=True).traceback
590+
theme = _safe_get_theme(force_no_color=True).traceback
561591
row.append(
562592
' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
563593
theme.filename,
@@ -1344,9 +1374,9 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs):
13441374
"""
13451375
colorize = kwargs.get("colorize", False)
13461376
if colorize:
1347-
theme = _colorize.get_theme(force_color=True).traceback
1377+
theme = _safe_get_theme(force_color=True).traceback
13481378
else:
1349-
theme = _colorize.get_theme(force_no_color=True).traceback
1379+
theme = _safe_get_theme(force_no_color=True).traceback
13501380

13511381
indent = 3 * _depth * ' '
13521382
if not self._have_exc_type:
@@ -1494,9 +1524,9 @@ def _format_syntax_error(self, stype, **kwargs):
14941524
# Show exactly where the problem was found.
14951525
colorize = kwargs.get("colorize", False)
14961526
if colorize:
1497-
theme = _colorize.get_theme(force_color=True).traceback
1527+
theme = _safe_get_theme(force_color=True).traceback
14981528
else:
1499-
theme = _colorize.get_theme(force_no_color=True).traceback
1529+
theme = _safe_get_theme(force_no_color=True).traceback
15001530
filename_suffix = ''
15011531
if self.lineno is not None:
15021532
yield ' File {}"{}"{}, line {}{}{}\n'.format(

Lib/unittest/runner.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
import time
55
import warnings
66

7-
from _colorize import get_theme
8-
97
from . import result
108
from .case import _SubTest
119
from .signals import registerResult
10+
lazy from _colorize import get_theme
1211

1312
__unittest = True
1413

0 commit comments

Comments
 (0)