atrace 0.1.3__tar.gz → 0.1.6__tar.gz
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.
- {atrace-0.1.3 → atrace-0.1.6}/.github/workflows/publish-to-pypi.yml +6 -0
- {atrace-0.1.3 → atrace-0.1.6}/.gitignore +3 -0
- atrace-0.1.6/PKG-INFO +67 -0
- atrace-0.1.6/README.md +57 -0
- {atrace-0.1.3 → atrace-0.1.6}/pyproject.toml +10 -2
- {atrace-0.1.3 → atrace-0.1.6}/src/atrace/__init__.py +39 -26
- {atrace-0.1.3 → atrace-0.1.6}/src/atrace/model.py +12 -6
- atrace-0.1.6/src/atrace/outputlogger.py +35 -0
- atrace-0.1.6/src/atrace/report.py +74 -0
- atrace-0.1.3/src/atrace/tracers.py → atrace-0.1.6/src/atrace/vartracer.py +13 -40
- {atrace-0.1.3/src → atrace-0.1.6/src/examples}/complete_example.py +10 -6
- atrace-0.1.6/src/examples/function_example.py +9 -0
- atrace-0.1.6/src/examples/global_example.py +13 -0
- {atrace-0.1.3/inspiration → atrace-0.1.6/src/examples}/pyproject.toml +5 -3
- atrace-0.1.6/src/examples/small_example.py +18 -0
- atrace-0.1.6/src/examples/tiny_example.py +4 -0
- atrace-0.1.6/tests/no_test_sample.py +16 -0
- atrace-0.1.6/tests/test_report.py +151 -0
- {atrace-0.1.3 → atrace-0.1.6}/uv.lock +106 -1
- atrace-0.1.3/.vscode/settings.json +0 -11
- atrace-0.1.3/PKG-INFO +0 -77
- atrace-0.1.3/README.md +0 -67
- atrace-0.1.3/inspiration/.python-version +0 -1
- atrace-0.1.3/inspiration/devto_example.py +0 -56
- atrace-0.1.3/inspiration/pytracetool_example.py +0 -43
- atrace-0.1.3/inspiration/simple_program.py +0 -10
- atrace-0.1.3/inspiration/stack_overflow_example.py +0 -67
- atrace-0.1.3/inspiration/uv.lock +0 -378
- atrace-0.1.3/src/atrace/report.py +0 -38
- atrace-0.1.3/src/example_with_classes.py +0 -14
- atrace-0.1.3/src/small_example.py +0 -17
- {atrace-0.1.3 → atrace-0.1.6}/src/atrace/py.typed +0 -0
|
@@ -25,6 +25,12 @@ jobs:
|
|
|
25
25
|
uses: astral-sh/setup-uv@v7
|
|
26
26
|
- name: Install Python 3.13
|
|
27
27
|
run: uv python install 3.13
|
|
28
|
+
- name: Lint
|
|
29
|
+
run: uv run ruff check src tests
|
|
30
|
+
- name: Type check
|
|
31
|
+
run: uv run mypy src tests
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: uv run pytest
|
|
28
34
|
- name: Build
|
|
29
35
|
run: uv build
|
|
30
36
|
- name: Publish
|
atrace-0.1.6/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atrace
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Generate trace tables for programs
|
|
5
|
+
Project-URL: Repository, https://github.com/nwolff/atrace.git
|
|
6
|
+
Author-email: Nicholas Wolff <nwolff@gmail.com>
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: tabulate>=0.9.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Usage
|
|
12
|
+
|
|
13
|
+
Automatically prints a trace table of a program once the execution is finished.
|
|
14
|
+
|
|
15
|
+
Just import the module.
|
|
16
|
+
|
|
17
|
+
An idea of how things look:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Does not work with :
|
|
24
|
+
|
|
25
|
+
- Multithreaded programs
|
|
26
|
+
- Multi-module programs
|
|
27
|
+
- Classes
|
|
28
|
+
|
|
29
|
+
# TODO
|
|
30
|
+
|
|
31
|
+
- Unit tests for the capture part (because the line numbers are so hard). If pytest adds too much magic, then revert to UnitTest
|
|
32
|
+
- Fix line numbers in trace_vars. https://discuss.python.org/t/trace-a-line-after-the-line-but-not-only-before-the-line/89475/7
|
|
33
|
+
This might not even be possible. Start solving it without functions in the scope.
|
|
34
|
+
|
|
35
|
+
- Handle returns (with and without return statements)
|
|
36
|
+
|
|
37
|
+
- Thonny, which adds a lot of indirection and magic. Try and find a way to edit the package directly when running in thonny
|
|
38
|
+
|
|
39
|
+
# Later
|
|
40
|
+
|
|
41
|
+
- localize the names of the line and output columns in the report
|
|
42
|
+
|
|
43
|
+
# Done
|
|
44
|
+
|
|
45
|
+
- Sets the program print to stdout unhindered (this is important for input to work properly),
|
|
46
|
+
but captures the prints at the same time to show in the trace at the end.
|
|
47
|
+
- Emits the trace at the end if the application ends normally and abruptly (exception, signal, etc.)
|
|
48
|
+
- Shows bindings to local variables when entering a function
|
|
49
|
+
- Handles mutations to objects like lists (by copying the previous version and then comparing)
|
|
50
|
+
- Parallel assignations show up properly
|
|
51
|
+
- Changes to variables in other scopes (for instance global)
|
|
52
|
+
|
|
53
|
+
# Build
|
|
54
|
+
|
|
55
|
+
Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
|
|
56
|
+
|
|
57
|
+
# Inspiration
|
|
58
|
+
|
|
59
|
+
https://github.com/DarshanLakshman/PyTracerTool/blob/master/PyTracerTool/pytracertool.py
|
|
60
|
+
|
|
61
|
+
Does almost everything I want, but has many flaws:
|
|
62
|
+
|
|
63
|
+
- it chokes trying to deepcopy some objects
|
|
64
|
+
- it cannot be simply imported into a module
|
|
65
|
+
- it fumbles the handling of output (preventing programs with input from working properly).
|
|
66
|
+
- it repeats values for no good reason
|
|
67
|
+
- its line numbers are very confused
|
atrace-0.1.6/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
|
|
3
|
+
Automatically prints a trace table of a program once the execution is finished.
|
|
4
|
+
|
|
5
|
+
Just import the module.
|
|
6
|
+
|
|
7
|
+
An idea of how things look:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Does not work with :
|
|
14
|
+
|
|
15
|
+
- Multithreaded programs
|
|
16
|
+
- Multi-module programs
|
|
17
|
+
- Classes
|
|
18
|
+
|
|
19
|
+
# TODO
|
|
20
|
+
|
|
21
|
+
- Unit tests for the capture part (because the line numbers are so hard). If pytest adds too much magic, then revert to UnitTest
|
|
22
|
+
- Fix line numbers in trace_vars. https://discuss.python.org/t/trace-a-line-after-the-line-but-not-only-before-the-line/89475/7
|
|
23
|
+
This might not even be possible. Start solving it without functions in the scope.
|
|
24
|
+
|
|
25
|
+
- Handle returns (with and without return statements)
|
|
26
|
+
|
|
27
|
+
- Thonny, which adds a lot of indirection and magic. Try and find a way to edit the package directly when running in thonny
|
|
28
|
+
|
|
29
|
+
# Later
|
|
30
|
+
|
|
31
|
+
- localize the names of the line and output columns in the report
|
|
32
|
+
|
|
33
|
+
# Done
|
|
34
|
+
|
|
35
|
+
- Sets the program print to stdout unhindered (this is important for input to work properly),
|
|
36
|
+
but captures the prints at the same time to show in the trace at the end.
|
|
37
|
+
- Emits the trace at the end if the application ends normally and abruptly (exception, signal, etc.)
|
|
38
|
+
- Shows bindings to local variables when entering a function
|
|
39
|
+
- Handles mutations to objects like lists (by copying the previous version and then comparing)
|
|
40
|
+
- Parallel assignations show up properly
|
|
41
|
+
- Changes to variables in other scopes (for instance global)
|
|
42
|
+
|
|
43
|
+
# Build
|
|
44
|
+
|
|
45
|
+
Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
|
|
46
|
+
|
|
47
|
+
# Inspiration
|
|
48
|
+
|
|
49
|
+
https://github.com/DarshanLakshman/PyTracerTool/blob/master/PyTracerTool/pytracertool.py
|
|
50
|
+
|
|
51
|
+
Does almost everything I want, but has many flaws:
|
|
52
|
+
|
|
53
|
+
- it chokes trying to deepcopy some objects
|
|
54
|
+
- it cannot be simply imported into a module
|
|
55
|
+
- it fumbles the handling of output (preventing programs with input from working properly).
|
|
56
|
+
- it repeats values for no good reason
|
|
57
|
+
- its line numbers are very confused
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "atrace"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.6"
|
|
4
4
|
description = "Generate trace tables for programs"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Nicholas Wolff", email = "nwolff@gmail.com" }]
|
|
@@ -15,4 +15,12 @@ requires = ["hatchling"]
|
|
|
15
15
|
build-backend = "hatchling.build"
|
|
16
16
|
|
|
17
17
|
[dependency-groups]
|
|
18
|
-
dev = [
|
|
18
|
+
dev = [
|
|
19
|
+
"mypy>=1.19.1",
|
|
20
|
+
"pytest>=9.0.2",
|
|
21
|
+
"ruff>=0.14.14",
|
|
22
|
+
"types-tabulate>=0.9.0.20241207",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.uv.workspace]
|
|
26
|
+
members = ["src/examples"]
|
|
@@ -3,27 +3,13 @@ import inspect
|
|
|
3
3
|
import signal
|
|
4
4
|
import sys
|
|
5
5
|
from types import FrameType
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Optional
|
|
7
7
|
|
|
8
|
-
from . import model, report,
|
|
8
|
+
from . import model, outputlogger, report, vartracer
|
|
9
9
|
|
|
10
10
|
trace: model.Trace = []
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def just_kicking_off(frame: FrameType, event: str, arg: Any):
|
|
14
|
-
return None
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_importer_frame() -> Optional[FrameType]:
|
|
18
|
-
# Get the current call stack
|
|
19
|
-
for frame_info in inspect.stack():
|
|
20
|
-
# Filter out internal importlib frames and the current module's frame
|
|
21
|
-
filename = frame_info.filename
|
|
22
|
-
if not filename.startswith("<") and filename != __file__:
|
|
23
|
-
return frame_info.frame
|
|
24
|
-
return None
|
|
25
|
-
|
|
26
|
-
|
|
27
13
|
original_stdout = sys.stdout
|
|
28
14
|
|
|
29
15
|
|
|
@@ -32,29 +18,39 @@ def exit_handler():
|
|
|
32
18
|
report.dump_report(trace)
|
|
33
19
|
|
|
34
20
|
|
|
21
|
+
# https://stackoverflow.com/questions/23468042/the-invocation-of-signal-handler-and-atexit-handler-in-python
|
|
35
22
|
def sig_handler(_signo, _frame):
|
|
36
23
|
sys.exit(0)
|
|
37
24
|
|
|
38
25
|
|
|
26
|
+
def get_importer_frame() -> Optional[FrameType]:
|
|
27
|
+
# Get the current call stack
|
|
28
|
+
for frame_info in inspect.stack():
|
|
29
|
+
# Filter out internal importlib frames and the current module's frame
|
|
30
|
+
filename = frame_info.filename
|
|
31
|
+
if not filename.startswith("<") and filename != __file__:
|
|
32
|
+
return frame_info.frame
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
39
36
|
def setup():
|
|
40
|
-
#
|
|
41
|
-
sys.settrace(just_kicking_off)
|
|
37
|
+
"""See https://docs.python.org/3/library/sys.html#sys.settrace for an explanation of all the convoluted things in here"""
|
|
42
38
|
|
|
43
39
|
# We want to only trace the module that imports us
|
|
44
40
|
importer_frame = get_importer_frame()
|
|
45
41
|
if importer_frame:
|
|
46
42
|
module_of_interest = inspect.getmodule(importer_frame)
|
|
47
43
|
if module_of_interest:
|
|
48
|
-
var_tracer =
|
|
49
|
-
trace=trace, module_of_interest=module_of_interest
|
|
50
|
-
)
|
|
51
|
-
# Setup tracing outside of functions
|
|
52
|
-
importer_frame.f_trace = var_tracer.trace_vars
|
|
44
|
+
var_tracer = vartracer.VarTracer(trace, module_of_interest)
|
|
53
45
|
|
|
54
|
-
# Setup tracing inside of functions
|
|
46
|
+
# Setup tracing inside of functions.
|
|
47
|
+
# Order is important. This must be called before seting the trace function for the current frame
|
|
55
48
|
sys.settrace(var_tracer.trace_vars)
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
# Setup tracing outside of functions
|
|
51
|
+
importer_frame.f_trace = var_tracer.trace_vars
|
|
52
|
+
|
|
53
|
+
sys.stdout = outputlogger.OutputLogger(trace=trace, stdout=sys.stdout)
|
|
58
54
|
|
|
59
55
|
atexit.register(exit_handler)
|
|
60
56
|
catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
|
|
@@ -62,4 +58,21 @@ def setup():
|
|
|
62
58
|
signal.signal(sig, sig_handler)
|
|
63
59
|
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
def var_trace_for_code(code: str) -> model.Trace:
|
|
62
|
+
trace: model.Trace = []
|
|
63
|
+
# We want to only trace the module that imports us
|
|
64
|
+
importer_frame = get_importer_frame()
|
|
65
|
+
if importer_frame:
|
|
66
|
+
module_of_interest = inspect.getmodule(importer_frame)
|
|
67
|
+
if module_of_interest:
|
|
68
|
+
var_tracer = vartracer.VarTracer(trace, module_of_interest)
|
|
69
|
+
try:
|
|
70
|
+
sys.settrace(var_tracer.trace_vars)
|
|
71
|
+
exec(code)
|
|
72
|
+
finally:
|
|
73
|
+
sys.settrace(None)
|
|
74
|
+
return trace
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if "pytest" not in sys.modules:
|
|
78
|
+
setup()
|
|
@@ -2,28 +2,34 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import Any, TypeAlias
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
@dataclass
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
6
|
class PrintEvent:
|
|
7
7
|
text: str
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@dataclass
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
11
|
class Variable:
|
|
12
|
-
|
|
12
|
+
scope: str
|
|
13
13
|
name: str
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@dataclass
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ReturnEvent:
|
|
18
|
+
function_name: str
|
|
19
|
+
return_value: Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
17
23
|
class VariableChangeEvent:
|
|
18
24
|
variable: Variable
|
|
19
25
|
value: Any
|
|
20
26
|
|
|
21
27
|
|
|
22
|
-
@dataclass
|
|
28
|
+
@dataclass(frozen=True)
|
|
23
29
|
class TraceItem:
|
|
24
30
|
line_no: int
|
|
25
31
|
function_name: str
|
|
26
|
-
event: PrintEvent | VariableChangeEvent |
|
|
32
|
+
event: PrintEvent | VariableChangeEvent | ReturnEvent
|
|
27
33
|
|
|
28
34
|
|
|
29
35
|
Trace: TypeAlias = list[TraceItem]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TextIO
|
|
3
|
+
|
|
4
|
+
from . import model
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OutputLogger:
|
|
8
|
+
"""
|
|
9
|
+
OutputLogger wraps stdout, passing down writes and flushes to it, while simultaneously capturing the text to the trace.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, trace: model.Trace, stdout: TextIO):
|
|
13
|
+
self.trace = trace
|
|
14
|
+
self.stdout = stdout
|
|
15
|
+
|
|
16
|
+
def write(self, text: str) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Write to stdout and record the text in the trace
|
|
19
|
+
"""
|
|
20
|
+
self.stdout.write(text)
|
|
21
|
+
|
|
22
|
+
frame = sys._getframe(1)
|
|
23
|
+
# An alternative that uses a public API, but then the type checker bothers me
|
|
24
|
+
# frame = inspect.currentframe().f_back
|
|
25
|
+
|
|
26
|
+
self.trace.append(
|
|
27
|
+
model.TraceItem(
|
|
28
|
+
line_no=frame.f_lineno,
|
|
29
|
+
function_name=frame.f_code.co_name,
|
|
30
|
+
event=model.PrintEvent(text),
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def flush(self):
|
|
35
|
+
self.stdout.flush()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, TypeAlias
|
|
3
|
+
|
|
4
|
+
from tabulate import tabulate
|
|
5
|
+
|
|
6
|
+
from . import model
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Instant:
|
|
11
|
+
line_no: int
|
|
12
|
+
variable_changes: dict[model.Variable, Any]
|
|
13
|
+
output: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Instants: TypeAlias = list[Instant]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def trace_to_instants(trace: model.Trace) -> Instants:
|
|
20
|
+
instants = []
|
|
21
|
+
instant = None
|
|
22
|
+
for trace_item in trace:
|
|
23
|
+
if instant is None or instant.line_no != trace_item.line_no:
|
|
24
|
+
instant = Instant(trace_item.line_no, {}, "")
|
|
25
|
+
instants.append(instant)
|
|
26
|
+
|
|
27
|
+
match trace_item.event:
|
|
28
|
+
case model.PrintEvent(text):
|
|
29
|
+
instant.output += text
|
|
30
|
+
case model.VariableChangeEvent(variable, value):
|
|
31
|
+
# Don't clobber an existing value a variable if it changes again on the same line
|
|
32
|
+
# (this happens for example in tight loops)
|
|
33
|
+
if variable in instant.variable_changes:
|
|
34
|
+
instant = Instant(trace_item.line_no, {}, "")
|
|
35
|
+
instants.append(instant)
|
|
36
|
+
instant.variable_changes[variable] = value
|
|
37
|
+
case model.ReturnEvent():
|
|
38
|
+
pass # XXX
|
|
39
|
+
|
|
40
|
+
return instants
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def variable_to_column_name(var: model.Variable) -> str:
|
|
44
|
+
return var.name if var.scope == "<module>" else f"({var.scope}) {var.name}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def formatted_table_from_instants(instants: Instants) -> str:
|
|
48
|
+
variables = []
|
|
49
|
+
|
|
50
|
+
for instant in instants:
|
|
51
|
+
for variable in instant.variable_changes:
|
|
52
|
+
if variable not in variables:
|
|
53
|
+
variables.append(variable)
|
|
54
|
+
|
|
55
|
+
LINE_NO, OUTPUT = "ligne", "affichage"
|
|
56
|
+
|
|
57
|
+
table = []
|
|
58
|
+
for instant in instants:
|
|
59
|
+
row: dict[str, Any] = {}
|
|
60
|
+
table.append(row)
|
|
61
|
+
|
|
62
|
+
row[LINE_NO] = instant.line_no
|
|
63
|
+
for variable in variables:
|
|
64
|
+
cell_for_variable = instant.variable_changes.get(variable, "")
|
|
65
|
+
row[variable_to_column_name(variable)] = cell_for_variable
|
|
66
|
+
row[OUTPUT] = instant.output
|
|
67
|
+
|
|
68
|
+
return tabulate(table, headers="keys", tablefmt="simple_outline")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def dump_report(trace: model.Trace) -> None:
|
|
72
|
+
instants = trace_to_instants(trace)
|
|
73
|
+
formatted_table = formatted_table_from_instants(instants)
|
|
74
|
+
print(formatted_table)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import inspect
|
|
3
|
-
import sys
|
|
4
3
|
from types import FrameType, ModuleType
|
|
5
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
6
5
|
|
|
7
6
|
from atrace import model
|
|
8
7
|
|
|
@@ -24,7 +23,7 @@ def copy_carefully(d: dict[str, Any]):
|
|
|
24
23
|
for k, v in d.items():
|
|
25
24
|
try:
|
|
26
25
|
v_copy = copy.deepcopy(v)
|
|
27
|
-
except:
|
|
26
|
+
except Exception:
|
|
28
27
|
v_copy = v
|
|
29
28
|
res[k] = v_copy
|
|
30
29
|
return res
|
|
@@ -34,14 +33,21 @@ class VarTracer:
|
|
|
34
33
|
def __init__(self, trace: model.Trace, module_of_interest: ModuleType):
|
|
35
34
|
self.trace = trace
|
|
36
35
|
self.module_of_interest = module_of_interest
|
|
37
|
-
self.last_locals = {}
|
|
36
|
+
self.last_locals: dict[str, Any] = {}
|
|
38
37
|
|
|
39
38
|
def trace_vars(self, frame: FrameType, event: str, arg: Any):
|
|
40
39
|
if inspect.getmodule(frame) != self.module_of_interest:
|
|
41
40
|
return
|
|
42
41
|
if event == "return":
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
self.trace.append(
|
|
43
|
+
model.TraceItem(
|
|
44
|
+
line_no=frame.f_lineno,
|
|
45
|
+
function_name=frame.f_code.co_name,
|
|
46
|
+
event=model.ReturnEvent(
|
|
47
|
+
function_name=frame.f_code.co_name, return_value=arg # XXX
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
45
51
|
|
|
46
52
|
code = frame.f_code
|
|
47
53
|
|
|
@@ -51,17 +57,15 @@ class VarTracer:
|
|
|
51
57
|
old_locals = self.last_locals[code.co_name]
|
|
52
58
|
|
|
53
59
|
locals_now = copy_carefully(filtered_variables(frame.f_locals))
|
|
54
|
-
# print("locals_now", locals_now)
|
|
55
60
|
|
|
56
61
|
for var, new_val in filtered_variables(locals_now).items():
|
|
57
62
|
if var not in old_locals or old_locals[var] != new_val:
|
|
58
|
-
# print("çççç", frame.f_lineno, var)
|
|
59
63
|
self.trace.append(
|
|
60
64
|
model.TraceItem(
|
|
61
65
|
line_no=frame.f_lineno,
|
|
62
66
|
function_name=frame.f_code.co_name,
|
|
63
67
|
event=model.VariableChangeEvent(
|
|
64
|
-
variable=model.Variable(
|
|
68
|
+
variable=model.Variable(scope=code.co_name, name=var),
|
|
65
69
|
value=new_val,
|
|
66
70
|
),
|
|
67
71
|
)
|
|
@@ -69,34 +73,3 @@ class VarTracer:
|
|
|
69
73
|
|
|
70
74
|
self.last_locals[code.co_name] = locals_now
|
|
71
75
|
return self.trace_vars
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class OutputLogger(object):
|
|
75
|
-
"""
|
|
76
|
-
OutputLogger captures and logs the output produced by print statements during code execution.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
def __init__(self, trace: model.Trace, stdout: TextIO):
|
|
80
|
-
self.trace = trace
|
|
81
|
-
self.stdout = stdout
|
|
82
|
-
|
|
83
|
-
def write(self, text: str) -> None:
|
|
84
|
-
"""
|
|
85
|
-
Write to stdout and record it in the trace
|
|
86
|
-
"""
|
|
87
|
-
self.stdout.write(text)
|
|
88
|
-
|
|
89
|
-
frame = sys._getframe(1)
|
|
90
|
-
# An alternative that uses a public API, but then the type checker bothers me
|
|
91
|
-
# frame = inspect.currentframe().f_back
|
|
92
|
-
|
|
93
|
-
self.trace.append(
|
|
94
|
-
model.TraceItem(
|
|
95
|
-
line_no=frame.f_lineno,
|
|
96
|
-
function_name=frame.f_code.co_name,
|
|
97
|
-
event=model.PrintEvent(text),
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def flush(self):
|
|
102
|
-
self.stdout.flush()
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import atrace
|
|
1
|
+
import atrace # noqa
|
|
2
2
|
|
|
3
3
|
x, y = 3, 6
|
|
4
4
|
|
|
5
5
|
while x < y:
|
|
6
6
|
x = x + 1
|
|
7
|
-
y = y - 1
|
|
8
7
|
|
|
9
8
|
print("x", x)
|
|
10
|
-
print("y", x)
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
t = (1, 2)
|
|
11
|
+
|
|
12
|
+
kids = ["riri", "fifi", "loulou"]
|
|
13
|
+
while kids:
|
|
14
|
+
print(kids.pop(0))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
for i in range(5):
|
|
18
|
+
print(i)
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
def double(a):
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
|
-
name = "
|
|
2
|
+
name = "examples"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
7
|
-
dependencies = [
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"tabulate>=0.9.0",
|
|
9
|
+
]
|