traceflow-py 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
traceflow/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TraceFlow — A lightweight Python decorator for visually tracing function execution.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from traceflow import watch
|
|
6
|
+
|
|
7
|
+
@watch()
|
|
8
|
+
def my_function(x):
|
|
9
|
+
return x * 2
|
|
10
|
+
|
|
11
|
+
Global controls:
|
|
12
|
+
import traceflow
|
|
13
|
+
traceflow.disable()
|
|
14
|
+
traceflow.enable()
|
|
15
|
+
traceflow.is_enabled()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from traceflow.core import watch, enable, disable, is_enabled
|
|
19
|
+
|
|
20
|
+
__version__ = "1.2.0"
|
|
21
|
+
__author__ = "Aarav Agarwal"
|
|
22
|
+
__all__ = ["watch", "enable", "disable", "is_enabled"]
|
traceflow/core.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core implementation of the TraceFlow @watch decorator.
|
|
3
|
+
|
|
4
|
+
This module contains all tracing logic: call tree rendering, timing,
|
|
5
|
+
variable state tracking, truncation, exception handling, and async support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
import contextvars
|
|
10
|
+
import inspect
|
|
11
|
+
import time as _time
|
|
12
|
+
import sys as _sys
|
|
13
|
+
|
|
14
|
+
# ── Global State ────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
_depth = contextvars.ContextVar('depth', default=0)
|
|
17
|
+
_enabled = True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def enable():
|
|
21
|
+
"""Enable all tracing globally."""
|
|
22
|
+
global _enabled
|
|
23
|
+
_enabled = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def disable():
|
|
27
|
+
"""Disable all tracing globally. Decorated functions still execute normally."""
|
|
28
|
+
global _enabled
|
|
29
|
+
_enabled = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_enabled():
|
|
33
|
+
"""Return whether tracing is currently enabled."""
|
|
34
|
+
return _enabled
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── Decorator ───────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def watch(func=None, *, track_time=True, truncate_len=50, max_depth=None, export_path=None, track_vars=False):
|
|
40
|
+
"""
|
|
41
|
+
Decorator that traces function calls, producing an indented call tree.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
track_time (bool): Append execution time to each return line. Default True.
|
|
45
|
+
truncate_len (int): Max characters for argument/return repr. 0 or None to disable.
|
|
46
|
+
max_depth (int): Stop tracing beyond this call depth. None for unlimited.
|
|
47
|
+
export_path (str): Write trace output to this file instead of stdout.
|
|
48
|
+
track_vars (bool): Log local variable assignments inside the function (sync only).
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The decorated function (sync or async wrapper).
|
|
52
|
+
|
|
53
|
+
Example::
|
|
54
|
+
|
|
55
|
+
@watch(track_time=True)
|
|
56
|
+
def fibonacci(n):
|
|
57
|
+
if n <= 1:
|
|
58
|
+
return n
|
|
59
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
60
|
+
|
|
61
|
+
fibonacci(3)
|
|
62
|
+
# fibonacci(3)
|
|
63
|
+
# ├── fibonacci(2)
|
|
64
|
+
# │ ├── fibonacci(1)
|
|
65
|
+
# │ │ └── return 1 [0.0003s]
|
|
66
|
+
# │ ├── fibonacci(0)
|
|
67
|
+
# │ │ └── return 0 [0.0000s]
|
|
68
|
+
# │ └── return 1 [0.0006s]
|
|
69
|
+
# ├── fibonacci(1)
|
|
70
|
+
# │ └── return 1 [0.0000s]
|
|
71
|
+
# └── return 2 [0.0008s]
|
|
72
|
+
"""
|
|
73
|
+
if func is None:
|
|
74
|
+
return functools.partial(watch, track_time=track_time, truncate_len=truncate_len,
|
|
75
|
+
max_depth=max_depth, export_path=export_path, track_vars=track_vars)
|
|
76
|
+
|
|
77
|
+
# ── Helpers ─────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
def _get_repr(val):
|
|
80
|
+
"""Return a repr string for *val*, truncated to *truncate_len* characters."""
|
|
81
|
+
s = repr(val)
|
|
82
|
+
if truncate_len and len(s) > truncate_len:
|
|
83
|
+
s = s[:truncate_len - 3] + "..."
|
|
84
|
+
return s
|
|
85
|
+
|
|
86
|
+
def _log(msg):
|
|
87
|
+
"""Print to console or append to file."""
|
|
88
|
+
if export_path:
|
|
89
|
+
try:
|
|
90
|
+
with open(export_path, "a", encoding="utf-8") as f:
|
|
91
|
+
f.write(msg + "\n")
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
else:
|
|
95
|
+
print(msg)
|
|
96
|
+
|
|
97
|
+
def _prefix(depth):
|
|
98
|
+
"""Build the tree prefix for a child node at the given depth."""
|
|
99
|
+
if depth == 0:
|
|
100
|
+
return ""
|
|
101
|
+
return "│ " * (depth - 1) + "├── "
|
|
102
|
+
|
|
103
|
+
def _continuation(depth):
|
|
104
|
+
"""Build the continuation (vertical lines) for content at the given depth."""
|
|
105
|
+
return "│ " * depth
|
|
106
|
+
|
|
107
|
+
# ── Trace entry / exit ──────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
def _trace_call(args, kwargs, depth):
|
|
110
|
+
"""Print the function call line and return start_time."""
|
|
111
|
+
pos_args = [_get_repr(a) for a in args]
|
|
112
|
+
kw_args = [f"{k}={_get_repr(v)}" for k, v in kwargs.items()]
|
|
113
|
+
all_args = ", ".join(pos_args + kw_args)
|
|
114
|
+
call_str = f"{func.__name__}({all_args})"
|
|
115
|
+
_log(f"{_prefix(depth)}{call_str}")
|
|
116
|
+
return _time.perf_counter() if track_time else None
|
|
117
|
+
|
|
118
|
+
def _trace_return(depth, start_time, result=None, exc=None):
|
|
119
|
+
"""Print the return/exception line as the last child of the call."""
|
|
120
|
+
elapsed = f" [{_time.perf_counter() - start_time:.4f}s]" if (track_time and start_time) else ""
|
|
121
|
+
ret_prefix = _continuation(depth) + "└── "
|
|
122
|
+
if exc is not None:
|
|
123
|
+
_log(f"{ret_prefix}{type(exc).__name__}: {str(exc)}{elapsed}")
|
|
124
|
+
else:
|
|
125
|
+
_log(f"{ret_prefix}return {_get_repr(result)}{elapsed}")
|
|
126
|
+
|
|
127
|
+
# ── Variable state tracking (sys.settrace) ──────────────────
|
|
128
|
+
|
|
129
|
+
def _make_var_tracer(depth):
|
|
130
|
+
"""Create a sys.settrace local tracer for variable state tracking."""
|
|
131
|
+
var_prefix = _continuation(depth) + "│ "
|
|
132
|
+
prev_locals = [None] # None = first snapshot not yet captured
|
|
133
|
+
target_code = func.__code__
|
|
134
|
+
|
|
135
|
+
def local_tracer(frame, event, arg):
|
|
136
|
+
if event == 'line':
|
|
137
|
+
current = {}
|
|
138
|
+
for k, v in frame.f_locals.items():
|
|
139
|
+
if k.startswith('_'):
|
|
140
|
+
continue
|
|
141
|
+
try:
|
|
142
|
+
current[k] = _get_repr(v)
|
|
143
|
+
except Exception:
|
|
144
|
+
current[k] = '<repr error>'
|
|
145
|
+
|
|
146
|
+
if prev_locals[0] is None:
|
|
147
|
+
# First line event — capture parameters as baseline, don't log them
|
|
148
|
+
prev_locals[0] = current
|
|
149
|
+
else:
|
|
150
|
+
for k, v in current.items():
|
|
151
|
+
if k not in prev_locals[0] or prev_locals[0][k] != v:
|
|
152
|
+
_log(f"{var_prefix}· {k} = {v}")
|
|
153
|
+
prev_locals[0] = current
|
|
154
|
+
return local_tracer
|
|
155
|
+
|
|
156
|
+
def global_tracer(frame, event, arg):
|
|
157
|
+
if event == 'call' and frame.f_code is target_code:
|
|
158
|
+
return local_tracer
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
return global_tracer
|
|
162
|
+
|
|
163
|
+
# ── Wrapper selection ───────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
if inspect.iscoroutinefunction(func):
|
|
166
|
+
@functools.wraps(func)
|
|
167
|
+
async def async_wrapper(*args, **kwargs):
|
|
168
|
+
if not _enabled:
|
|
169
|
+
return await func(*args, **kwargs)
|
|
170
|
+
|
|
171
|
+
depth = _depth.get()
|
|
172
|
+
if max_depth is not None and depth >= max_depth:
|
|
173
|
+
token = _depth.set(depth + 1)
|
|
174
|
+
try:
|
|
175
|
+
return await func(*args, **kwargs)
|
|
176
|
+
finally:
|
|
177
|
+
_depth.reset(token)
|
|
178
|
+
|
|
179
|
+
start_time = _trace_call(args, kwargs, depth)
|
|
180
|
+
token = _depth.set(depth + 1)
|
|
181
|
+
# track_vars not supported for async (sys.settrace is thread-level)
|
|
182
|
+
try:
|
|
183
|
+
result = await func(*args, **kwargs)
|
|
184
|
+
_depth.reset(token)
|
|
185
|
+
_trace_return(depth, start_time, result=result)
|
|
186
|
+
return result
|
|
187
|
+
except Exception as e:
|
|
188
|
+
_depth.reset(token)
|
|
189
|
+
_trace_return(depth, start_time, exc=e)
|
|
190
|
+
raise
|
|
191
|
+
return async_wrapper
|
|
192
|
+
else:
|
|
193
|
+
@functools.wraps(func)
|
|
194
|
+
def sync_wrapper(*args, **kwargs):
|
|
195
|
+
if not _enabled:
|
|
196
|
+
return func(*args, **kwargs)
|
|
197
|
+
|
|
198
|
+
depth = _depth.get()
|
|
199
|
+
if max_depth is not None and depth >= max_depth:
|
|
200
|
+
token = _depth.set(depth + 1)
|
|
201
|
+
try:
|
|
202
|
+
return func(*args, **kwargs)
|
|
203
|
+
finally:
|
|
204
|
+
_depth.reset(token)
|
|
205
|
+
|
|
206
|
+
start_time = _trace_call(args, kwargs, depth)
|
|
207
|
+
token = _depth.set(depth + 1)
|
|
208
|
+
|
|
209
|
+
if track_vars:
|
|
210
|
+
old_trace = _sys.gettrace()
|
|
211
|
+
_sys.settrace(_make_var_tracer(depth))
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
result = func(*args, **kwargs)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
if track_vars:
|
|
217
|
+
_sys.settrace(old_trace)
|
|
218
|
+
_depth.reset(token)
|
|
219
|
+
_trace_return(depth, start_time, exc=e)
|
|
220
|
+
raise
|
|
221
|
+
else:
|
|
222
|
+
if track_vars:
|
|
223
|
+
_sys.settrace(old_trace)
|
|
224
|
+
_depth.reset(token)
|
|
225
|
+
_trace_return(depth, start_time, result=result)
|
|
226
|
+
return result
|
|
227
|
+
return sync_wrapper
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: traceflow-py
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A lightweight decorator for visually tracing Python function calls with indented call trees, timing, variable tracking, and async support.
|
|
5
|
+
Author: Aarav Agarwal
|
|
6
|
+
License: All Rights Reserved
|
|
7
|
+
Project-URL: Homepage, https://github.com/Firestar3/TraceFlow
|
|
8
|
+
Project-URL: Repository, https://github.com/Firestar3/TraceFlow
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Firestar3/TraceFlow/issues
|
|
10
|
+
Keywords: tracing,debugging,decorator,call-tree,profiling,recursion,async,logging
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# TraceFlow
|
|
30
|
+
|
|
31
|
+
**TraceFlow** is a lightweight, zero-dependency Python decorator library for visually tracing function execution. It renders beautiful, indented call trees directly to your console — making debugging recursive, nested, and async code effortless.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
fibonacci(3)
|
|
35
|
+
├── fibonacci(2)
|
|
36
|
+
│ ├── fibonacci(1)
|
|
37
|
+
│ │ └── return 1 [0.0003s]
|
|
38
|
+
│ ├── fibonacci(0)
|
|
39
|
+
│ │ └── return 0 [0.0000s]
|
|
40
|
+
│ └── return 1 [0.0006s]
|
|
41
|
+
├── fibonacci(1)
|
|
42
|
+
│ └── return 1 [0.0000s]
|
|
43
|
+
└── return 2 [0.0008s]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
| Feature | Description |
|
|
51
|
+
|---|---|
|
|
52
|
+
| 🌳 **Call Trees** | Nested, indented tree visualization for every function call |
|
|
53
|
+
| ⏱️ **Execution Time** | Per-call timing with `[0.0042s]` annotations |
|
|
54
|
+
| 📐 **Truncation** | Smart character-limit truncation for large arguments and returns |
|
|
55
|
+
| 💥 **Exception Tracing** | Captures and renders exceptions inline in the call tree |
|
|
56
|
+
| 🔁 **Async Support** | Safely traces `async`/`await` functions and `asyncio.gather` |
|
|
57
|
+
| 🔒 **Depth Limiting** | Cap tracing depth with `max_depth` to reduce noise |
|
|
58
|
+
| 📁 **File Export** | Redirect trace output to a file instead of the console |
|
|
59
|
+
| 🔇 **Global Toggle** | `traceflow.disable()` / `traceflow.enable()` to control all tracing at runtime |
|
|
60
|
+
| 🔬 **Variable Tracking** | `track_vars=True` to log every local variable assignment inside a function |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Clone the repo
|
|
68
|
+
git clone https://github.com/Firestar3/TraceFlow.git
|
|
69
|
+
cd TraceFlow
|
|
70
|
+
|
|
71
|
+
# Install locally
|
|
72
|
+
pip install .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or simply drop the `traceflow/` folder into your project — no dependencies required.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from traceflow import watch
|
|
83
|
+
|
|
84
|
+
@watch()
|
|
85
|
+
def fibonacci(n):
|
|
86
|
+
if n <= 1:
|
|
87
|
+
return n
|
|
88
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
89
|
+
|
|
90
|
+
fibonacci(3)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Output:**
|
|
94
|
+
```
|
|
95
|
+
fibonacci(3)
|
|
96
|
+
├── fibonacci(2)
|
|
97
|
+
│ ├── fibonacci(1)
|
|
98
|
+
│ │ └── return 1 [0.0003s]
|
|
99
|
+
│ ├── fibonacci(0)
|
|
100
|
+
│ │ └── return 0 [0.0000s]
|
|
101
|
+
│ └── return 1 [0.0006s]
|
|
102
|
+
├── fibonacci(1)
|
|
103
|
+
│ └── return 1 [0.0000s]
|
|
104
|
+
└── return 2 [0.0008s]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Feature Guide
|
|
110
|
+
|
|
111
|
+
### 1. Execution Time Tracking
|
|
112
|
+
|
|
113
|
+
Every traced call automatically measures wall-clock time.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from traceflow import watch
|
|
117
|
+
import time
|
|
118
|
+
|
|
119
|
+
@watch(track_time=True)
|
|
120
|
+
def slow_add(a, b):
|
|
121
|
+
time.sleep(0.05)
|
|
122
|
+
return a + b
|
|
123
|
+
|
|
124
|
+
slow_add(10, 20)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
slow_add(10, 20)
|
|
129
|
+
└── return 30 [0.0502s]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Set `track_time=False` to disable timing:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
@watch(track_time=False)
|
|
136
|
+
def add(a, b):
|
|
137
|
+
return a + b
|
|
138
|
+
|
|
139
|
+
add(1, 2)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
add(1, 2)
|
|
144
|
+
└── return 3
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### 2. Argument & Return Truncation
|
|
150
|
+
|
|
151
|
+
Prevent massive data structures from flooding your console with `truncate_len`.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
@watch(truncate_len=30)
|
|
155
|
+
def process(data):
|
|
156
|
+
return [x * 2 for x in data]
|
|
157
|
+
|
|
158
|
+
process(list(range(50)))
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
process([0, 1, 2, 3, 4, 5, 6, 7, 8,...)
|
|
163
|
+
└── return [0, 2, 4, 6, 8, 10, 12, 14,... [0.0000s]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The character limit applies to both arguments and return values. Set `truncate_len=None` or `truncate_len=0` to disable truncation entirely.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 3. Exception Tracing
|
|
171
|
+
|
|
172
|
+
Exceptions are captured, rendered in the tree, and re-raised — so your error handling works normally while you get full visibility.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
@watch()
|
|
176
|
+
def divide(a, b):
|
|
177
|
+
return a / b
|
|
178
|
+
|
|
179
|
+
@watch()
|
|
180
|
+
def safe_math(x, y):
|
|
181
|
+
return divide(x, y)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
safe_math(10, 0)
|
|
185
|
+
except ZeroDivisionError:
|
|
186
|
+
pass
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
safe_math(10, 0)
|
|
191
|
+
├── divide(10, 0)
|
|
192
|
+
│ └── ZeroDivisionError: division by zero [0.0000s]
|
|
193
|
+
└── ZeroDivisionError: division by zero [0.0000s]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The exception propagates through each level of the call tree, showing exactly where it originated and how it bubbled up.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### 4. Max Depth Limiting
|
|
201
|
+
|
|
202
|
+
Reduce noise in deeply recursive functions by capping the trace depth.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
@watch(max_depth=2)
|
|
206
|
+
def deep_fib(n):
|
|
207
|
+
if n <= 1:
|
|
208
|
+
return n
|
|
209
|
+
return deep_fib(n - 1) + deep_fib(n - 2)
|
|
210
|
+
|
|
211
|
+
deep_fib(4)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
deep_fib(4)
|
|
216
|
+
├── deep_fib(3)
|
|
217
|
+
│ └── return 2 [0.0000s]
|
|
218
|
+
├── deep_fib(2)
|
|
219
|
+
│ └── return 1 [0.0000s]
|
|
220
|
+
└── return 3 [0.0000s]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Calls beyond `max_depth` still execute normally — they just aren't traced.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### 5. Async Function Support
|
|
228
|
+
|
|
229
|
+
TraceFlow natively supports `async` functions. Concurrent tasks from `asyncio.gather` are traced cleanly.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
import asyncio
|
|
233
|
+
from traceflow import watch
|
|
234
|
+
|
|
235
|
+
@watch()
|
|
236
|
+
async def fetch_data(id):
|
|
237
|
+
await asyncio.sleep(0.01)
|
|
238
|
+
return f"data_{id}"
|
|
239
|
+
|
|
240
|
+
@watch()
|
|
241
|
+
async def fetch_all():
|
|
242
|
+
return await asyncio.gather(fetch_data(1), fetch_data(2), fetch_data(3))
|
|
243
|
+
|
|
244
|
+
asyncio.run(fetch_all())
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
fetch_all()
|
|
249
|
+
├── fetch_data(1)
|
|
250
|
+
│ └── return 'data_1' [0.0123s]
|
|
251
|
+
├── fetch_data(2)
|
|
252
|
+
│ └── return 'data_2' [0.0123s]
|
|
253
|
+
├── fetch_data(3)
|
|
254
|
+
│ └── return 'data_3' [0.0123s]
|
|
255
|
+
└── return ['data_1', 'data_2', 'data_3'] [0.0126s]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### 6. File Export
|
|
261
|
+
|
|
262
|
+
Redirect all trace output to a text file instead of the console.
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
@watch(export_path="trace_output.txt")
|
|
266
|
+
def fibonacci(n):
|
|
267
|
+
if n <= 1:
|
|
268
|
+
return n
|
|
269
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
270
|
+
|
|
271
|
+
fibonacci(3)
|
|
272
|
+
# -> Trace written to trace_output.txt
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The file is appended to, so multiple runs accumulate in the same file.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### 7. Global Enable / Disable
|
|
280
|
+
|
|
281
|
+
Turn all tracing on or off at runtime without removing decorators. Functions still execute normally when tracing is disabled — only the output is suppressed.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
import traceflow
|
|
285
|
+
from traceflow import watch
|
|
286
|
+
|
|
287
|
+
@watch()
|
|
288
|
+
def add(a, b):
|
|
289
|
+
return a + b
|
|
290
|
+
|
|
291
|
+
add(1, 2) # Trace is printed
|
|
292
|
+
|
|
293
|
+
traceflow.disable()
|
|
294
|
+
add(3, 4) # No trace output, but returns 7 normally
|
|
295
|
+
|
|
296
|
+
traceflow.enable()
|
|
297
|
+
add(5, 6) # Trace resumes
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
add(1, 2)
|
|
302
|
+
└── return 3 [0.0000s]
|
|
303
|
+
|
|
304
|
+
add(5, 6)
|
|
305
|
+
└── return 11 [0.0000s]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Also available: `traceflow.is_enabled()` to check current state.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### 8. Variable State Tracking
|
|
313
|
+
|
|
314
|
+
See exactly how local variables change inside a function, line by line. Parameters are captured as a baseline and not logged (they're already visible in the call signature).
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
@watch(track_vars=True)
|
|
318
|
+
def compute(x, y):
|
|
319
|
+
total = x + y
|
|
320
|
+
doubled = total * 2
|
|
321
|
+
message = f"Result: {doubled}"
|
|
322
|
+
return doubled
|
|
323
|
+
|
|
324
|
+
compute(5, 3)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
compute(5, 3)
|
|
329
|
+
│ · total = 8
|
|
330
|
+
│ · doubled = 16
|
|
331
|
+
│ · message = 'Result: 16'
|
|
332
|
+
└── return 16 [0.0002s]
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Variable tracking inside a loop:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
@watch(track_vars=True)
|
|
339
|
+
def sum_list(items):
|
|
340
|
+
total = 0
|
|
341
|
+
for val in items:
|
|
342
|
+
total += val
|
|
343
|
+
return total
|
|
344
|
+
|
|
345
|
+
sum_list([10, 20, 30])
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
sum_list([10, 20, 30])
|
|
350
|
+
│ · total = 0
|
|
351
|
+
│ · val = 10
|
|
352
|
+
│ · total = 10
|
|
353
|
+
│ · val = 20
|
|
354
|
+
│ · total = 30
|
|
355
|
+
│ · val = 30
|
|
356
|
+
│ · total = 60
|
|
357
|
+
└── return 60 [0.0001s]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
> **Note:** Variable tracking uses `sys.settrace` and is only available for synchronous functions. It adds overhead and is best used for targeted debugging, not production code.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### 9. Keyword Arguments
|
|
365
|
+
|
|
366
|
+
TraceFlow displays both positional and keyword arguments.
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
@watch()
|
|
370
|
+
def greet(name, greeting="Hello", punctuation="!"):
|
|
371
|
+
return f"{greeting}, {name}{punctuation}"
|
|
372
|
+
|
|
373
|
+
greet("Aarav", greeting="Hey", punctuation="!!")
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
greet('Aarav', greeting='Hey', punctuation='!!')
|
|
378
|
+
└── return 'Hey, Aarav!!' [0.0000s]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Configuration Reference
|
|
384
|
+
|
|
385
|
+
| Parameter | Type | Default | Description |
|
|
386
|
+
|---|---|---|---|
|
|
387
|
+
| `track_time` | `bool` | `True` | Append `[0.0042s]` execution time to each return line |
|
|
388
|
+
| `truncate_len` | `int` | `50` | Max characters for argument/return repr. `0` or `None` to disable |
|
|
389
|
+
| `max_depth` | `int` | `None` | Stop tracing beyond this call depth. `None` for unlimited |
|
|
390
|
+
| `export_path` | `str` | `None` | Write trace to a file instead of stdout |
|
|
391
|
+
| `track_vars` | `bool` | `False` | Log local variable assignments inside the function (sync only) |
|
|
392
|
+
|
|
393
|
+
### Global Functions
|
|
394
|
+
|
|
395
|
+
| Function | Description |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `traceflow.enable()` | Enable all tracing (default state) |
|
|
398
|
+
| `traceflow.disable()` | Disable all tracing — decorated functions still run normally |
|
|
399
|
+
| `traceflow.is_enabled()` | Returns `True` if tracing is currently active |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Author
|
|
404
|
+
|
|
405
|
+
**Aarav Agarwal**
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
All Rights Reserved.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
traceflow/__init__.py,sha256=rdrpNy3Ha-bXsK77HX3BfKYX-puf24PR5cnUrBrRtD4,476
|
|
2
|
+
traceflow/core.py,sha256=yttjGSbLDl_NPcXNuN651ePfmVM2G-57oOqExPSaMrM,8546
|
|
3
|
+
traceflow_py-1.2.0.dist-info/licenses/LICENSE,sha256=XD4Y9VNyR0ZiCXGbFwyV0JmVnsxGb6m88KVOyK-Ah-c,470
|
|
4
|
+
traceflow_py-1.2.0.dist-info/METADATA,sha256=njO7m0_g8YQVSm-yj-x7P_S4oLJ8ie7ifPGwlPEAVtM,10053
|
|
5
|
+
traceflow_py-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
traceflow_py-1.2.0.dist-info/top_level.txt,sha256=SqOQaaNe15Fx2bMjjT4p-KNgKl2MqxAQVJT5ocAXjZo,10
|
|
7
|
+
traceflow_py-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
All Rights Reserved
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aarav Agarwal
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") may not be
|
|
6
|
+
copied, modified, merged, published, distributed, sublicensed, or sold without
|
|
7
|
+
the express written permission of the copyright holder.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
10
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
11
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
traceflow
|