Skip to content

Commit 6053f24

Browse files
committed
Per mpage's suggestion: use traceback style formatring for frames
1 parent dabd158 commit 6053f24

File tree

3 files changed

+36
-27
lines changed

3 files changed

+36
-27
lines changed

Doc/library/asyncio-stack.rst

+17-14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ a suspended *future*.
1818
.. versionadded:: 3.14
1919

2020

21-
.. function:: print_call_graph(*, future=None, file=None)
21+
.. function:: print_call_graph(*, future=None, file=None, depth=1)
2222

2323
Print the async call graph for the current task or the provided
2424
:class:`Task` or :class:`Future`.
@@ -27,6 +27,10 @@ a suspended *future*.
2727
If not passed, the current running task will be used. If there's no
2828
current task, the function returns ``None``.
2929

30+
If the function is called on *the current task*, the optional
31+
keyword-only ``depth`` argument can be used to skip the specified
32+
number of frames from top of the stack.
33+
3034
If *file* is not specified the function will print to :data:`sys.stdout`.
3135

3236
**Example:**
@@ -48,19 +52,14 @@ a suspended *future*.
4852
4953
will print::
5054

51-
* Task(name='Task-2', id=0x105038fe0)
52-
+ Call stack:
53-
| * print_call_graph()
54-
| asyncio/stack.py:231
55-
| * async test()
56-
| test.py:4
57-
+ Awaited by:
58-
* Task(name='Task-1', id=0x1050a6060)
59-
+ Call stack:
60-
| * async TaskGroup.__aexit__()
61-
| asyncio/taskgroups.py:107
62-
| * async main()
63-
| test.py:7
55+
* Task(name='Task-2', id=0x1039f0fe0)
56+
+ Call stack:
57+
| File 't2.py', line 4, in async test()
58+
+ Awaited by:
59+
* Task(name='Task-1', id=0x103a5e060)
60+
+ Call stack:
61+
| File 'taskgroups.py', line 107, in async TaskGroup.__aexit__()
62+
| File 't2.py', line 7, in async main()
6463

6564
For rendering the call stack to a string the following pattern
6665
should be used:
@@ -85,6 +84,10 @@ a suspended *future*.
8584
If not passed, the current running task will be used. If there's no
8685
current task, the function returns ``None``.
8786

87+
If the function is called on *the current task*, the optional
88+
keyword-only ``depth`` argument can be used to skip the specified
89+
number of frames from top of the stack.
90+
8891
Returns a ``FutureCallGraph`` named tuple:
8992

9093
* ``FutureCallGraph(future, call_graph, awaited_by)``

Lib/asyncio/stack.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ def _build_stack_for_future(future: any) -> FutureCallGraph:
7575
return FutureCallGraph(future, st, awaited_by)
7676

7777

78-
def capture_call_graph(*, future: any = None) -> FutureCallGraph | None:
78+
def capture_call_graph(
79+
*,
80+
future: any = None,
81+
depth: int = 1,
82+
) -> FutureCallGraph | None:
7983
"""Capture async call stack for the current task or the provided Future.
8084
8185
The stack is represented with three data structures:
@@ -103,6 +107,10 @@ def capture_call_graph(*, future: any = None) -> FutureCallGraph | None:
103107
Receives an optional keyword-only "future" argument. If not passed,
104108
the current task will be used. If there's no current task, the function
105109
returns None.
110+
111+
If "capture_call_graph()" is introspecting *the current task*, the
112+
optional keyword-only "depth" argument can be used to skip the specified
113+
number of frames from top of the stack.
106114
"""
107115

108116
loop = events._get_running_loop()
@@ -135,7 +143,7 @@ def capture_call_graph(*, future: any = None) -> FutureCallGraph | None:
135143

136144
call_graph: list[FrameCallGraphEntry | CoroutineCallGraphEntry] = []
137145

138-
f = sys._getframe(1)
146+
f = sys._getframe(depth)
139147
try:
140148
while f is not None:
141149
is_async = f.f_generator is not None
@@ -161,7 +169,7 @@ def capture_call_graph(*, future: any = None) -> FutureCallGraph | None:
161169
return FutureCallGraph(future, call_graph, awaited_by)
162170

163171

164-
def print_call_graph(*, future: any = None, file=None) -> None:
172+
def print_call_graph(*, future: any = None, file=None, depth: int = 1) -> None:
165173
"""Print async call stack for the current task or the provided Future."""
166174

167175
def render_level(st: FutureCallGraph, buf: list[str], level: int):
@@ -185,10 +193,9 @@ def add_line(line: str):
185193
if isinstance(ste, FrameCallGraphEntry):
186194
f = ste.frame
187195
add_line(
188-
f' | * {f.f_code.co_qualname}()'
189-
)
190-
add_line(
191-
f' | {f.f_code.co_filename}:{f.f_lineno}'
196+
f' | File {f.f_code.co_filename!r},'
197+
f' line {f.f_lineno}, in'
198+
f' {f.f_code.co_qualname}()'
192199
)
193200
else:
194201
assert isinstance(ste, CoroutineCallGraphEntry)
@@ -209,10 +216,9 @@ def add_line(line: str):
209216
tag = 'generator'
210217

211218
add_line(
212-
f' | * {tag} {code.co_qualname}()'
213-
)
214-
add_line(
215-
f' | {f.f_code.co_filename}:{f.f_lineno}'
219+
f' | File {f.f_code.co_filename!r},'
220+
f' line {f.f_lineno}, in'
221+
f' {tag} {code.co_qualname}()'
216222
)
217223

218224
if st.awaited_by:
@@ -222,7 +228,7 @@ def add_line(line: str):
222228
for fut in st.awaited_by:
223229
render_level(fut, buf, level + 1)
224230

225-
stack = capture_call_graph(future=future)
231+
stack = capture_call_graph(future=future, depth=depth + 1)
226232
if stack is None:
227233
return
228234

Lib/test/test_asyncio/test_stack.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ async def main():
107107
])
108108

109109
self.assertIn(
110-
'* async TestCallStack.test_stack_tgroup()',
110+
' async TestCallStack.test_stack_tgroup()',
111111
stack_for_c5[1])
112112

113113

0 commit comments

Comments
 (0)