Skip to content
Snippets Groups Projects
Commit fdd23bef authored by Gordon Ball's avatar Gordon Ball
Browse files

New upstream version 0.2.0

parent dff4602b
No related branches found
No related tags found
No related merge requests found
......@@ -27,7 +27,7 @@ include_package_data = True
tests_require = pytest; typeguard; pygments; littleutils
[options.extras_require]
tests = pytest; typeguard; pygments; littleutils
tests = pytest; typeguard; pygments; littleutils; cython
[coverage:run]
relative_files = True
......@@ -83,7 +83,10 @@ class Source(executing.Source):
@cached_property
def pieces(self) -> List[range]:
if not self.tree:
raise AttributeError("This file doesn't contain valid Python, so .pieces doesn't exist")
return [
range(i, i + 1)
for i in range(1, len(self.lines) + 1)
]
return list(self._clean_pieces())
@cached_property
......@@ -290,15 +293,22 @@ class Line(object):
A list of one or zero RangeInLines for the executing node of this frame.
The list will have one element if the node can be found and it overlaps this line.
"""
return self._raw_executing_node_ranges(
self.frame_info._executing_node_common_indent
)
def _raw_executing_node_ranges(self, common_indent=0) -> List[RangeInLine]:
ex = self.frame_info.executing
node = ex.node
if node:
rang = self.range_from_node(node, ex)
rang = self.range_from_node(node, ex, common_indent)
if rang:
return [rang]
return []
def range_from_node(self, node: ast.AST, data: Any) -> Optional[RangeInLine]:
def range_from_node(
self, node: ast.AST, data: Any, common_indent: int = 0
) -> Optional[RangeInLine]:
"""
If the given node overlaps with this line, return a RangeInLine
with the correct start and end and the given data.
......@@ -316,6 +326,8 @@ class Line(object):
else:
range_start = 0
range_start = max(range_start, common_indent)
if end == self.lineno:
try:
range_end = node.last_token.end[1]
......@@ -343,7 +355,7 @@ class Line(object):
If strip_leading_indent is true (the default) then leading spaces
common to all lines in this frame will be excluded.
"""
if pygmented:
if pygmented and self.frame_info.scope:
assert_(not markers, ValueError("Cannot use pygmented with markers"))
start_line, lines = self.frame_info._pygmented_scope_lines
result = lines[self.lineno - start_line]
......@@ -560,10 +572,12 @@ class FrameInfo(object):
@cached_property
def scope_pieces(self) -> List[range]:
"""
All the pieces (ranges of lines) contained in this object's .scope.
All the pieces (ranges of lines) contained in this object's .scope,
unless there is no .scope (because the source isn't valid Python syntax)
in which case it returns all the pieces in the source file, each containing one line.
"""
if not self.scope:
return []
return self.source.pieces
scope_start, scope_end = line_range(self.scope)
return [
......@@ -610,9 +624,6 @@ class FrameInfo(object):
"""
The piece (range of lines) containing the line currently being executed
by the interpreter in this frame.
Raises an exception if .scope is None, which usually means the source code
for this frame is unavailable.
"""
return only(
piece
......@@ -650,6 +661,25 @@ class FrameInfo(object):
return pieces
@cached_property
def _executing_node_common_indent(self) -> int:
"""
The common minimal indentation shared by the markers intended
for an exception node that spans multiple lines.
Intended to be used only internally.
"""
indents = []
lines = [line for line in self.lines if isinstance(line, Line)]
for line in lines:
for rang in line._raw_executing_node_ranges():
begin_text = len(line.text) - len(line.text.lstrip())
indent = max(rang.start, begin_text)
indents.append(indent)
return min(indents) if indents else 0
@cached_property
def lines(self) -> List[Union[Line, LineGap]]:
"""
......@@ -673,15 +703,13 @@ class FrameInfo(object):
for i, piece in enumerate(pieces):
if (
i == 1
and self.scope
and pieces[0] == self.scope_pieces[0]
and pieces[1] != self.scope_pieces[1]
):
result.append(LINE_GAP)
lines = [
Line(self, i)
for i in piece
] # type: List[Line]
lines = [Line(self, i) for i in piece] # type: List[Line]
if piece != self.executing_piece:
lines = truncate(
lines,
......
import os
import pyximport
from typeguard.importhook import install_import_hook
pyximport.install()
if not os.environ.get("STACK_DATA_SLOW_TESTS"):
install_import_hook(["stack_data"])
Traceback (most recent call last):
File "cython_example.pyx", line 2, in tests.samples.cython_example.foo
1 | def foo():
--> 2 | bar()
3 |
File "cython_example.pyx", line 5, in tests.samples.cython_example.bar
2 | bar()
3 |
4 | cdef bar():
--> 5 | raise ValueError("bar!")
ValueError: bar!
......@@ -6,7 +6,7 @@ Traceback (most recent call last):
--> 57 | 1 /
^^^
58 | 0 + 4
^^^^^^^^^^^
^^^
59 | + 5
60 | )
61 | }"""
......
def foo():
bar()
cdef bar():
raise ValueError("bar!")
......@@ -17,7 +17,8 @@ def foo():
5,
6
][0])
result = print_stack()
result = print_stack(
)
return result
......
......@@ -534,7 +534,6 @@ def test_invalid_source():
filename = str(samples_dir / "not_code.txt")
source = Source.for_filename(filename)
assert not source.tree
assert not hasattr(source, "pieces")
assert not hasattr(source, "tokens_by_lineno")
......@@ -574,12 +573,12 @@ def test_example():
result = bar()
print(result)
assert result == """\
bar at line 26
bar at line 27
--------------
24 | def bar():
25 | <var>names</var> = {}
26 > <exec>exec("result = foo()", globals(), <var>names</var>)</exec>
27 | return <var>names</var>["result"]
25 | def bar():
26 | <var>names</var> = {}
27 > <exec>exec("result = foo()", globals(), <var>names</var>)</exec>
28 | return <var>names</var>["result"]
names = {}
<module> at line 1
......@@ -597,8 +596,9 @@ foo at line 20
(...)
18 | <var> 6</var>
19 | <var> ][0]</var>)
20 > result = <exec>print_stack()</exec>
21 | return result
20 > result = <exec>print_stack(</exec>
21 | <exec>)</exec>
22 | return result
[
1,
2,
......
......@@ -21,7 +21,7 @@ class BaseFormatter(Formatter):
class MyFormatter(BaseFormatter):
def format_frame(self, frame):
if not frame.filename.endswith(("formatter_example.py", "<string>")):
if not frame.filename.endswith(("formatter_example.py", "<string>", "cython_example.pyx")):
return
yield from super().format_frame(frame)
......@@ -72,3 +72,11 @@ def test_example(capsys):
f_string()
except Exception:
MyFormatter().print_exception()
from .samples import cython_example
with check_example("cython_example"):
try:
cython_example.foo()
except Exception:
MyFormatter().print_exception()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment