Skip to content

Commit f1e94ab

Browse files
committed
Fix f-string formatting in traceback of Python 3.12
In Python 3.12, new tokens were added to "tokenize" module in order to differentiate simple strings and f-string. Additionally, the expressions inside the f-string are properly parsed as well. The unit tests have been updated consequently.
1 parent 22bccb7 commit f1e94ab

File tree

4 files changed

+30
-11
lines changed

4 files changed

+30
-11
lines changed

loguru/_better_exceptions.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class SyntaxHighlighter:
4646
_builtins = set(dir(builtins))
4747
_constants = {"True", "False", "None"}
4848
_punctation = {"(", ")", "[", "]", "{", "}", ":", ",", ";"}
49+
_strings = {tokenize.STRING}
50+
_fstring_middle = None
51+
52+
if sys.version_info >= (3, 12):
53+
_strings.update({tokenize.FSTRING_START, tokenize.FSTRING_MIDDLE, tokenize.FSTRING_END})
54+
_fstring_middle = tokenize.FSTRING_MIDDLE
4955

5056
def __init__(self, style=None):
5157
self._style = style or self._default_style
@@ -56,7 +62,12 @@ def highlight(self, source):
5662
output = ""
5763

5864
for token in self.tokenize(source):
59-
type_, string, start, end, line = token
65+
type_, string, (start_row, start_column), (_, end_column), line = token
66+
67+
if type_ == self._fstring_middle:
68+
# When an f-string contains "{{" or "}}", they appear as "{" or "}" in the "string"
69+
# attribute of the token. However, they do not count in the column position.
70+
end_column += string.count("{") + string.count("}")
6071

6172
if type_ == tokenize.NAME:
6273
if string in self._constants:
@@ -74,23 +85,20 @@ def highlight(self, source):
7485
color = style["operator"]
7586
elif type_ == tokenize.NUMBER:
7687
color = style["number"]
77-
elif type_ == tokenize.STRING:
88+
elif type_ in self._strings:
7889
color = style["string"]
7990
elif type_ == tokenize.COMMENT:
8091
color = style["comment"]
8192
else:
8293
color = style["other"]
8394

84-
start_row, start_column = start
85-
_, end_column = end
86-
8795
if start_row != row:
8896
source = source[column:]
8997
row, column = start_row, 0
9098

9199
if type_ != tokenize.ENCODING:
92100
output += line[column:start_column]
93-
output += color.format(string)
101+
output += color.format(line[start_column:end_column])
94102

95103
column = end_column
96104

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11

22
Traceback (most recent call last):
33

4-
File "tests/exceptions/source/modern/f_string.py", line 17, in <module>
4+
File "tests/exceptions/source/modern/f_string.py", line 21, in <module>
55
hello()
66
└ <function hello at 0xDEADBEEF>
77

8-
File "tests/exceptions/source/modern/f_string.py", line 13, in hello
9-
f"{name}" and f'{{ {f / 0} }}'
8+
File "tests/exceptions/source/modern/f_string.py", line 11, in hello
9+
output = f"Hello" + f' ' + f"""World""" and world()
10+
 └ <function world at 0xDEADBEEF>
11+
12+
File "tests/exceptions/source/modern/f_string.py", line 17, in world
13+
f"{name} -> { f }" and {} or f'{{ {f / 0} }}'
14+
 │ │ └ 1
15+
 │ └ 1
16+
 └ 'world'
1017

1118
ZeroDivisionError: division by zero

tests/exceptions/source/modern/f_string.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99

1010
def hello():
11+
output = f"Hello" + f' ' + f"""World""" and world()
12+
13+
14+
def world():
1115
name = "world"
1216
f = 1
13-
f"{name}" and f'{{ {f / 0} }}'
17+
f"{name} -> { f }" and {} or f'{{ {f / 0} }}'
1418

1519

1620
with logger.catch():

tests/test_exceptions_formatting.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ def test_exception_others(filename):
230230
"filename, minimum_python_version",
231231
[
232232
("type_hints", (3, 6)),
233-
("f_string", (3, 6)),
234233
("positional_only_argument", (3, 8)),
235234
("walrus_operator", (3, 8)),
236235
("match_statement", (3, 10)),
@@ -242,6 +241,7 @@ def test_exception_others(filename):
242241
("grouped_as_cause_and_context", (3, 11)),
243242
("grouped_max_length", (3, 11)),
244243
("grouped_max_depth", (3, 11)),
244+
("f_string", (3, 12)), # Available since 3.6 but in 3.12 the lexer for f-string changed.
245245
],
246246
)
247247
def test_exception_modern(filename, minimum_python_version):

0 commit comments

Comments
 (0)