atrace 0.1.5__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.
@@ -25,8 +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: Check
29
- run: uv run ruff check src
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
30
34
  - name: Build
31
35
  run: uv build
32
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.5"
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,7 +15,12 @@ requires = ["hatchling"]
15
15
  build-backend = "hatchling.build"
16
16
 
17
17
  [dependency-groups]
18
- dev = ["mypy>=1.19.1", "ruff>=0.14.14"]
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
+ ]
19
24
 
20
25
  [tool.uv.workspace]
21
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 Any, Optional
6
+ from typing import Optional
7
7
 
8
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
 
@@ -37,12 +23,19 @@ def sig_handler(_signo, _frame):
37
23
  sys.exit(0)
38
24
 
39
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
+
40
36
  def setup():
41
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
- # This kicks off the tracing machinery (so that the lines below work)
44
- sys.settrace(just_kicking_off)
45
-
46
39
  # We want to only trace the module that imports us
47
40
  importer_frame = get_importer_frame()
48
41
  if importer_frame:
@@ -50,12 +43,13 @@ def setup():
50
43
  if module_of_interest:
51
44
  var_tracer = vartracer.VarTracer(trace, module_of_interest)
52
45
 
46
+ # Setup tracing inside of functions.
47
+ # Order is important. This must be called before seting the trace function for the current frame
48
+ sys.settrace(var_tracer.trace_vars)
49
+
53
50
  # Setup tracing outside of functions
54
51
  importer_frame.f_trace = var_tracer.trace_vars
55
52
 
56
- # Setup tracing inside of functions
57
- sys.settrace(var_tracer.trace_vars)
58
-
59
53
  sys.stdout = outputlogger.OutputLogger(trace=trace, stdout=sys.stdout)
60
54
 
61
55
  atexit.register(exit_handler)
@@ -64,4 +58,21 @@ def setup():
64
58
  signal.signal(sig, sig_handler)
65
59
 
66
60
 
67
- setup()
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
10
  @dataclass(frozen=True)
11
11
  class Variable:
12
- function_name: str | None
12
+ scope: str
13
13
  name: str
14
14
 
15
15
 
16
+ @dataclass(frozen=True)
17
+ class ReturnEvent:
18
+ function_name: str
19
+ return_value: Any
20
+
21
+
16
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]
@@ -6,7 +6,7 @@ from . import model
6
6
 
7
7
  class OutputLogger:
8
8
  """
9
- OutputLogger wraps stdout, writing to it and simultaneously capturing the text to the trace.
9
+ OutputLogger wraps stdout, passing down writes and flushes to it, while simultaneously capturing the text to the trace.
10
10
  """
11
11
 
12
12
  def __init__(self, trace: model.Trace, stdout: TextIO):
@@ -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)
@@ -33,13 +33,21 @@ class VarTracer:
33
33
  def __init__(self, trace: model.Trace, module_of_interest: ModuleType):
34
34
  self.trace = trace
35
35
  self.module_of_interest = module_of_interest
36
- self.last_locals = {}
36
+ self.last_locals: dict[str, Any] = {}
37
37
 
38
38
  def trace_vars(self, frame: FrameType, event: str, arg: Any):
39
39
  if inspect.getmodule(frame) != self.module_of_interest:
40
40
  return
41
41
  if event == "return":
42
- return
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
+ )
43
51
 
44
52
  code = frame.f_code
45
53
 
@@ -57,9 +65,7 @@ class VarTracer:
57
65
  line_no=frame.f_lineno,
58
66
  function_name=frame.f_code.co_name,
59
67
  event=model.VariableChangeEvent(
60
- variable=model.Variable(
61
- function_name=code.co_name, name=var
62
- ),
68
+ variable=model.Variable(scope=code.co_name, name=var),
63
69
  value=new_val,
64
70
  ),
65
71
  )
@@ -7,6 +7,8 @@ while x < y:
7
7
 
8
8
  print("x", x)
9
9
 
10
+ t = (1, 2)
11
+
10
12
  kids = ["riri", "fifi", "loulou"]
11
13
  while kids:
12
14
  print(kids.pop(0))
@@ -21,8 +23,6 @@ def double(a):
21
23
  return b
22
24
 
23
25
 
24
- ligne = 666 # To test if it messes up the table header ligne
25
-
26
26
  print(double(6))
27
27
 
28
28
 
@@ -0,0 +1,9 @@
1
+ import atrace # noqa
2
+
3
+
4
+ def f(a, b):
5
+ c = a + b
6
+ return c
7
+
8
+
9
+ print(f(3, 14))
@@ -0,0 +1,13 @@
1
+ import atrace # noqa
2
+
3
+ c = "first value of c"
4
+
5
+
6
+ def f(a, b):
7
+ global c
8
+ c = a + b
9
+ d = a - b
10
+ return c, d
11
+
12
+
13
+ print(f(3, 14))
@@ -0,0 +1,18 @@
1
+ import atrace # noqa
2
+
3
+ x, y = 1, 3
4
+
5
+ while x < y:
6
+ x = x + 1
7
+
8
+ print("x:", x)
9
+
10
+ t = 1, 2
11
+
12
+
13
+ def f(a, b):
14
+ c = a + " " + b
15
+ return c + "!"
16
+
17
+
18
+ print(f("Bonjour", "tout le monde"))
@@ -0,0 +1,16 @@
1
+ # import sys
2
+
3
+ import atrace
4
+
5
+ # from atrace import model, vartracer
6
+
7
+ code = """
8
+ print("hello")
9
+ x = 1
10
+ x = x + 1
11
+ y, z = 7, 8
12
+ """
13
+
14
+ trace = atrace.var_trace_for_code(code)
15
+ print(trace)
16
+ assert 1 == 0 # To show the output
@@ -0,0 +1,151 @@
1
+ import pytest
2
+
3
+ from atrace.model import (
4
+ PrintEvent,
5
+ ReturnEvent,
6
+ TraceItem,
7
+ Variable,
8
+ VariableChangeEvent,
9
+ )
10
+ from atrace.report import Instant, trace_to_instants
11
+
12
+
13
+ def test_simple_assignments():
14
+ trace = [
15
+ TraceItem(
16
+ line_no=1,
17
+ function_name="<module>",
18
+ event=VariableChangeEvent(Variable("<module>", "x"), 1),
19
+ ),
20
+ TraceItem(
21
+ line_no=2,
22
+ function_name="<module>",
23
+ event=VariableChangeEvent(Variable("<module>", "y"), 2),
24
+ ),
25
+ ]
26
+
27
+ assert trace_to_instants(trace) == [
28
+ Instant(
29
+ line_no=1,
30
+ variable_changes={Variable("<module>", "x"): 1},
31
+ output="",
32
+ ),
33
+ Instant(
34
+ line_no=2,
35
+ variable_changes={Variable("<module>", "y"): 2},
36
+ output="",
37
+ ),
38
+ ]
39
+
40
+
41
+ def test_parallel_assignments():
42
+ trace = [
43
+ TraceItem(
44
+ line_no=1,
45
+ function_name="<module>",
46
+ event=VariableChangeEvent(Variable("<module>", "x"), 1),
47
+ ),
48
+ TraceItem(
49
+ line_no=1,
50
+ function_name="<module>",
51
+ event=VariableChangeEvent(Variable("<module>", "y"), 2),
52
+ ),
53
+ ]
54
+
55
+ assert trace_to_instants(trace) == [
56
+ Instant(
57
+ line_no=1,
58
+ variable_changes={
59
+ Variable("<module>", "x"): 1,
60
+ Variable("<module>", "y"): 2,
61
+ },
62
+ output="",
63
+ )
64
+ ]
65
+
66
+
67
+ def test_assignments_in_tight_loop():
68
+ trace = [
69
+ TraceItem(
70
+ line_no=1,
71
+ function_name="<module>",
72
+ event=VariableChangeEvent(Variable("<module>", "x"), 1),
73
+ ),
74
+ TraceItem(
75
+ line_no=1,
76
+ function_name="<module>",
77
+ event=VariableChangeEvent(Variable("<module>", "x"), 2),
78
+ ),
79
+ ]
80
+
81
+ assert trace_to_instants(trace) == [
82
+ Instant(
83
+ line_no=1,
84
+ variable_changes={Variable("<module>", "x"): 1},
85
+ output="",
86
+ ),
87
+ Instant(
88
+ line_no=1,
89
+ variable_changes={Variable("<module>", "x"): 2},
90
+ output="",
91
+ ),
92
+ ]
93
+
94
+
95
+ def test_simple_output():
96
+ trace = [
97
+ TraceItem(line_no=1, function_name="<module>", event=PrintEvent("hello")),
98
+ TraceItem(line_no=2, function_name="<module>", event=PrintEvent("world")),
99
+ ]
100
+
101
+ assert trace_to_instants(trace) == [
102
+ Instant(
103
+ line_no=1,
104
+ variable_changes={},
105
+ output="hello",
106
+ ),
107
+ Instant(
108
+ line_no=2,
109
+ variable_changes={},
110
+ output="world",
111
+ ),
112
+ ]
113
+
114
+
115
+ def test_coalesce_output():
116
+ trace = [
117
+ TraceItem(line_no=1, function_name="<module>", event=PrintEvent("hello")),
118
+ TraceItem(line_no=1, function_name="<module>", event=PrintEvent("world")),
119
+ ]
120
+
121
+ assert trace_to_instants(trace) == [
122
+ Instant(
123
+ line_no=1,
124
+ variable_changes={},
125
+ output="helloworld",
126
+ ),
127
+ ]
128
+
129
+
130
+ @pytest.mark.skip(reason="no way of currently testing this")
131
+ def test_return_value():
132
+ trace = [
133
+ TraceItem(
134
+ line_no=1,
135
+ function_name="f",
136
+ event=VariableChangeEvent(Variable("f", "x"), 1),
137
+ ),
138
+ TraceItem(
139
+ line_no=2,
140
+ function_name="f",
141
+ event=ReturnEvent("f>", "a return value"),
142
+ ),
143
+ ]
144
+
145
+ assert trace_to_instants(trace) == [
146
+ Instant(
147
+ line_no=1,
148
+ variable_changes={Variable("f", "x"): 1},
149
+ output="",
150
+ ),
151
+ ]
@@ -10,7 +10,7 @@ members = [
10
10
 
11
11
  [[package]]
12
12
  name = "atrace"
13
- version = "0.1.5"
13
+ version = "0.1.6"
14
14
  source = { editable = "." }
15
15
  dependencies = [
16
16
  { name = "tabulate" },
@@ -19,7 +19,9 @@ dependencies = [
19
19
  [package.dev-dependencies]
20
20
  dev = [
21
21
  { name = "mypy" },
22
+ { name = "pytest" },
22
23
  { name = "ruff" },
24
+ { name = "types-tabulate" },
23
25
  ]
24
26
 
25
27
  [package.metadata]
@@ -28,7 +30,18 @@ requires-dist = [{ name = "tabulate", specifier = ">=0.9.0" }]
28
30
  [package.metadata.requires-dev]
29
31
  dev = [
30
32
  { name = "mypy", specifier = ">=1.19.1" },
33
+ { name = "pytest", specifier = ">=9.0.2" },
31
34
  { name = "ruff", specifier = ">=0.14.14" },
35
+ { name = "types-tabulate", specifier = ">=0.9.0.20241207" },
36
+ ]
37
+
38
+ [[package]]
39
+ name = "colorama"
40
+ version = "0.4.6"
41
+ source = { registry = "https://pypi.org/simple" }
42
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
43
+ wheels = [
44
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
32
45
  ]
33
46
 
34
47
  [[package]]
@@ -42,6 +55,27 @@ dependencies = [
42
55
  [package.metadata]
43
56
  requires-dist = [{ name = "tabulate", specifier = ">=0.9.0" }]
44
57
 
58
+ [[package]]
59
+ name = "exceptiongroup"
60
+ version = "1.3.1"
61
+ source = { registry = "https://pypi.org/simple" }
62
+ dependencies = [
63
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
64
+ ]
65
+ sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
66
+ wheels = [
67
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
68
+ ]
69
+
70
+ [[package]]
71
+ name = "iniconfig"
72
+ version = "2.3.0"
73
+ source = { registry = "https://pypi.org/simple" }
74
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
75
+ wheels = [
76
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
77
+ ]
78
+
45
79
  [[package]]
46
80
  name = "librt"
47
81
  version = "0.7.8"
@@ -170,6 +204,15 @@ wheels = [
170
204
  { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
171
205
  ]
172
206
 
207
+ [[package]]
208
+ name = "packaging"
209
+ version = "26.0"
210
+ source = { registry = "https://pypi.org/simple" }
211
+ sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
212
+ wheels = [
213
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
214
+ ]
215
+
173
216
  [[package]]
174
217
  name = "pathspec"
175
218
  version = "1.0.4"
@@ -179,6 +222,42 @@ wheels = [
179
222
  { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
180
223
  ]
181
224
 
225
+ [[package]]
226
+ name = "pluggy"
227
+ version = "1.6.0"
228
+ source = { registry = "https://pypi.org/simple" }
229
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
230
+ wheels = [
231
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
232
+ ]
233
+
234
+ [[package]]
235
+ name = "pygments"
236
+ version = "2.19.2"
237
+ source = { registry = "https://pypi.org/simple" }
238
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
239
+ wheels = [
240
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
241
+ ]
242
+
243
+ [[package]]
244
+ name = "pytest"
245
+ version = "9.0.2"
246
+ source = { registry = "https://pypi.org/simple" }
247
+ dependencies = [
248
+ { name = "colorama", marker = "sys_platform == 'win32'" },
249
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
250
+ { name = "iniconfig" },
251
+ { name = "packaging" },
252
+ { name = "pluggy" },
253
+ { name = "pygments" },
254
+ { name = "tomli", marker = "python_full_version < '3.11'" },
255
+ ]
256
+ sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
257
+ wheels = [
258
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
259
+ ]
260
+
182
261
  [[package]]
183
262
  name = "ruff"
184
263
  version = "0.14.14"
@@ -268,6 +347,15 @@ wheels = [
268
347
  { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
269
348
  ]
270
349
 
350
+ [[package]]
351
+ name = "types-tabulate"
352
+ version = "0.9.0.20241207"
353
+ source = { registry = "https://pypi.org/simple" }
354
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" }
355
+ wheels = [
356
+ { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" },
357
+ ]
358
+
271
359
  [[package]]
272
360
  name = "typing-extensions"
273
361
  version = "4.15.0"
atrace-0.1.5/PKG-INFO DELETED
@@ -1,86 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: atrace
3
- Version: 0.1.5
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 animated example of a trace table: https://www.101computing.net/using-trace-tables/
18
-
19
- An idea of how things look:
20
-
21
- ```
22
- +-----------+-----+-----+---------+---------------+-----------------------+-------------------------+
23
- | ligne | x | y | (f) a | (f) b | (f) c | affichage |
24
- +===========+=====+=====+=========+===============+=======================+=========================+
25
- | 5 | 1 | 3 | | | | |
26
- | 7 | 2 | | | | | |
27
- | 5 | | 2 | | | | |
28
- | 7 | 3 | | | | | |
29
- | 5 | | 1 | | | | |
30
- | 7 | 4 | | | | | |
31
- | 5 | | | | | | |
32
- | 9 | | | | | | somme: 4 |
33
- | 12 | | | Bonjour | tout le monde | | |
34
- | 14 | | | | | Bonjour tout le monde | Bonjour tout le monde ! |
35
- +-----------+-----+-----+---------+---------------+-----------------------+-------------------------+
36
- ```
37
-
38
- Does not work with :
39
-
40
- - Multithreaded programs
41
- - Multi-module programs
42
- - Classes
43
-
44
- # TODO
45
-
46
- - 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
47
- This is a bitch
48
- - Thonny, which adds a lot of indirection and magic
49
-
50
- # Later
51
-
52
- - Make robust. In other words should never raise an exception.
53
- - mypy, unit-tests. Integrate in build pipeline
54
- - localize the names of the line and output columns in the report
55
- - Handle classes better
56
-
57
- # Possible enhancements
58
-
59
- - More details when recursive invocations
60
- - Think about how to show returns
61
- - Find if there could be a good use for colors in the trace
62
-
63
- # Done
64
-
65
- - Sets the program print to stdout unhindered (this is important for input to work properly),
66
- but captures the prints at the same time to show in the trace at the end.
67
- - Emits the trace at the end if the application ends normally and abruptly (exception, signal, etc.)
68
- - Shows bindings to local variables when entering a function
69
- - Handles mutations to objects like lists (by copying the previous version and then comparing)
70
- - Parallel assignations show up properly
71
-
72
- # Build
73
-
74
- Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
75
-
76
- # Inspiration
77
-
78
- https://github.com/DarshanLakshman/PyTracerTool/blob/master/PyTracerTool/pytracertool.py
79
-
80
- Does almost everything I want, but has many flaws:
81
-
82
- - it chokes trying to deepcopy some objects
83
- - it cannot be simply imported into a module
84
- - it fumbles the handling of output (preventing programs with input from working properly).
85
- - it repeats values for no good reason
86
- - its line numbers are very confused
atrace-0.1.5/README.md DELETED
@@ -1,76 +0,0 @@
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 animated example of a trace table: https://www.101computing.net/using-trace-tables/
8
-
9
- An idea of how things look:
10
-
11
- ```
12
- +-----------+-----+-----+---------+---------------+-----------------------+-------------------------+
13
- | ligne | x | y | (f) a | (f) b | (f) c | affichage |
14
- +===========+=====+=====+=========+===============+=======================+=========================+
15
- | 5 | 1 | 3 | | | | |
16
- | 7 | 2 | | | | | |
17
- | 5 | | 2 | | | | |
18
- | 7 | 3 | | | | | |
19
- | 5 | | 1 | | | | |
20
- | 7 | 4 | | | | | |
21
- | 5 | | | | | | |
22
- | 9 | | | | | | somme: 4 |
23
- | 12 | | | Bonjour | tout le monde | | |
24
- | 14 | | | | | Bonjour tout le monde | Bonjour tout le monde ! |
25
- +-----------+-----+-----+---------+---------------+-----------------------+-------------------------+
26
- ```
27
-
28
- Does not work with :
29
-
30
- - Multithreaded programs
31
- - Multi-module programs
32
- - Classes
33
-
34
- # TODO
35
-
36
- - 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
37
- This is a bitch
38
- - Thonny, which adds a lot of indirection and magic
39
-
40
- # Later
41
-
42
- - Make robust. In other words should never raise an exception.
43
- - mypy, unit-tests. Integrate in build pipeline
44
- - localize the names of the line and output columns in the report
45
- - Handle classes better
46
-
47
- # Possible enhancements
48
-
49
- - More details when recursive invocations
50
- - Think about how to show returns
51
- - Find if there could be a good use for colors in the trace
52
-
53
- # Done
54
-
55
- - Sets the program print to stdout unhindered (this is important for input to work properly),
56
- but captures the prints at the same time to show in the trace at the end.
57
- - Emits the trace at the end if the application ends normally and abruptly (exception, signal, etc.)
58
- - Shows bindings to local variables when entering a function
59
- - Handles mutations to objects like lists (by copying the previous version and then comparing)
60
- - Parallel assignations show up properly
61
-
62
- # Build
63
-
64
- Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
65
-
66
- # Inspiration
67
-
68
- https://github.com/DarshanLakshman/PyTracerTool/blob/master/PyTracerTool/pytracertool.py
69
-
70
- Does almost everything I want, but has many flaws:
71
-
72
- - it chokes trying to deepcopy some objects
73
- - it cannot be simply imported into a module
74
- - it fumbles the handling of output (preventing programs with input from working properly).
75
- - it repeats values for no good reason
76
- - its line numbers are very confused
@@ -1,98 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Any
3
-
4
- from tabulate import tabulate
5
-
6
- from . import model
7
-
8
-
9
- def collect_variable_list(trace: model.Trace) -> list[model.Variable]:
10
- variable_list = []
11
- for traceItem in trace:
12
- match traceItem.event:
13
- case model.VariableChangeEvent(variable_name, _):
14
- if variable_name not in variable_list:
15
- variable_list.append(variable_name)
16
- return variable_list
17
-
18
-
19
- def coalesce_print_events(trace: model.Trace) -> model.Trace:
20
- last_print_item = None
21
- for trace_item in trace:
22
- match trace_item.event:
23
- case model.PrintEvent(text):
24
- if (
25
- last_print_item is None
26
- or trace_item.line_no != last_print_item.line_no
27
- ):
28
- last_print_item = trace_item
29
- else:
30
- last_print_item.event.text += text
31
- trace_item.event = None # Mark for deletion
32
- case _:
33
- last_print_item = None
34
-
35
- return [item for item in trace if item.event is not None]
36
-
37
-
38
- def contains_prints(trace: model.Trace) -> bool:
39
- for trace_item in trace:
40
- match trace_item.event:
41
- case model.PrintEvent:
42
- return True
43
- return False
44
-
45
-
46
- def variable_to_column_name(var: model.Variable) -> str:
47
- context = None if var.function_name == "<module>" else var.function_name
48
- return "(" + context + ") " + var.name if context else var.name
49
-
50
-
51
- @dataclass
52
- class Line:
53
- line_no: int
54
- variable_changes: dict[str, Any]
55
- output: str | None
56
-
57
-
58
- def dump_report(trace: model.Trace) -> None:
59
- trace = coalesce_print_events(trace)
60
-
61
- # Build intermediate "lines" datastructure, that collects all events that happen on a given line
62
- lines = []
63
- current_line = None
64
- for trace_item in trace:
65
- if current_line is None or current_line.line_no != trace_item.line_no:
66
- current_line = Line(trace_item.line_no, {}, None)
67
- lines.append(current_line)
68
-
69
- match trace_item.event:
70
- case model.VariableChangeEvent(variable, value):
71
- # Don't clobber a new value if it changes on the same line
72
- # (this happens for example in tight loops)
73
- if variable in current_line.variable_changes:
74
- current_line = Line(trace_item.line_no, {}, None)
75
- lines.append(current_line)
76
-
77
- current_line.variable_changes[variable_to_column_name(variable)] = value
78
- case model.PrintEvent(text):
79
- current_line.output = text
80
-
81
- # The extra spaces so the names cannot collide with variable names
82
- LINE_NO, OUTPUT = " ligne", "affichage "
83
-
84
- variable_list = collect_variable_list(trace)
85
- headers = [LINE_NO] + [variable_to_column_name(v) for v in variable_list] + [OUTPUT]
86
- table = []
87
- for line in lines:
88
- table_row = [line.line_no]
89
- for column_name in headers[1:-1]:
90
- variable_value = line.variable_changes.get(column_name)
91
- if variable_value:
92
- table_row.append(variable_value)
93
- else:
94
- table_row.append(None)
95
- table_row.append(line.output)
96
- table.append(table_row)
97
-
98
- print(tabulate(table, headers=headers, tablefmt="outline"))
@@ -1,14 +0,0 @@
1
- import atrace # noqa
2
-
3
-
4
- class Animal:
5
- def __init__(self, name):
6
- self.name = name
7
-
8
-
9
- class Cat(Animal):
10
- pass
11
-
12
-
13
- ivy = Cat("Ivy Megaman")
14
- print(ivy)
@@ -1,17 +0,0 @@
1
- import atrace # noqa
2
-
3
- x, y = 1, 3
4
-
5
- while y > 0:
6
- x = x + 1
7
- y = y - 1
8
-
9
- print("somme: ", x)
10
-
11
-
12
- def f(a, b):
13
- c = a + " " + b
14
- print(c, "!")
15
-
16
-
17
- f("Bonjour", "tout le monde")
File without changes
File without changes