Skip to content

Commit 722a27d

Browse files
committed
fixes #3
1 parent e50bcb2 commit 722a27d

3 files changed

Lines changed: 55 additions & 8 deletions

File tree

DESIGN.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ This keeps tracing focused to the target function and any nested defs within its
108108
### Call stack capture
109109

110110
Each recorded call includes a `stack_str` captured on call entry. The stack is a newline-joined
111-
list of `func_name (file:line)` frames, filtered so `fn` is the shallowest frame shown (frames
112-
above `fn` are omitted). For frames under `fn`'s directory, `file` is shown relative to that
113-
directory; other frames keep full paths. If `target_func` is `None`, `stack_str` is empty.
111+
list of `qualname (file:line)` frames (so methods include the class name), filtered so `fn` is the
112+
shallowest frame shown (frames above `fn` are omitted). For frames under `fn`'s directory, `file`
113+
is shown relative to that directory. Remaining absolute paths are normalized by replacing the
114+
longest matching prefixes first (e.g., `$SITE_PACKAGES`, `$VIRTUAL_ENV`, `~`, Python prefix paths).
115+
If `target_func` is `None`, `stack_str` is empty.
114116

115117
### Mapping execution position to an AST line
116118

tests/test_tracefunc.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ def _wrapper_calls_target_two_places(n):
141141
return out
142142

143143

144+
class _StackClass:
145+
def inc(self, x):
146+
return x + 1
147+
148+
149+
def _wrapper_calls_method(x):
150+
obj = _StackClass()
151+
return obj.inc(x)
152+
153+
144154
# ----------------------------
145155
# Helpers
146156
# ----------------------------
@@ -568,3 +578,10 @@ def test_stack_traces_include_target_and_show_two_call_sites():
568578
assert lines[-1].startswith("_targeted_inner (")
569579
assert any("_wrapper_calls_target_two_places (" in line for line in lines)
570580
assert base_dir not in stack
581+
582+
583+
def test_stack_traces_include_class_name_for_methods():
584+
res_list = tracefunc(_wrapper_calls_method, 3, target_func=_StackClass.inc)
585+
assert len(res_list) == 1
586+
stack = res_list[0][0]
587+
assert any(line.startswith("_StackClass.inc (") for line in stack.splitlines())

tracefunc/core.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ast, builtins, dis, inspect, os, sys, textwrap
1+
import ast, builtins, dis, inspect, os, sys, sysconfig, textwrap
22
from fastcore.utils import *
33

44
comp_node_types = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
@@ -86,6 +86,27 @@ def tracefunc(fn, /, *args, target_func=None, **kwargs):
8686

8787
stack_anchor = fn.__code__ if target_func is not None else None
8888
stack_base = os.path.dirname(stack_anchor.co_filename) if stack_anchor else None
89+
repls = []
90+
venv = os.environ.get("VIRTUAL_ENV")
91+
if venv: repls.append((os.path.realpath(venv), "$VIRTUAL_ENV"))
92+
home = os.path.expanduser("~")
93+
if home: repls.append((os.path.realpath(home), "~"))
94+
for p, label in (
95+
(sys.prefix, "$PYTHON_PREFIX"),
96+
(getattr(sys, "base_prefix", None), "$PYTHON_BASE"),
97+
(getattr(sys, "exec_prefix", None), "$PYTHON_EXEC_PREFIX"),
98+
(getattr(sys, "base_exec_prefix", None), "$PYTHON_BASE_EXEC_PREFIX"),
99+
):
100+
if p: repls.append((os.path.realpath(p), label))
101+
paths = sysconfig.get_paths()
102+
for key, label in (
103+
("purelib", "$SITE_PACKAGES"),
104+
("platlib", "$PLAT_SITE_PACKAGES"),
105+
("stdlib", "$PYTHON_STDLIB"),
106+
("platstdlib", "$PYTHON_PLAT_STDLIB"),
107+
):
108+
if key in paths: repls.append((os.path.realpath(paths[key]), label))
109+
repls.sort(key=lambda x: len(x[0]), reverse=True)
89110
if target_func is None: target_func = fn
90111

91112
try: src_lines, block_first_lineno = inspect.getsourcelines(target_func)
@@ -284,12 +305,19 @@ def _stack_str(frame):
284305
frames = frames[i:]
285306
break
286307
else: return ""
308+
def _shorten(path):
309+
if stack_base and path.startswith(stack_base + os.sep):
310+
return os.path.relpath(path, stack_base)
311+
if not os.path.isabs(path): return path
312+
for base, label in repls:
313+
if path == base or path.startswith(base + os.sep):
314+
return label + path[len(base):]
315+
return path
287316
out = []
288317
for fr in frames:
289-
path = fr.f_code.co_filename
290-
if stack_base and path.startswith(stack_base + os.sep):
291-
path = os.path.relpath(path, stack_base)
292-
out.append(f"{fr.f_code.co_name} ({path}:{fr.f_lineno})")
318+
path = _shorten(fr.f_code.co_filename)
319+
name = getattr(fr.f_code, "co_qualname", fr.f_code.co_name)
320+
out.append(f"{name} ({path}:{fr.f_lineno})")
293321
return "\n".join(out)
294322

295323
def _snapshot(call_idx, line_id, frame, *, force=False):

0 commit comments

Comments
 (0)