Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-133346: Make theming support in _colorize extensible#133347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ambv merged 21 commits intopython:mainfromambv:colorize-theme-support
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
21 commits
Select commitHold shift + click to select a range
3344008
gh-133346: Make theming support in _colorize extensible
ambvMay 3, 2025
68e2385
Fix lint
ambvMay 3, 2025
252dfa0
Add theming to traceback.py
ambvMay 3, 2025
b29c9b9
Fix WASI test failures
ambvMay 3, 2025
ca34939
Apply suggestions from code review
ambvMay 3, 2025
5c5c3e1
Add theming to unittest
hugovkMay 3, 2025
f449e7b
Add missing plumbing for _colorize.Unittest
ambvMay 4, 2025
8da314f
Adapt tests to theming when executed with -j
ambvMay 4, 2025
65d3a78
Fix. lint.
ambvMay 4, 2025
0866fd8
Add blurb and some docstrings
ambvMay 4, 2025
965c7cf
FIX. LINT.
ambvMay 4, 2025
230d658
Add theming to argparse
hugovkMay 4, 2025
c4808c8
Apply suggestions from code review
ambvMay 4, 2025
fd9c85c
Update test_argparse to use theme not color and fix bug
hugovkMay 4, 2025
664ef14
Merge branch 'main' into colorize-theme-support
ambvMay 5, 2025
e67fda9
Rename REPL to Syntax now that pdb is using it, too; adapt pdb tests
ambvMay 5, 2025
70ca161
Bring json.tool into the theming fold
ambvMay 5, 2025
3166d63
Merge branch 'main' into colorize-theme-support
ambvMay 5, 2025
5c45ddb
Make no_colors() into classmethods
ambvMay 5, 2025
68a5c44
Address review
ambvMay 5, 2025
9f51769
Group effort
ambvMay 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
Add theming to traceback.py
  • Loading branch information
@ambv
ambv committedMay 3, 2025
commit252dfa059f2a8784b4a802198ae8a4d83bf5b31a
24 changes: 22 additions & 2 deletionsLib/_colorize.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -122,18 +122,38 @@ class REPL(ThemeSection):
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Traceback(ThemeSection):
type: str = ANSIColors.BOLD_MAGENTA
message: str = ANSIColors.MAGENTA
filename: str = ANSIColors.MAGENTA
line_no: str = ANSIColors.MAGENTA
frame: str = ANSIColors.MAGENTA
error_highlight: str = ANSIColors.BOLD_RED
error_range: str = ANSIColors.RED
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Theme:
repl: REPL = field(default_factory=REPL)

def copy_with(self, *, repl: REPL | None) -> Self:
traceback: Traceback = field(default_factory=Traceback)

def copy_with(
self,
*,
repl: REPL | None = None,
traceback: Traceback | None = None,
) -> Self:
return type(self)(
repl=repl or self.repl,
traceback=traceback or self.traceback,
)

def no_colors(self) -> Self:
return type(self)(
repl=self.repl.no_colors(),
traceback=self.traceback.no_colors(),
)


Expand Down
7 changes: 4 additions & 3 deletionsLib/asyncio/__main__.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@
import types
import warnings

from _colorize importcan_colorize, ANSIColors # type: ignore[import-not-found]
from _colorize importget_theme
from _pyrepl.console import InteractiveColoredConsole

from . import futures
Expand DownExpand Up@@ -101,8 +101,9 @@ def run(self):
exec(startup_code, console.locals)

ps1 = getattr(sys, "ps1", ">>> ")
if can_colorize() and CAN_USE_PYREPL:
ps1 = f"{ANSIColors.BOLD_MAGENTA}{ps1}{ANSIColors.RESET}"
if CAN_USE_PYREPL:
theme = get_theme().repl
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
console.write(f"{ps1}import asyncio\n")

if CAN_USE_PYREPL:
Expand Down
4 changes: 2 additions & 2 deletionsLib/test/test_pyrepl/test_reader.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,11 +11,11 @@
from .support import reader_no_colors as prepare_reader
from _pyrepl.console import Event
from _pyrepl.reader import Reader
from _colorize importget_theme
from _colorize importdefault_theme


overrides = {"reset": "z", "soft_keyword": "K"}
colors = {overrides.get(k, k[0].lower()): v for k, v inget_theme().repl.items()}
colors = {overrides.get(k, k[0].lower()): v for k, v indefault_theme.repl.items()}


class TestReader(ScreenEqualMixin, TestCase):
Expand Down
114 changes: 58 additions & 56 deletionsLib/test/test_traceback.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,12 @@
test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])

color_overrides = {"reset": "z", "filename": "fn", "error_highlight": "E"}
colors = {
color_overrides.get(k, k[0].lower()): v
for k, v in _colorize.default_theme.traceback.items()
}


LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'

Expand DownExpand Up@@ -4721,6 +4727,8 @@ class MyList(list):


class TestColorizedTraceback(unittest.TestCase):
maxDiff = None

def test_colorized_traceback(self):
def foo(*args):
x = {'a':{'b': None}}
Expand All@@ -4743,9 +4751,9 @@ def bar():
e, capture_locals=True
)
lines = "".join(exc.format(colorize=True))
red =_colorize.ANSIColors.RED
boldr =_colorize.ANSIColors.BOLD_RED
reset =_colorize.ANSIColors.RESET
red =colors["e"]
boldr =colors["E"]
reset =colors["z"]
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)
Expand All@@ -4761,18 +4769,16 @@ def test_colorized_syntax_error(self):
e, capture_locals=True
)
actual = "".join(exc.format(colorize=True))
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',
f' {boldr}^{reset}\n',
f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n']
)
self.assertIn(expected, actual)
def expected(t, m, fn, l, f, E, e, z):
return "".join(
[
f' File {fn}"<string>"{z}, line {l}1{z}\n',
f' a {E}${z} b\n',
f' {E}^{z}\n',
f'{t}SyntaxError{z}: {m}invalid syntax{z}\n'
]
)
self.assertIn(expected(**colors), actual)

def test_colorized_traceback_is_the_default(self):
def foo():
Expand All@@ -4788,23 +4794,21 @@ def foo():
exception_print(e)
actual = tbstderr.getvalue().splitlines()

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}, '
f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}',
f' {red}foo{reset+boldr}(){reset}',
f' {red}~~~{reset+boldr}^^{reset}',
f' File {magenta}"{__file__}"{reset}, '
f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}',
f' {red}1{reset+boldr}/{reset+red}0{reset}',
f' {red}~{reset+boldr}^{reset+red}~{reset}',
f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
self.assertEqual(actual, expected)
def expected(t, m, fn, l, f, E, e, z):
return [
'Traceback (most recent call last):',
f' File {fn}"{__file__}"{z}, '
f'line {l}{lno_foo+5}{z}, in {f}test_colorized_traceback_is_the_default{z}',
f' {e}foo{z}{E}(){z}',
f' {e}~~~{z}{E}^^{z}',
f' File {fn}"{__file__}"{z}, '
f'line {l}{lno_foo+1}{z}, in {f}foo{z}',
f' {e}1{z}{E}/{z}{e}0{z}',
f' {e}~{z}{E}^{z}{e}~{z}',
f'{t}ZeroDivisionError{z}: {m}division by zero{z}',
]
self.assertEqual(actual, expected(**colors))

def test_colorized_traceback_from_exception_group(self):
def foo():
Expand All@@ -4822,33 +4826,31 @@ def foo():
e, capture_locals=True
)

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
actual = "".join(exc.format(colorize=True)).splitlines()
expected = [f" + Exception Group Traceback (most recent call last):",
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}',
f' | {red}foo{reset}{boldr}(){reset}',
f' | {red}~~~{reset}{boldr}^^{reset}',
f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
f" | foo = {foo}",
f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}',
f' | raise ExceptionGroup("test", exceptions)',
f" | exceptions = [ZeroDivisionError('division by zero')]",
f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}',
f' +-+---------------- 1 ----------------',
f' | Traceback (most recent call last):',
f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}',
f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}',
f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}',
f" | exceptions = [ZeroDivisionError('division by zero')]",
f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}',
f' +------------------------------------']
self.assertEqual(actual, expected)
def expected(t, m, fn, l, f, E, e, z):
return [
f" + Exception Group Traceback (most recent call last):",
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+9}{z}, in {f}test_colorized_traceback_from_exception_group{z}',
f' | {e}foo{z}{E}(){z}',
f' | {e}~~~{z}{E}^^{z}',
f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
f" | foo = {foo}",
f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+6}{z}, in {f}foo{z}',
f' | raise ExceptionGroup("test", exceptions)',
f" | exceptions = [ZeroDivisionError('division by zero')]",
f' | {t}ExceptionGroup{z}: {m}test (1 sub-exception){z}',
f' +-+---------------- 1 ----------------',
f' | Traceback (most recent call last):',
f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+3}{z}, in {f}foo{z}',
f' | {e}1 {z}{E}/{z}{e} 0{z}',
f' | {e}~~{z}{E}^{z}{e}~~{z}',
f" | exceptions = [ZeroDivisionError('division by zero')]",
f' | {t}ZeroDivisionError{z}: {m}division by zero{z}',
f' +------------------------------------',
]
self.assertEqual(actual, expected(**colors))

if __name__ == "__main__":
unittest.main()
102 changes: 46 additions & 56 deletionsLib/traceback.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,10 @@
import keyword
import tokenize
import io
from contextlib import suppress
import _colorize
from _colorize import ANSIColors

from contextlib import suppress
from _colorize import get_theme, theme_no_color

__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
Expand DownExpand Up@@ -186,16 +187,11 @@ def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs
def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=False):
valuestr = _safe_string(value, 'exception')
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}"
else:
line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}"
theme = (theme_no_color if not colorize else get_theme()).traceback
if value is None or not valuestr:
line = f"{theme.type}{etype}{theme.reset}{end_char}"
else:
if value is None or not valuestr:
line = f"{etype}{end_char}"
else:
line = f"{etype}: {valuestr}{end_char}"
line = f"{theme.type}{etype}{theme.reset}: {theme.message}{valuestr}{theme.reset}{end_char}"
return line


Expand DownExpand Up@@ -538,22 +534,21 @@ def format_frame_summary(self, frame_summary, **kwargs):
filename = frame_summary.filename
if frame_summary.filename.startswith("<stdin>-"):
filename = "<stdin>"
if colorize:
row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
ANSIColors.MAGENTA,
filename,
ANSIColors.RESET,
ANSIColors.MAGENTA,
frame_summary.lineno,
ANSIColors.RESET,
ANSIColors.MAGENTA,
frame_summary.name,
ANSIColors.RESET,
)

theme = (theme_no_color if not colorize else get_theme()).traceback
row.append(
' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
theme.filename,
filename,
theme.reset,
theme.line_no,
frame_summary.lineno,
theme.reset,
theme.frame,
frame_summary.name,
theme.reset,
)
else:
row.append(' File "{}", line {}, in {}\n'.format(
filename, frame_summary.lineno, frame_summary.name))
)
if frame_summary._dedented_lines and frame_summary._dedented_lines.strip():
if (
frame_summary.colno is None or
Expand DownExpand Up@@ -672,11 +667,11 @@ def output_line(lineno):
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(theme.error_highlight + "".join(char for char, _ in caret_group) +theme.reset)
colorized_carets_parts.append(theme.error_highlight + "".join(caret for _, caret in caret_group) +theme.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(theme.error_range + "".join(char for char, _ in caret_group) +theme.reset)
colorized_carets_parts.append(theme.error_range + "".join(caret for _, caret in caret_group) +theme.reset)
else:
colorized_line_parts.append("".join(char for char, _ in caret_group))
colorized_carets_parts.append("".join(caret for _, caret in caret_group))
Expand DownExpand Up@@ -1378,20 +1373,17 @@ def _format_syntax_error(self, stype, **kwargs):
"""Format SyntaxError exceptions (internal helper)."""
# Show exactly where the problem was found.
colorize = kwargs.get("colorize", False)
theme = (theme_no_color if not colorize else get_theme()).traceback
filename_suffix = ''
if self.lineno is not None:
if colorize:
yield ' File {}"{}"{}, line {}{}{}\n'.format(
ANSIColors.MAGENTA,
self.filename or "<string>",
ANSIColors.RESET,
ANSIColors.MAGENTA,
self.lineno,
ANSIColors.RESET,
)
else:
yield ' File "{}", line {}\n'.format(
self.filename or "<string>", self.lineno)
yield ' File {}"{}"{}, line {}{}{}\n'.format(
theme.filename,
self.filename or "<string>",
theme.reset,
theme.line_no,
self.lineno,
theme.reset,
)
elif self.filename is not None:
filename_suffix = ' ({})'.format(self.filename)

Expand DownExpand Up@@ -1441,11 +1433,11 @@ def _format_syntax_error(self, stype, **kwargs):
# colorize from colno to end_colno
ltext = (
ltext[:colno] +
ANSIColors.BOLD_RED + ltext[colno:end_colno] +ANSIColors.RESET +
theme.error_highlight + ltext[colno:end_colno] +theme.reset +
ltext[end_colno:]
)
start_color =ANSIColors.BOLD_RED
end_color =ANSIColors.RESET
start_color =theme.error_highlight
end_color =theme.reset
yield ' {}\n'.format(ltext)
yield ' {}{}{}{}\n'.format(
"".join(caretspace),
Expand All@@ -1456,17 +1448,15 @@ def _format_syntax_error(self, stype, **kwargs):
else:
yield ' {}\n'.format(ltext)
msg = self.msg or "<no detail available>"
if colorize:
yield "{}{}{}: {}{}{}{}\n".format(
ANSIColors.BOLD_MAGENTA,
stype,
ANSIColors.RESET,
ANSIColors.MAGENTA,
msg,
ANSIColors.RESET,
filename_suffix)
else:
yield "{}: {}{}\n".format(stype, msg, filename_suffix)
yield "{}{}{}: {}{}{}{}\n".format(
theme.type,
stype,
theme.reset,
theme.message,
msg,
theme.reset,
filename_suffix,
)

def format(self, *, chain=True, _ctx=None, **kwargs):
"""Format the exception.
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp