Python’s trace module traces statement execution and records line-level coverage without any third-party dependencies. import trace. Create: t = trace.Trace(count=True, trace=False, countfuncs=False, countcallers=False, ignoremods=(), ignoredirs=()) — count=True counts executions per line; trace=True prints each line as it runs; countfuncs=True records whether each function was called; countcallers=True builds a caller → callee dict. Ignore: ignoremods=["unittest"] — exclude modules by name; ignoredirs=[sysconfig.get_path("stdlib")] — exclude directories by prefix. Run callable: t.runfunc(fn, *args, **kwargs) — traces fn and returns its result. Run code string: t.run("import os; os.getcwd()"). Results: r = t.results() — CoverageResults object; r.counts — dict[(filename, lineno), int]; r.write_results(show_missing=True, coverdir="/tmp/cover") — writes annotated .cover files. Command line: python -m trace --count -C cover_dir script.py — same functionality from the shell. Claude Code generates statement coverage collectors, execution tracers, function call graphs, and lightweight CI coverage reporters.
CLAUDE.md for trace
## trace Stack
- Stdlib: import trace, sys
- Create: t = trace.Trace(count=True, trace=False)
- Run: result = t.runfunc(my_function, arg1, arg2)
- Results: r = t.results() # CoverageResults
- Counts: r.counts # {(filename, lineno): exec_count}
- Write: r.write_results(show_missing=True, coverdir="/tmp/cover")
- Ignore: ignoredirs=[sysconfig.get_path("stdlib")]
- CLI: python -m trace --count -C ./cover script.py
trace Execution Tracing Pipeline
# app/traceutil.py — coverage, tracer, call graph, reporter
from __future__ import annotations
import sys
import sysconfig
import trace
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable
# ─────────────────────────────────────────────────────────────────────────────
# 1. Helpers for ignoring stdlib / third-party
# ─────────────────────────────────────────────────────────────────────────────
def _stdlib_dirs() -> list[str]:
"""Return a list of stdlib directory prefixes to exclude."""
dirs = []
for key in ("stdlib", "platstdlib", "purelib", "platlib"):
p = sysconfig.get_path(key)
if p:
dirs.append(p)
return dirs
def make_tracer(
count: bool = True,
trace_lines: bool = False,
countfuncs: bool = False,
countcallers: bool = False,
ignore_stdlib: bool = True,
extra_ignoredirs: "list[str] | None" = None,
ignoremods: "list[str] | None" = None,
) -> trace.Trace:
"""
Create a configured Trace instance with sensible defaults.
Example:
t = make_tracer(count=True, ignore_stdlib=True)
t.runfunc(my_function, x, y)
report_coverage(t.results(), "src/")
"""
ignoredirs = (_stdlib_dirs() if ignore_stdlib else []) + (extra_ignoredirs or [])
return trace.Trace(
count=count,
trace=trace_lines,
countfuncs=countfuncs,
countcallers=countcallers,
ignoredirs=ignoredirs,
ignoremods=ignoremods or [],
)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Coverage collection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class LineCount:
"""Execution count info for one source file."""
filename: str
total_lines: int
executed: int
missed: int
coverage_pct: float
missed_lines: list[int] = field(default_factory=list)
def __str__(self) -> str:
return (
f"{self.filename} "
f"{self.executed}/{self.total_lines} lines "
f"{self.coverage_pct:.1f}%"
+ (f" missed={self.missed_lines}" if self.missed_lines else "")
)
@dataclass
class CoverageReport:
"""Coverage report for one traced execution."""
files: list[LineCount] = field(default_factory=list)
@property
def total_lines(self) -> int:
return sum(f.total_lines for f in self.files)
@property
def executed_lines(self) -> int:
return sum(f.executed for f in self.files)
@property
def overall_pct(self) -> float:
if self.total_lines == 0:
return 0.0
return 100.0 * self.executed_lines / self.total_lines
def __str__(self) -> str:
lines = [f"CoverageReport: {self.overall_pct:.1f}% "
f"({self.executed_lines}/{self.total_lines})"]
for f in self.files:
lines.append(f" {f}")
return "\n".join(lines)
def coverage_of_file(
source_path: "str | Path",
counts: "dict[tuple, int]",
) -> "LineCount | None":
"""
Compute coverage stats for one source file given the counts dict.
Example:
t = make_tracer()
t.runfunc(fn)
lc = coverage_of_file("mymodule.py", t.results().counts)
"""
p = Path(source_path).resolve()
src_str = str(p)
# Collect executed line numbers for this file
executed = {lineno for (fname, lineno), cnt in counts.items()
if Path(fname).resolve() == p and cnt > 0}
if not p.exists():
return None
# Count executable lines (non-empty, non-comment)
executable: list[int] = []
for i, line in enumerate(p.read_text().splitlines(), 1):
stripped = line.strip()
if stripped and not stripped.startswith("#"):
executable.append(i)
if not executable:
return None
total = len(executable)
exec_count = sum(1 for ln in executable if ln in executed)
missed = [ln for ln in executable if ln not in executed]
pct = 100.0 * exec_count / total if total else 0.0
return LineCount(
filename=str(p),
total_lines=total,
executed=exec_count,
missed=total - exec_count,
coverage_pct=pct,
missed_lines=missed,
)
def report_coverage(
results: trace.CoverageResults,
source_root: "str | Path",
) -> CoverageReport:
"""
Build a CoverageReport for all .py files under source_root
that appear in the trace results.
Example:
t = make_tracer()
t.runfunc(run_app)
report = report_coverage(t.results(), "src/myapp")
print(report)
"""
root = Path(source_root).resolve()
counts = results.counts
seen: set[Path] = set()
for (fname, _) in counts.keys():
p = Path(fname).resolve()
if str(p).startswith(str(root)):
seen.add(p)
file_reports = []
for p in sorted(seen):
lc = coverage_of_file(p, counts)
if lc:
file_reports.append(lc)
return CoverageReport(files=file_reports)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Function coverage (countfuncs)
# ─────────────────────────────────────────────────────────────────────────────
def called_functions(
fn: Callable,
*args,
ignore_stdlib: bool = True,
**kwargs,
) -> dict[str, list[str]]:
"""
Return a dict of {module: [function_names]} called during fn(*args).
Uses countfuncs=True mode.
Example:
calls = called_functions(my_app.run, config)
for mod, fns in calls.items():
print(f" {mod}: {fns}")
"""
t = make_tracer(count=False, countfuncs=True, ignore_stdlib=ignore_stdlib)
t.runfunc(fn, *args, **kwargs)
r = t.results()
result: dict[str, list[str]] = {}
for (filename, modname, funcname) in r.calledfuncs:
result.setdefault(modname or filename, []).append(funcname)
return result
# ─────────────────────────────────────────────────────────────────────────────
# 4. Line-by-line tracer context manager
# ─────────────────────────────────────────────────────────────────────────────
class TraceContext:
"""
Context manager that traces execution of a block and exposes the results.
Example:
with TraceContext(ignore_stdlib=True) as tc:
result = my_computation(data)
print(report_coverage(tc.results, "src/"))
"""
def __init__(self, ignore_stdlib: bool = True, countfuncs: bool = False) -> None:
self._tracer = make_tracer(
count=True,
ignore_stdlib=ignore_stdlib,
countfuncs=countfuncs,
)
self.results: "trace.CoverageResults | None" = None
def __enter__(self) -> "TraceContext":
# Install sys.settrace manually so tracing applies to the with-block
# Note: trace.Trace.runfunc is simpler; this gives block-level control
self._old_trace = sys.gettrace()
sys.settrace(self._tracer.localtrace)
return self
def __exit__(self, *_: object) -> None:
sys.settrace(self._old_trace)
self.results = self._tracer.results()
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== trace demo ===")
with tempfile.TemporaryDirectory() as td:
td_path = Path(td)
# ── write a module to trace ────────────────────────────────────────────
subject = td_path / "subject.py"
subject.write_text(
"def add(a, b):\n"
" return a + b\n"
"\n"
"def subtract(a, b):\n"
" return a - b\n"
"\n"
"def run():\n"
" x = add(10, 5)\n"
" y = add(x, 3)\n"
" return y\n"
)
# Import the subject module
import importlib.util
spec = importlib.util.spec_from_file_location("subject", subject)
subject_mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(subject_mod)
# ── basic coverage trace ───────────────────────────────────────────────
print("\n--- make_tracer + runfunc + coverage_of_file ---")
t = make_tracer(count=True, ignore_stdlib=True)
result_val = t.runfunc(subject_mod.run)
print(f" run() returned: {result_val}")
lc = coverage_of_file(subject, t.results().counts)
if lc:
print(f" {lc}")
# ── report_coverage ───────────────────────────────────────────────────
print("\n--- report_coverage ---")
report = report_coverage(t.results(), td_path)
print(report)
# ── countfuncs ────────────────────────────────────────────────────────
print("\n--- called_functions ---")
calls = called_functions(subject_mod.run, ignore_stdlib=True)
for mod, fns in sorted(calls.items()):
print(f" {mod}: {fns}")
# ── write_results to disk ─────────────────────────────────────────────
print("\n--- write_results ---")
cover_dir = td_path / "cover"
cover_dir.mkdir()
t.results().write_results(show_missing=True, coverdir=str(cover_dir))
cover_files = list(cover_dir.glob("*.cover"))
print(f" wrote {len(cover_files)} .cover file(s)")
if cover_files:
lines = cover_files[0].read_text().splitlines()
for line in lines[:6]:
print(f" {line}")
# ── trace_lines mode ──────────────────────────────────────────────────
print("\n--- trace_lines mode ---")
import io as _io
old_stdout = sys.stdout
sys.stdout = _io.StringIO()
t2 = make_tracer(count=False, trace_lines=True, ignore_stdlib=True)
t2.runfunc(subject_mod.add, 3, 4)
output = sys.stdout.getvalue()
sys.stdout = old_stdout
for line in output.strip().splitlines()[:4]:
print(f" traced: {line}")
print("\n=== done ===")
For the coverage.py (PyPI) alternative — coverage run and coverage report provide branch coverage, .coverage data files, HTML/XML/JSON reports, parallel mode, and plugin architecture — use coverage.py for all production coverage measurement; use trace (stdlib) when you cannot install third-party packages, need a quick one-off coverage check in a minimal environment, or want to programmatically inspect the counts dict directly. For the sys.settrace alternative — sys.settrace(handler) installs a low-level line/call/return/exception event hook at the interpreter level — use sys.settrace when you need full control over the profiling callback (e.g., to log specific variable values or instrument individual functions); use trace.Trace when you just want execution counts or a call graph without writing the event dispatcher yourself. The Claude Skills 360 bundle includes trace skill sets covering make_tracer() with ignore_stdlib/countfuncs/countcallers options, LineCount/CoverageReport dataclasses, coverage_of_file() per-file coverage stats, report_coverage() multi-file coverage reporter, called_functions() countfuncs driver, and TraceContext context manager with results attribute. Start with the free tier to try execution tracing patterns and trace pipeline code generation.