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

Commit16448ca

Browse files
pablogsalambv
andauthored
gh-112730: Use color to highlight error locations (gh-112732)
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent3870d19 commit16448ca

File tree

8 files changed

+369
-40
lines changed

8 files changed

+369
-40
lines changed

‎Doc/using/cmdline.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,27 @@ Miscellaneous options
612612
..versionadded::3.13
613613
The ``-X presite`` option.
614614

615+
Controlling Color
616+
~~~~~~~~~~~~~~~~~
617+
618+
The Python interpreter is configured by default to use colors to highlight
619+
output in certain situations such as when displaying tracebacks. This
620+
behavior can be controlled by setting different environment variables.
621+
622+
Setting the environment variable ``TERM`` to ``dumb`` will disable color.
623+
624+
If the environment variable ``FORCE_COLOR`` is set, then color will be
625+
enabled regardless of the value of TERM. This is useful on CI systems which
626+
aren’t terminals but can none-the-less display ANSI escape sequences.
627+
628+
If the environment variable ``NO_COLOR`` is set, Python will disable all color
629+
in the output. This takes precedence over ``FORCE_COLOR``.
630+
631+
All these environment variables are used also by other tools to control color
632+
output. To control the color output only in the Python interpreter, the
633+
:envvar:`PYTHON_COLORS` environment variable can be used. This variable takes
634+
precedence over ``NO_COLOR``, which in turn takes precedence over
635+
``FORCE_COLOR``.
615636

616637
Options you shouldn't use
617638
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1110,6 +1131,12 @@ conflict.
11101131

11111132
..versionadded::3.13
11121133

1134+
..envvar::PYTHON_COLORS
1135+
1136+
If this variable is set to ``1``, the interpreter will colorize various kinds
1137+
of output. Setting it to ``0`` deactivates this behavior.
1138+
1139+
..versionadded::3.13
11131140

11141141
Debug-mode variables
11151142
~~~~~~~~~~~~~~~~~~~~

‎Doc/whatsnew/3.13.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ Important deprecations, removals or restrictions:
8585
New Features
8686
============
8787

88+
Improved Error Messages
89+
-----------------------
8890

91+
* The interpreter now colorizes error messages when displaying tracebacks by default.
92+
This feature can be controlled via the new:envvar:`PYTHON_COLORS` environment
93+
variable as well as the canonical ``NO_COLOR`` and ``FORCE_COLOR`` environment
94+
variables. (Contributed by Pablo Galindo Salgado in:gh:`112730`.)
8995

9096
Other Language Changes
9197
======================

‎Lib/test/test_traceback.py

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
importinspect
99
importbuiltins
1010
importunittest
11+
importunittest.mock
1112
importre
1213
importtempfile
1314
importrandom
@@ -24,6 +25,7 @@
2425
importjson
2526
importtextwrap
2627
importtraceback
28+
importcontextlib
2729
fromfunctoolsimportpartial
2830
frompathlibimportPath
2931

@@ -41,6 +43,14 @@
4143
classTracebackCases(unittest.TestCase):
4244
# For now, a very minimal set of tests. I want to be sure that
4345
# formatting of SyntaxErrors works based on changes for 2.1.
46+
defsetUp(self):
47+
super().setUp()
48+
self.colorize=traceback._COLORIZE
49+
traceback._COLORIZE=False
50+
51+
deftearDown(self):
52+
super().tearDown()
53+
traceback._COLORIZE=self.colorize
4454

4555
defget_exception_format(self,func,exc):
4656
try:
@@ -521,7 +531,7 @@ def test_signatures(self):
521531
self.assertEqual(
522532
str(inspect.signature(traceback.print_exception)),
523533
('(exc, /, value=<implicit>, tb=<implicit>, '
524-
'limit=None, file=None, chain=True)'))
534+
'limit=None, file=None, chain=True, **kwargs)'))
525535

526536
self.assertEqual(
527537
str(inspect.signature(traceback.format_exception)),
@@ -3031,7 +3041,7 @@ def some_inner(k, v):
30313041

30323042
deftest_custom_format_frame(self):
30333043
classCustomStackSummary(traceback.StackSummary):
3034-
defformat_frame_summary(self,frame_summary):
3044+
defformat_frame_summary(self,frame_summary,colorize=False):
30353045
returnf'{frame_summary.filename}:{frame_summary.lineno}'
30363046

30373047
defsome_inner():
@@ -3056,7 +3066,7 @@ def g():
30563066
tb=g()
30573067

30583068
classSkip_G(traceback.StackSummary):
3059-
defformat_frame_summary(self,frame_summary):
3069+
defformat_frame_summary(self,frame_summary,colorize=False):
30603070
ifframe_summary.name=='g':
30613071
returnNone
30623072
returnsuper().format_frame_summary(frame_summary)
@@ -3076,7 +3086,6 @@ def __repr__(self) -> str:
30763086
raiseException("Unrepresentable")
30773087

30783088
classTestTracebackException(unittest.TestCase):
3079-
30803089
defdo_test_smoke(self,exc,expected_type_str):
30813090
try:
30823091
raiseexc
@@ -4245,6 +4254,115 @@ def test_levenshtein_distance_short_circuit(self):
42454254
res3=traceback._levenshtein_distance(a,b,threshold)
42464255
self.assertGreater(res3,threshold,msg=(a,b,threshold))
42474256

4257+
classTestColorizedTraceback(unittest.TestCase):
4258+
deftest_colorized_traceback(self):
4259+
deffoo(*args):
4260+
x= {'a':{'b':None}}
4261+
y=x['a']['b']['c']
4262+
4263+
defbaz(*args):
4264+
returnfoo(1,2,3,4)
4265+
4266+
defbar():
4267+
returnbaz(1,
4268+
2,3
4269+
,4)
4270+
try:
4271+
bar()
4272+
exceptExceptionase:
4273+
exc=traceback.TracebackException.from_exception(
4274+
e,capture_locals=True
4275+
)
4276+
lines="".join(exc.format(colorize=True))
4277+
red=traceback._ANSIColors.RED
4278+
boldr=traceback._ANSIColors.BOLD_RED
4279+
reset=traceback._ANSIColors.RESET
4280+
self.assertIn("y = "+red+"x['a']['b']"+reset+boldr+"['c']"+reset,lines)
4281+
self.assertIn("return "+red+"foo"+reset+boldr+"(1,2,3,4)"+reset,lines)
4282+
self.assertIn("return "+red+"baz"+reset+boldr+"(1,"+reset,lines)
4283+
self.assertIn(boldr+"2,3"+reset,lines)
4284+
self.assertIn(boldr+",4)"+reset,lines)
4285+
self.assertIn(red+"bar"+reset+boldr+"()"+reset,lines)
4286+
4287+
deftest_colorized_syntax_error(self):
4288+
try:
4289+
compile("a $ b","<string>","exec")
4290+
exceptSyntaxErrorase:
4291+
exc=traceback.TracebackException.from_exception(
4292+
e,capture_locals=True
4293+
)
4294+
actual="".join(exc.format(colorize=True))
4295+
red=traceback._ANSIColors.RED
4296+
magenta=traceback._ANSIColors.MAGENTA
4297+
boldm=traceback._ANSIColors.BOLD_MAGENTA
4298+
boldr=traceback._ANSIColors.BOLD_RED
4299+
reset=traceback._ANSIColors.RESET
4300+
expected="".join([
4301+
f' File{magenta}"<string>"{reset}, line{magenta}1{reset}\n',
4302+
f' a{boldr}${reset} b\n',
4303+
f'{boldr}^{reset}\n',
4304+
f'{boldm}SyntaxError{reset}:{magenta}invalid syntax{reset}\n']
4305+
)
4306+
self.assertIn(expected,actual)
4307+
4308+
deftest_colorized_traceback_is_the_default(self):
4309+
deffoo():
4310+
1/0
4311+
4312+
from_testcapiimportexception_print
4313+
try:
4314+
foo()
4315+
self.fail("No exception thrown.")
4316+
exceptExceptionase:
4317+
withcaptured_output("stderr")astbstderr:
4318+
withunittest.mock.patch('traceback._can_colorize',return_value=True):
4319+
exception_print(e)
4320+
actual=tbstderr.getvalue().splitlines()
4321+
4322+
red=traceback._ANSIColors.RED
4323+
boldr=traceback._ANSIColors.BOLD_RED
4324+
magenta=traceback._ANSIColors.MAGENTA
4325+
boldm=traceback._ANSIColors.BOLD_MAGENTA
4326+
reset=traceback._ANSIColors.RESET
4327+
lno_foo=foo.__code__.co_firstlineno
4328+
expected= ['Traceback (most recent call last):',
4329+
f' File{magenta}"{__file__}"{reset}, '
4330+
f'line{magenta}{lno_foo+5}{reset}, in{magenta}test_colorized_traceback_is_the_default{reset}',
4331+
f'{red}foo{reset+boldr}(){reset}',
4332+
f'{red}~~~{reset+boldr}^^{reset}',
4333+
f' File{magenta}"{__file__}"{reset}, '
4334+
f'line{magenta}{lno_foo+1}{reset}, in{magenta}foo{reset}',
4335+
f'{red}1{reset+boldr}/{reset+red}0{reset}',
4336+
f'{red}~{reset+boldr}^{reset+red}~{reset}',
4337+
f'{boldm}ZeroDivisionError{reset}:{magenta}division by zero{reset}']
4338+
self.assertEqual(actual,expected)
4339+
4340+
deftest_colorized_detection_checks_for_environment_variables(self):
4341+
ifsys.platform=="win32":
4342+
virtual_patching=unittest.mock.patch("nt._supports_virtual_terminal",return_value=True)
4343+
else:
4344+
virtual_patching=contextlib.nullcontext()
4345+
withvirtual_patching:
4346+
withunittest.mock.patch("os.isatty")asisatty_mock:
4347+
isatty_mock.return_value=True
4348+
withunittest.mock.patch("os.environ", {'TERM':'dumb'}):
4349+
self.assertEqual(traceback._can_colorize(),False)
4350+
withunittest.mock.patch("os.environ", {'PYTHON_COLORS':'1'}):
4351+
self.assertEqual(traceback._can_colorize(),True)
4352+
withunittest.mock.patch("os.environ", {'PYTHON_COLORS':'0'}):
4353+
self.assertEqual(traceback._can_colorize(),False)
4354+
withunittest.mock.patch("os.environ", {'NO_COLOR':'1'}):
4355+
self.assertEqual(traceback._can_colorize(),False)
4356+
withunittest.mock.patch("os.environ", {'NO_COLOR':'1',"PYTHON_COLORS":'1'}):
4357+
self.assertEqual(traceback._can_colorize(),True)
4358+
withunittest.mock.patch("os.environ", {'FORCE_COLOR':'1'}):
4359+
self.assertEqual(traceback._can_colorize(),True)
4360+
withunittest.mock.patch("os.environ", {'FORCE_COLOR':'1','NO_COLOR':'1'}):
4361+
self.assertEqual(traceback._can_colorize(),False)
4362+
withunittest.mock.patch("os.environ", {'FORCE_COLOR':'1',"PYTHON_COLORS":'0'}):
4363+
self.assertEqual(traceback._can_colorize(),False)
4364+
isatty_mock.return_value=False
4365+
self.assertEqual(traceback._can_colorize(),False)
42484366

42494367
if__name__=="__main__":
42504368
unittest.main()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp