mirror of
https://github.com/python/cpython.git
synced 2024-11-23 01:45:25 +08:00
gh-117225: Move colorize functionality to own internal module (#118283)
This commit is contained in:
parent
164e2c31c0
commit
3b3f8dea57
64
Lib/_colorize.py
Normal file
64
Lib/_colorize.py
Normal file
@ -0,0 +1,64 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
COLORIZE = True
|
||||
|
||||
|
||||
class ANSIColors:
|
||||
BOLD_GREEN = "\x1b[1;32m"
|
||||
BOLD_MAGENTA = "\x1b[1;35m"
|
||||
BOLD_RED = "\x1b[1;31m"
|
||||
GREEN = "\x1b[32m"
|
||||
GREY = "\x1b[90m"
|
||||
MAGENTA = "\x1b[35m"
|
||||
RED = "\x1b[31m"
|
||||
RESET = "\x1b[0m"
|
||||
YELLOW = "\x1b[33m"
|
||||
|
||||
|
||||
NoColors = ANSIColors()
|
||||
|
||||
for attr in dir(NoColors):
|
||||
if not attr.startswith("__"):
|
||||
setattr(NoColors, attr, "")
|
||||
|
||||
|
||||
def get_colors(colorize: bool = False) -> ANSIColors:
|
||||
if colorize or can_colorize():
|
||||
return ANSIColors()
|
||||
else:
|
||||
return NoColors
|
||||
|
||||
|
||||
def can_colorize() -> bool:
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import nt
|
||||
|
||||
if not nt._supports_virtual_terminal():
|
||||
return False
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
if not sys.flags.ignore_environment:
|
||||
if os.environ.get("PYTHON_COLORS") == "0":
|
||||
return False
|
||||
if os.environ.get("PYTHON_COLORS") == "1":
|
||||
return True
|
||||
if "NO_COLOR" in os.environ:
|
||||
return False
|
||||
if not COLORIZE:
|
||||
return False
|
||||
if not sys.flags.ignore_environment:
|
||||
if "FORCE_COLOR" in os.environ:
|
||||
return True
|
||||
if os.environ.get("TERM") == "dumb":
|
||||
return False
|
||||
|
||||
if not hasattr(sys.stderr, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(sys.stderr.fileno())
|
||||
except io.UnsupportedOperation:
|
||||
return sys.stderr.isatty()
|
@ -104,7 +104,8 @@ import traceback
|
||||
import unittest
|
||||
from io import StringIO, IncrementalNewlineDecoder
|
||||
from collections import namedtuple
|
||||
from traceback import _ANSIColors, _can_colorize
|
||||
import _colorize # Used in doctests
|
||||
from _colorize import ANSIColors, can_colorize
|
||||
|
||||
|
||||
class TestResults(namedtuple('TestResults', 'failed attempted')):
|
||||
@ -1180,8 +1181,8 @@ class DocTestRunner:
|
||||
The `run` method is used to process a single DocTest case. It
|
||||
returns a TestResults instance.
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> tests = DocTestFinder().find(_TestClass)
|
||||
>>> runner = DocTestRunner(verbose=False)
|
||||
@ -1234,7 +1235,7 @@ class DocTestRunner:
|
||||
overriding the methods `report_start`, `report_success`,
|
||||
`report_unexpected_exception`, and `report_failure`.
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
# This divider string is used to separate failure messages, and to
|
||||
# separate sections of the summary.
|
||||
@ -1314,7 +1315,7 @@ class DocTestRunner:
|
||||
|
||||
def _failure_header(self, test, example):
|
||||
red, reset = (
|
||||
(_ANSIColors.RED, _ANSIColors.RESET) if _can_colorize() else ("", "")
|
||||
(ANSIColors.RED, ANSIColors.RESET) if can_colorize() else ("", "")
|
||||
)
|
||||
out = [f"{red}{self.DIVIDER}{reset}"]
|
||||
if test.filename:
|
||||
@ -1556,8 +1557,8 @@ class DocTestRunner:
|
||||
# Make sure sys.displayhook just prints the value to stdout
|
||||
save_displayhook = sys.displayhook
|
||||
sys.displayhook = sys.__displayhook__
|
||||
saved_can_colorize = traceback._can_colorize
|
||||
traceback._can_colorize = lambda: False
|
||||
saved_can_colorize = _colorize.can_colorize
|
||||
_colorize.can_colorize = lambda: False
|
||||
color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None}
|
||||
for key in color_variables:
|
||||
color_variables[key] = os.environ.pop(key, None)
|
||||
@ -1569,7 +1570,7 @@ class DocTestRunner:
|
||||
sys.settrace(save_trace)
|
||||
linecache.getlines = self.save_linecache_getlines
|
||||
sys.displayhook = save_displayhook
|
||||
traceback._can_colorize = saved_can_colorize
|
||||
_colorize.can_colorize = saved_can_colorize
|
||||
for key, value in color_variables.items():
|
||||
if value is not None:
|
||||
os.environ[key] = value
|
||||
@ -1609,20 +1610,13 @@ class DocTestRunner:
|
||||
else:
|
||||
failed.append((name, (failures, tries, skips)))
|
||||
|
||||
if _can_colorize():
|
||||
bold_green = _ANSIColors.BOLD_GREEN
|
||||
bold_red = _ANSIColors.BOLD_RED
|
||||
green = _ANSIColors.GREEN
|
||||
red = _ANSIColors.RED
|
||||
reset = _ANSIColors.RESET
|
||||
yellow = _ANSIColors.YELLOW
|
||||
else:
|
||||
bold_green = ""
|
||||
bold_red = ""
|
||||
green = ""
|
||||
red = ""
|
||||
reset = ""
|
||||
yellow = ""
|
||||
ansi = _colorize.get_colors()
|
||||
bold_green = ansi.BOLD_GREEN
|
||||
bold_red = ansi.BOLD_RED
|
||||
green = ansi.GREEN
|
||||
red = ansi.RED
|
||||
reset = ansi.RESET
|
||||
yellow = ansi.YELLOW
|
||||
|
||||
if verbose:
|
||||
if notests:
|
||||
|
@ -2579,20 +2579,21 @@ def copy_python_src_ignore(path, names):
|
||||
}
|
||||
return ignored
|
||||
|
||||
|
||||
def force_not_colorized(func):
|
||||
"""Force the terminal not to be colorized."""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
import traceback
|
||||
original_fn = traceback._can_colorize
|
||||
import _colorize
|
||||
original_fn = _colorize.can_colorize
|
||||
variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None}
|
||||
try:
|
||||
for key in variables:
|
||||
variables[key] = os.environ.pop(key, None)
|
||||
traceback._can_colorize = lambda: False
|
||||
_colorize.can_colorize = lambda: False
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
traceback._can_colorize = original_fn
|
||||
_colorize.can_colorize = original_fn
|
||||
for key, value in variables.items():
|
||||
if value is not None:
|
||||
os.environ[key] = value
|
||||
|
59
Lib/test/test__colorize.py
Normal file
59
Lib/test/test__colorize.py
Normal file
@ -0,0 +1,59 @@
|
||||
import contextlib
|
||||
import sys
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import _colorize
|
||||
from test.support import force_not_colorized
|
||||
|
||||
ORIGINAL_CAN_COLORIZE = _colorize.can_colorize
|
||||
|
||||
|
||||
def setUpModule():
|
||||
_colorize.can_colorize = lambda: False
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
_colorize.can_colorize = ORIGINAL_CAN_COLORIZE
|
||||
|
||||
|
||||
class TestColorizeFunction(unittest.TestCase):
|
||||
@force_not_colorized
|
||||
def test_colorized_detection_checks_for_environment_variables(self):
|
||||
if sys.platform == "win32":
|
||||
virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal",
|
||||
return_value=True)
|
||||
else:
|
||||
virtual_patching = contextlib.nullcontext()
|
||||
with virtual_patching:
|
||||
|
||||
flags = unittest.mock.MagicMock(ignore_environment=False)
|
||||
with (unittest.mock.patch("os.isatty") as isatty_mock,
|
||||
unittest.mock.patch("sys.flags", flags),
|
||||
unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)):
|
||||
isatty_mock.return_value = True
|
||||
with unittest.mock.patch("os.environ", {'TERM': 'dumb'}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}):
|
||||
self.assertEqual(_colorize.can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ",
|
||||
{'NO_COLOR': '1', "PYTHON_COLORS": '1'}):
|
||||
self.assertEqual(_colorize.can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}):
|
||||
self.assertEqual(_colorize.can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ",
|
||||
{'FORCE_COLOR': '1', 'NO_COLOR': '1'}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ",
|
||||
{'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
isatty_mock.return_value = False
|
||||
with unittest.mock.patch("os.environ", {}):
|
||||
self.assertEqual(_colorize.can_colorize(), False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -16,7 +16,7 @@ import unittest
|
||||
import tempfile
|
||||
import types
|
||||
import contextlib
|
||||
import traceback
|
||||
import _colorize
|
||||
|
||||
|
||||
def doctest_skip_if(condition):
|
||||
@ -893,8 +893,8 @@ Unit tests for the `DocTestRunner` class.
|
||||
DocTestRunner is used to run DocTest test cases, and to accumulate
|
||||
statistics. Here's a simple DocTest case we can use:
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> def f(x):
|
||||
... '''
|
||||
@ -951,7 +951,7 @@ the failure and proceeds to the next example:
|
||||
ok
|
||||
TestResults(failed=1, attempted=3)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
def verbose_flag(): r"""
|
||||
The `verbose` flag makes the test runner generate more detailed
|
||||
@ -1027,8 +1027,8 @@ An expected exception is specified with a traceback message. The
|
||||
lines between the first line and the type/value may be omitted or
|
||||
replaced with any other string:
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> def f(x):
|
||||
... '''
|
||||
@ -1261,7 +1261,7 @@ unexpected exception:
|
||||
ZeroDivisionError: integer division or modulo by zero
|
||||
TestResults(failed=1, attempted=1)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
def displayhook(): r"""
|
||||
Test that changing sys.displayhook doesn't matter for doctest.
|
||||
@ -1303,8 +1303,8 @@ together).
|
||||
The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False
|
||||
and 1/0:
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> def f(x):
|
||||
... '>>> True\n1\n'
|
||||
@ -1725,7 +1725,7 @@ more than one flag value. Here we verify that's fixed:
|
||||
|
||||
Clean up.
|
||||
>>> del doctest.OPTIONFLAGS_BY_NAME[unlikely]
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
|
||||
"""
|
||||
|
||||
@ -1736,8 +1736,8 @@ Option directives can be used to turn option flags on or off for a
|
||||
single example. To turn an option on for an example, follow that
|
||||
example with a comment of the form ``# doctest: +OPTION``:
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> def f(x): r'''
|
||||
... >>> print(list(range(10))) # should fail: no ellipsis
|
||||
@ -1947,7 +1947,7 @@ source:
|
||||
Traceback (most recent call last):
|
||||
ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS'
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
|
||||
def test_testsource(): r"""
|
||||
@ -2031,8 +2031,8 @@ if not hasattr(sys, 'gettrace') or not sys.gettrace():
|
||||
with a version that restores stdout. This is necessary for you to
|
||||
see debugger output.
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> doc = '''
|
||||
... >>> x = 42
|
||||
@ -2157,7 +2157,7 @@ if not hasattr(sys, 'gettrace') or not sys.gettrace():
|
||||
9
|
||||
TestResults(failed=1, attempted=3)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
|
||||
def test_pdb_set_trace_nested():
|
||||
@ -2694,8 +2694,8 @@ calling module. The return value is (#failures, #tests).
|
||||
|
||||
We don't want color or `-v` in sys.argv for these tests.
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> save_argv = sys.argv
|
||||
>>> if '-v' in sys.argv:
|
||||
@ -2863,7 +2863,7 @@ Test the verbose output:
|
||||
TestResults(failed=0, attempted=2)
|
||||
>>> doctest.master = None # Reset master.
|
||||
>>> sys.argv = save_argv
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
|
||||
class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader):
|
||||
@ -3001,8 +3001,8 @@ if supports_unicode:
|
||||
def test_unicode(): """
|
||||
Check doctest with a non-ascii filename:
|
||||
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> doc = '''
|
||||
... >>> raise Exception('clé')
|
||||
@ -3030,7 +3030,7 @@ Check doctest with a non-ascii filename:
|
||||
Exception: clé
|
||||
TestResults(failed=1, attempted=1)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
|
||||
|
||||
@ -3325,8 +3325,8 @@ def test_run_doctestsuite_multiple_times():
|
||||
|
||||
def test_exception_with_note(note):
|
||||
"""
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> test_exception_with_note('Note')
|
||||
Traceback (most recent call last):
|
||||
@ -3378,7 +3378,7 @@ def test_exception_with_note(note):
|
||||
note
|
||||
TestResults(failed=1, attempted=...)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
exc = ValueError('Text')
|
||||
exc.add_note(note)
|
||||
@ -3459,8 +3459,8 @@ def test_syntax_error_subclass_from_stdlib():
|
||||
|
||||
def test_syntax_error_with_incorrect_expected_note():
|
||||
"""
|
||||
>>> save_colorize = traceback._COLORIZE
|
||||
>>> traceback._COLORIZE = False
|
||||
>>> save_colorize = _colorize.COLORIZE
|
||||
>>> _colorize.COLORIZE = False
|
||||
|
||||
>>> def f(x):
|
||||
... r'''
|
||||
@ -3491,7 +3491,7 @@ def test_syntax_error_with_incorrect_expected_note():
|
||||
note2
|
||||
TestResults(failed=1, attempted=...)
|
||||
|
||||
>>> traceback._COLORIZE = save_colorize
|
||||
>>> _colorize.COLORIZE = save_colorize
|
||||
"""
|
||||
|
||||
|
||||
|
@ -26,9 +26,9 @@ from test.support import force_not_colorized
|
||||
import json
|
||||
import textwrap
|
||||
import traceback
|
||||
import contextlib
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
import _colorize
|
||||
|
||||
MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else ''
|
||||
|
||||
@ -40,25 +40,18 @@ test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
|
||||
|
||||
LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'
|
||||
|
||||
ORIGINAL_CAN_COLORIZE = traceback._can_colorize
|
||||
|
||||
def setUpModule():
|
||||
traceback._can_colorize = lambda: False
|
||||
|
||||
def tearDownModule():
|
||||
traceback._can_colorize = ORIGINAL_CAN_COLORIZE
|
||||
|
||||
class TracebackCases(unittest.TestCase):
|
||||
# For now, a very minimal set of tests. I want to be sure that
|
||||
# formatting of SyntaxErrors works based on changes for 2.1.
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.colorize = traceback._COLORIZE
|
||||
traceback._COLORIZE = False
|
||||
self.colorize = _colorize.COLORIZE
|
||||
_colorize.COLORIZE = False
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
traceback._COLORIZE = self.colorize
|
||||
_colorize.COLORIZE = self.colorize
|
||||
|
||||
def get_exception_format(self, func, exc):
|
||||
try:
|
||||
@ -4478,9 +4471,9 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
e, capture_locals=True
|
||||
)
|
||||
lines = "".join(exc.format(colorize=True))
|
||||
red = traceback._ANSIColors.RED
|
||||
boldr = traceback._ANSIColors.BOLD_RED
|
||||
reset = traceback._ANSIColors.RESET
|
||||
red = _colorize.ANSIColors.RED
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
|
||||
self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
|
||||
self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
|
||||
@ -4496,11 +4489,11 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
e, capture_locals=True
|
||||
)
|
||||
actual = "".join(exc.format(colorize=True))
|
||||
red = traceback._ANSIColors.RED
|
||||
magenta = traceback._ANSIColors.MAGENTA
|
||||
boldm = traceback._ANSIColors.BOLD_MAGENTA
|
||||
boldr = traceback._ANSIColors.BOLD_RED
|
||||
reset = traceback._ANSIColors.RESET
|
||||
red = _colorize.ANSIColors.RED
|
||||
magenta = _colorize.ANSIColors.MAGENTA
|
||||
boldm = _colorize.ANSIColors.BOLD_MAGENTA
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
expected = "".join([
|
||||
f' File {magenta}"<string>"{reset}, line {magenta}1{reset}\n',
|
||||
f' a {boldr}${reset} b\n',
|
||||
@ -4519,15 +4512,15 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
self.fail("No exception thrown.")
|
||||
except Exception as e:
|
||||
with captured_output("stderr") as tbstderr:
|
||||
with unittest.mock.patch('traceback._can_colorize', return_value=True):
|
||||
with unittest.mock.patch('_colorize.can_colorize', return_value=True):
|
||||
exception_print(e)
|
||||
actual = tbstderr.getvalue().splitlines()
|
||||
|
||||
red = traceback._ANSIColors.RED
|
||||
boldr = traceback._ANSIColors.BOLD_RED
|
||||
magenta = traceback._ANSIColors.MAGENTA
|
||||
boldm = traceback._ANSIColors.BOLD_MAGENTA
|
||||
reset = traceback._ANSIColors.RESET
|
||||
red = _colorize.ANSIColors.RED
|
||||
boldr = _colorize.ANSIColors.BOLD_RED
|
||||
magenta = _colorize.ANSIColors.MAGENTA
|
||||
boldm = _colorize.ANSIColors.BOLD_MAGENTA
|
||||
reset = _colorize.ANSIColors.RESET
|
||||
lno_foo = foo.__code__.co_firstlineno
|
||||
expected = ['Traceback (most recent call last):',
|
||||
f' File {magenta}"{__file__}"{reset}, '
|
||||
@ -4541,38 +4534,6 @@ class TestColorizedTraceback(unittest.TestCase):
|
||||
f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@force_not_colorized
|
||||
def test_colorized_detection_checks_for_environment_variables(self):
|
||||
if sys.platform == "win32":
|
||||
virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", return_value=True)
|
||||
else:
|
||||
virtual_patching = contextlib.nullcontext()
|
||||
with virtual_patching:
|
||||
|
||||
flags = unittest.mock.MagicMock(ignore_environment=False)
|
||||
with (unittest.mock.patch("os.isatty") as isatty_mock,
|
||||
unittest.mock.patch("sys.flags", flags),
|
||||
unittest.mock.patch("traceback._can_colorize", ORIGINAL_CAN_COLORIZE)):
|
||||
isatty_mock.return_value = True
|
||||
with unittest.mock.patch("os.environ", {'TERM': 'dumb'}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}):
|
||||
self.assertEqual(traceback._can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}):
|
||||
self.assertEqual(traceback._can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}):
|
||||
self.assertEqual(traceback._can_colorize(), True)
|
||||
with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
isatty_mock.return_value = False
|
||||
with unittest.mock.patch("os.environ", {}):
|
||||
self.assertEqual(traceback._can_colorize(), False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""Extract, format and print information about Python stack traces."""
|
||||
|
||||
import os
|
||||
import io
|
||||
import collections.abc
|
||||
import itertools
|
||||
import linecache
|
||||
@ -9,6 +7,8 @@ import sys
|
||||
import textwrap
|
||||
import warnings
|
||||
from contextlib import suppress
|
||||
import _colorize
|
||||
from _colorize import ANSIColors
|
||||
|
||||
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||
'format_exception_only', 'format_list', 'format_stack',
|
||||
@ -21,7 +21,6 @@ __all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||
# Formatting and printing lists of traceback lines.
|
||||
#
|
||||
|
||||
_COLORIZE = True
|
||||
|
||||
def print_list(extracted_list, file=None):
|
||||
"""Print the list of tuples as returned by extract_tb() or
|
||||
@ -133,41 +132,10 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
|
||||
|
||||
BUILTIN_EXCEPTION_LIMIT = object()
|
||||
|
||||
def _can_colorize():
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import nt
|
||||
if not nt._supports_virtual_terminal():
|
||||
return False
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
if not sys.flags.ignore_environment:
|
||||
if os.environ.get("PYTHON_COLORS") == "0":
|
||||
return False
|
||||
if os.environ.get("PYTHON_COLORS") == "1":
|
||||
return True
|
||||
if "NO_COLOR" in os.environ:
|
||||
return False
|
||||
if not _COLORIZE:
|
||||
return False
|
||||
if not sys.flags.ignore_environment:
|
||||
if "FORCE_COLOR" in os.environ:
|
||||
return True
|
||||
if os.environ.get("TERM") == "dumb":
|
||||
return False
|
||||
|
||||
if not hasattr(sys.stderr, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
return os.isatty(sys.stderr.fileno())
|
||||
except io.UnsupportedOperation:
|
||||
return sys.stderr.isatty()
|
||||
|
||||
|
||||
def _print_exception_bltin(exc, /):
|
||||
file = sys.stderr if sys.stderr is not None else sys.__stderr__
|
||||
colorize = _can_colorize()
|
||||
colorize = _colorize.can_colorize()
|
||||
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
|
||||
|
||||
|
||||
@ -214,9 +182,9 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
|
||||
end_char = "\n" if insert_final_newline else ""
|
||||
if colorize:
|
||||
if value is None or not valuestr:
|
||||
line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}{end_char}"
|
||||
line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}"
|
||||
else:
|
||||
line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}: {_ANSIColors.MAGENTA}{valuestr}{_ANSIColors.RESET}{end_char}"
|
||||
line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}"
|
||||
else:
|
||||
if value is None or not valuestr:
|
||||
line = f"{etype}{end_char}"
|
||||
@ -224,6 +192,7 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
|
||||
line = f"{etype}: {valuestr}{end_char}"
|
||||
return line
|
||||
|
||||
|
||||
def _safe_string(value, what, func=str):
|
||||
try:
|
||||
return func(value)
|
||||
@ -449,17 +418,6 @@ def _get_code_position(code, instruction_index):
|
||||
|
||||
_RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c.
|
||||
|
||||
class _ANSIColors:
|
||||
RED = '\x1b[31m'
|
||||
BOLD_RED = '\x1b[1;31m'
|
||||
MAGENTA = '\x1b[35m'
|
||||
BOLD_MAGENTA = '\x1b[1;35m'
|
||||
GREEN = "\x1b[32m"
|
||||
BOLD_GREEN = "\x1b[1;32m"
|
||||
GREY = '\x1b[90m'
|
||||
RESET = '\x1b[0m'
|
||||
YELLOW = "\x1b[33m"
|
||||
|
||||
|
||||
class StackSummary(list):
|
||||
"""A list of FrameSummary objects, representing a stack of frames."""
|
||||
@ -564,15 +522,15 @@ class StackSummary(list):
|
||||
filename = "<stdin>"
|
||||
if colorize:
|
||||
row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.MAGENTA,
|
||||
filename,
|
||||
_ANSIColors.RESET,
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
frame_summary.lineno,
|
||||
_ANSIColors.RESET,
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
frame_summary.name,
|
||||
_ANSIColors.RESET,
|
||||
ANSIColors.RESET,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -696,11 +654,11 @@ class StackSummary(list):
|
||||
for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]):
|
||||
caret_group = list(group)
|
||||
if color == "^":
|
||||
colorized_line_parts.append(_ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET)
|
||||
colorized_carets_parts.append(_ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET)
|
||||
colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
|
||||
colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
|
||||
elif color == "~":
|
||||
colorized_line_parts.append(_ANSIColors.RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET)
|
||||
colorized_carets_parts.append(_ANSIColors.RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET)
|
||||
colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
|
||||
colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
|
||||
else:
|
||||
colorized_line_parts.append("".join(char for char, _ in caret_group))
|
||||
colorized_carets_parts.append("".join(caret for _, caret in caret_group))
|
||||
@ -1307,12 +1265,12 @@ class TracebackException:
|
||||
if self.lineno is not None:
|
||||
if colorize:
|
||||
yield ' File {}"{}"{}, line {}{}{}\n'.format(
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.MAGENTA,
|
||||
self.filename or "<string>",
|
||||
_ANSIColors.RESET,
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
self.lineno,
|
||||
_ANSIColors.RESET,
|
||||
ANSIColors.RESET,
|
||||
)
|
||||
else:
|
||||
yield ' File "{}", line {}\n'.format(
|
||||
@ -1352,11 +1310,11 @@ class TracebackException:
|
||||
# colorize from colno to end_colno
|
||||
ltext = (
|
||||
ltext[:colno] +
|
||||
_ANSIColors.BOLD_RED + ltext[colno:end_colno] + _ANSIColors.RESET +
|
||||
ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET +
|
||||
ltext[end_colno:]
|
||||
)
|
||||
start_color = _ANSIColors.BOLD_RED
|
||||
end_color = _ANSIColors.RESET
|
||||
start_color = ANSIColors.BOLD_RED
|
||||
end_color = ANSIColors.RESET
|
||||
yield ' {}\n'.format(ltext)
|
||||
yield ' {}{}{}{}\n'.format(
|
||||
"".join(caretspace),
|
||||
@ -1369,12 +1327,12 @@ class TracebackException:
|
||||
msg = self.msg or "<no detail available>"
|
||||
if colorize:
|
||||
yield "{}{}{}: {}{}{}{}\n".format(
|
||||
_ANSIColors.BOLD_MAGENTA,
|
||||
ANSIColors.BOLD_MAGENTA,
|
||||
stype,
|
||||
_ANSIColors.RESET,
|
||||
_ANSIColors.MAGENTA,
|
||||
ANSIColors.RESET,
|
||||
ANSIColors.MAGENTA,
|
||||
msg,
|
||||
_ANSIColors.RESET,
|
||||
ANSIColors.RESET,
|
||||
filename_suffix)
|
||||
else:
|
||||
yield "{}: {}{}\n".format(stype, msg, filename_suffix)
|
||||
|
1
Python/stdlib_module_names.h
generated
1
Python/stdlib_module_names.h
generated
@ -20,6 +20,7 @@ static const char* _Py_stdlib_module_names[] = {
|
||||
"_codecs_tw",
|
||||
"_collections",
|
||||
"_collections_abc",
|
||||
"_colorize",
|
||||
"_compat_pickle",
|
||||
"_compression",
|
||||
"_contextvars",
|
||||
|
Loading…
Reference in New Issue
Block a user