atrace 0.1.1__py3-none-any.whl → 0.1.3__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.
- atrace/__init__.py +42 -78
- atrace/model.py +29 -0
- atrace/report.py +38 -0
- atrace/tracers.py +102 -0
- atrace-0.1.3.dist-info/METADATA +77 -0
- atrace-0.1.3.dist-info/RECORD +8 -0
- atrace-0.1.1.dist-info/METADATA +0 -65
- atrace-0.1.1.dist-info/RECORD +0 -5
- {atrace-0.1.1.dist-info → atrace-0.1.3.dist-info}/WHEEL +0 -0
atrace/__init__.py
CHANGED
|
@@ -1,101 +1,65 @@
|
|
|
1
|
-
import
|
|
1
|
+
import atexit
|
|
2
2
|
import inspect
|
|
3
|
+
import signal
|
|
3
4
|
import sys
|
|
4
5
|
from types import FrameType
|
|
5
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Optional
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
last_locals = {}
|
|
8
|
+
from . import model, report, tracers
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
[
|
|
12
|
-
"tid",
|
|
13
|
-
]
|
|
14
|
-
)
|
|
10
|
+
trace: model.Trace = []
|
|
15
11
|
|
|
16
12
|
|
|
17
|
-
def
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def copy_carefully_using_ignored_variables(d: dict[str, Any]):
|
|
22
|
-
res = {}
|
|
23
|
-
for k, v in d.items():
|
|
24
|
-
if not ignore_variable(k):
|
|
25
|
-
res[k] = copy.deepcopy(v)
|
|
26
|
-
return res
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def copy_carefully(d: dict[str, Any]):
|
|
30
|
-
res = {}
|
|
31
|
-
for k, v in d.items():
|
|
32
|
-
try:
|
|
33
|
-
v_copy = copy.deepcopy(v)
|
|
34
|
-
except:
|
|
35
|
-
v_copy = v
|
|
36
|
-
res[k] = v_copy
|
|
37
|
-
return res
|
|
13
|
+
def just_kicking_off(frame: FrameType, event: str, arg: Any):
|
|
14
|
+
return None
|
|
38
15
|
|
|
39
16
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
locals_now = copy_carefully(frame.f_locals)
|
|
49
|
-
global last_locals
|
|
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
|
|
50
25
|
|
|
51
|
-
if last_locals is None: # We're being unloaded, it's the end of the program
|
|
52
|
-
return None
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
last_locals[code.co_name] = locals_now
|
|
56
|
-
return trace_vars
|
|
27
|
+
original_stdout = sys.stdout
|
|
57
28
|
|
|
58
|
-
old_locals = last_locals[code.co_name]
|
|
59
29
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
print(f"[{lineno}] NEW {var} = {new_val}")
|
|
64
|
-
elif old_locals[var] != new_val:
|
|
65
|
-
print(f"[{lineno}] MODIFIED {var}: {old_locals[var]} → {new_val}")
|
|
30
|
+
def exit_handler():
|
|
31
|
+
sys.stdout = original_stdout
|
|
32
|
+
report.dump_report(trace)
|
|
66
33
|
|
|
67
|
-
for var in old_locals:
|
|
68
|
-
if not ignore_variable(var):
|
|
69
|
-
if var not in locals_now:
|
|
70
|
-
print(f"[{lineno}] DELETED {var}")
|
|
71
34
|
|
|
72
|
-
|
|
73
|
-
|
|
35
|
+
def sig_handler(_signo, _frame):
|
|
36
|
+
sys.exit(0)
|
|
74
37
|
|
|
75
38
|
|
|
76
|
-
def
|
|
77
|
-
|
|
39
|
+
def setup():
|
|
40
|
+
# This kicks off the tracing machinery (so that the lines below work)
|
|
41
|
+
sys.settrace(just_kicking_off)
|
|
78
42
|
|
|
43
|
+
# We want to only trace the module that imports us
|
|
44
|
+
importer_frame = get_importer_frame()
|
|
45
|
+
if importer_frame:
|
|
46
|
+
module_of_interest = inspect.getmodule(importer_frame)
|
|
47
|
+
if module_of_interest:
|
|
48
|
+
var_tracer = tracers.VarTracer(
|
|
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
|
|
79
53
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for frame_info in inspect.stack():
|
|
83
|
-
# Filter out internal importlib frames and the current module's frame
|
|
84
|
-
filename = frame_info.filename
|
|
85
|
-
if not filename.startswith("<") and filename != __file__:
|
|
86
|
-
return frame_info.frame
|
|
87
|
-
return None
|
|
54
|
+
# Setup tracing inside of functions
|
|
55
|
+
sys.settrace(var_tracer.trace_vars)
|
|
88
56
|
|
|
57
|
+
sys.stdout = tracers.OutputLogger(trace=trace, stdout=sys.stdout)
|
|
89
58
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
59
|
+
atexit.register(exit_handler)
|
|
60
|
+
catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
|
|
61
|
+
for sig in catchable_sigs:
|
|
62
|
+
signal.signal(sig, sig_handler)
|
|
93
63
|
|
|
94
64
|
|
|
95
|
-
|
|
96
|
-
importer_frame = get_importer_frame()
|
|
97
|
-
if importer_frame:
|
|
98
|
-
importer_frame.f_trace = trace_vars
|
|
99
|
-
paused = False
|
|
100
|
-
else:
|
|
101
|
-
print("Cannot trace")
|
|
65
|
+
setup()
|
atrace/model.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, TypeAlias
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class PrintEvent:
|
|
7
|
+
text: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Variable:
|
|
12
|
+
module: str | None
|
|
13
|
+
name: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class VariableChangeEvent:
|
|
18
|
+
variable: Variable
|
|
19
|
+
value: Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class TraceItem:
|
|
24
|
+
line_no: int
|
|
25
|
+
function_name: str
|
|
26
|
+
event: PrintEvent | VariableChangeEvent | None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Trace: TypeAlias = list[TraceItem]
|
atrace/report.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from . import model
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def collect_variable_list(trace: model.Trace) -> list[model.Variable]:
|
|
5
|
+
variable_list = []
|
|
6
|
+
for traceItem in trace:
|
|
7
|
+
match traceItem.event:
|
|
8
|
+
case model.VariableChangeEvent(variable_name, _):
|
|
9
|
+
if variable_name not in variable_list:
|
|
10
|
+
variable_list.append(variable_name)
|
|
11
|
+
return variable_list
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def coalesce_print_events(trace: model.Trace) -> model.Trace:
|
|
15
|
+
last_print_item = None
|
|
16
|
+
for trace_item in trace:
|
|
17
|
+
match trace_item.event:
|
|
18
|
+
case model.PrintEvent(text):
|
|
19
|
+
if (
|
|
20
|
+
last_print_item is None
|
|
21
|
+
or trace_item.line_no != last_print_item.line_no
|
|
22
|
+
):
|
|
23
|
+
last_print_item = trace_item
|
|
24
|
+
else:
|
|
25
|
+
last_print_item.event.text += text
|
|
26
|
+
trace_item.event = None # Mark for deletion
|
|
27
|
+
|
|
28
|
+
return [i for i in trace if i.event is not None]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def dump_report(trace: model.Trace) -> None:
|
|
32
|
+
trace = coalesce_print_events(trace)
|
|
33
|
+
|
|
34
|
+
variable_list = collect_variable_list(trace)
|
|
35
|
+
print("vars:", variable_list)
|
|
36
|
+
|
|
37
|
+
for trace_item in trace:
|
|
38
|
+
print(trace_item.line_no, trace_item.event)
|
atrace/tracers.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import inspect
|
|
3
|
+
import sys
|
|
4
|
+
from types import FrameType, ModuleType
|
|
5
|
+
from typing import Any, TextIO
|
|
6
|
+
|
|
7
|
+
from atrace import model
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ignore_variable(name: str, value: Any):
|
|
11
|
+
return name.startswith("__") or callable(value) or isinstance(value, ModuleType)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def filtered_variables(variables: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
+
return {
|
|
16
|
+
name: value
|
|
17
|
+
for name, value in variables.items()
|
|
18
|
+
if not ignore_variable(name, value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def copy_carefully(d: dict[str, Any]):
|
|
23
|
+
res = {}
|
|
24
|
+
for k, v in d.items():
|
|
25
|
+
try:
|
|
26
|
+
v_copy = copy.deepcopy(v)
|
|
27
|
+
except:
|
|
28
|
+
v_copy = v
|
|
29
|
+
res[k] = v_copy
|
|
30
|
+
return res
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VarTracer:
|
|
34
|
+
def __init__(self, trace: model.Trace, module_of_interest: ModuleType):
|
|
35
|
+
self.trace = trace
|
|
36
|
+
self.module_of_interest = module_of_interest
|
|
37
|
+
self.last_locals = {}
|
|
38
|
+
|
|
39
|
+
def trace_vars(self, frame: FrameType, event: str, arg: Any):
|
|
40
|
+
if inspect.getmodule(frame) != self.module_of_interest:
|
|
41
|
+
return
|
|
42
|
+
if event == "return":
|
|
43
|
+
return
|
|
44
|
+
# print("####", event, frame.f_lineno, frame.f_code)
|
|
45
|
+
|
|
46
|
+
code = frame.f_code
|
|
47
|
+
|
|
48
|
+
if code.co_name not in self.last_locals:
|
|
49
|
+
old_locals = {}
|
|
50
|
+
else:
|
|
51
|
+
old_locals = self.last_locals[code.co_name]
|
|
52
|
+
|
|
53
|
+
locals_now = copy_carefully(filtered_variables(frame.f_locals))
|
|
54
|
+
# print("locals_now", locals_now)
|
|
55
|
+
|
|
56
|
+
for var, new_val in filtered_variables(locals_now).items():
|
|
57
|
+
if var not in old_locals or old_locals[var] != new_val:
|
|
58
|
+
# print("çççç", frame.f_lineno, var)
|
|
59
|
+
self.trace.append(
|
|
60
|
+
model.TraceItem(
|
|
61
|
+
line_no=frame.f_lineno,
|
|
62
|
+
function_name=frame.f_code.co_name,
|
|
63
|
+
event=model.VariableChangeEvent(
|
|
64
|
+
variable=model.Variable(module=code.co_name, name=var),
|
|
65
|
+
value=new_val,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.last_locals[code.co_name] = locals_now
|
|
71
|
+
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()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atrace
|
|
3
|
+
Version: 0.1.3
|
|
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
|
+
| Line | (double)a | (double)b | (main)l | (main)x | (main)y | OUTPUT |
|
|
24
|
+
+========+=============+=============+============================+===========+===========+==========+
|
|
25
|
+
| 2 | | | | 0 | | |
|
|
26
|
+
+--------+-------------+-------------+----------------------------+-----------+-----------+----------+
|
|
27
|
+
| 3 | | | | 0 | 10 | |
|
|
28
|
+
+--------+-------------+-------------+----------------------------+-----------+-----------+----------+
|
|
29
|
+
| 5 | | | | 0 | 10 | 10 |
|
|
30
|
+
+--------+-------------+-------------+----------------------------+-----------+-----------+----------+
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Does not work with :
|
|
34
|
+
|
|
35
|
+
- Multithreaded programs
|
|
36
|
+
- Multi-module programs
|
|
37
|
+
|
|
38
|
+
# Requirements/TODO
|
|
39
|
+
|
|
40
|
+
- Build the goddam table
|
|
41
|
+
- Fix line numbers in trace_vars
|
|
42
|
+
- Parallel assignations show up properly
|
|
43
|
+
- Thonny, which adds a shitload of indirection and magic
|
|
44
|
+
|
|
45
|
+
# Later
|
|
46
|
+
|
|
47
|
+
- Make robust. In other words should never raise an exception. ruff check, mypy, unit-tests.
|
|
48
|
+
- Handle classes better
|
|
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
|
+
|
|
61
|
+
# Build
|
|
62
|
+
|
|
63
|
+
Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
|
|
64
|
+
|
|
65
|
+
# Refs
|
|
66
|
+
|
|
67
|
+
- https://stackoverflow.com/questions/16258553/how-can-i-define-algebraic-data-types-in-python To model the events in the trace
|
|
68
|
+
|
|
69
|
+
- https://docs.python.org/3/library/sys.html#sys.settrace The python doc for settrace
|
|
70
|
+
|
|
71
|
+
- https://stackoverflow.com/questions/23468042/the-invocation-of-signal-handler-and-atexit-handler-in-python to dump the trace when the program stops, no matter how.
|
|
72
|
+
|
|
73
|
+
# Inspiration
|
|
74
|
+
|
|
75
|
+
- https://github.com/DarshanLakshman/PyTracerTool Does almost everything I want, but has flaws: it chokes trying to deepcopy some objects, it cannot be simply imported into a module.
|
|
76
|
+
|
|
77
|
+
- https://stackoverflow.com/questions/1645028/trace-table-for-python-programs Some ideas in there
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
atrace/__init__.py,sha256=qFhpaphaeyb40MQKk-N2AC17HsZyg8sjOCN__ukhSnY,1715
|
|
2
|
+
atrace/model.py,sha256=_Fd1XWH5CThBjpZJEWm7rBkxP79IcKDPB8JQq_ksa6Y,415
|
|
3
|
+
atrace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
atrace/report.py,sha256=c5H8EVhyoHLygI3DVkSv96Mn_bZxPcdmHt1TcY8HFqI,1218
|
|
5
|
+
atrace/tracers.py,sha256=TxTxZGe6Oxn-RKoI5nDFT71kA3Aa1oGxnWMbRrsELtE,2987
|
|
6
|
+
atrace-0.1.3.dist-info/METADATA,sha256=iGTG8NhZ7ZGsW8WtsEZzSaZcXWJbhInL-8HIuYNAHoo,3212
|
|
7
|
+
atrace-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
atrace-0.1.3.dist-info/RECORD,,
|
atrace-0.1.1.dist-info/METADATA
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: atrace
|
|
3
|
-
Version: 0.1.1
|
|
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.7
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
|
-
# TODO:
|
|
11
|
-
|
|
12
|
-
- entering a function, binding the local arguments, returning
|
|
13
|
-
- ignore function definitions (look at the type of object)
|
|
14
|
-
|
|
15
|
-
- display at end only
|
|
16
|
-
|
|
17
|
-
# Usage and intent
|
|
18
|
-
|
|
19
|
-
A package that automatically prints a trace table of a program, just by importing the module
|
|
20
|
-
|
|
21
|
-
- Should not interfere with the running program (apart from capturing stdout)
|
|
22
|
-
- Should display the trace at the end of execution (not while the program is interacting with the user)
|
|
23
|
-
- Should display the trace even if an exception interrupts the program
|
|
24
|
-
- Should display the trace even if the user interrupts the program
|
|
25
|
-
- Should handle mutations to objects like lists
|
|
26
|
-
- Should handle functions properly:
|
|
27
|
-
- entering the function
|
|
28
|
-
- binding the local arguments
|
|
29
|
-
- returning
|
|
30
|
-
|
|
31
|
-
An animated example of a trace table: https://www.101computing.net/using-trace-tables/
|
|
32
|
-
|
|
33
|
-
# Not in scope
|
|
34
|
-
|
|
35
|
-
- Multithreaded programs
|
|
36
|
-
|
|
37
|
-
# Implementation
|
|
38
|
-
|
|
39
|
-
To trace variables :
|
|
40
|
-
|
|
41
|
-
- Either https://docs.python.org/3/library/sys.html#sys.settrace
|
|
42
|
-
- Or https://docs.python.org/3/library/trace.html
|
|
43
|
-
|
|
44
|
-
To capture stdout :
|
|
45
|
-
|
|
46
|
-
- Either https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout
|
|
47
|
-
- Or just reassign stdout
|
|
48
|
-
|
|
49
|
-
To trap sigint:
|
|
50
|
-
|
|
51
|
-
- https://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python
|
|
52
|
-
|
|
53
|
-
# Build
|
|
54
|
-
|
|
55
|
-
Automatically deployed to pypi every time a new tag is pushed: https://pypi.org/project/atrace/
|
|
56
|
-
|
|
57
|
-
# Technical Refs
|
|
58
|
-
|
|
59
|
-
- First similar thing I found, doesn't work (chokes on deep copying some variables): https://github.com/DarshanLakshman/PyTracerTool
|
|
60
|
-
|
|
61
|
-
- 11 years old, doesn't work: https://github.com/mihneadb/python-execution-trace
|
|
62
|
-
|
|
63
|
-
- A hot mess: https://stackoverflow.com/questions/1645028/trace-table-for-python-programs
|
|
64
|
-
|
|
65
|
-
- A tutorial on the trace module: https://pymotw.com/2/trace/
|
atrace-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
atrace/__init__.py,sha256=wUmeppysEV7VYt8ZpVnhX9IL_Pec_50FjGqHmS5gEwc,2550
|
|
2
|
-
atrace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
atrace-0.1.1.dist-info/METADATA,sha256=4oknmUUp4BvNtji0hRV121JKvtkjC2BOO6pt9zPqDgI,2026
|
|
4
|
-
atrace-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
-
atrace-0.1.1.dist-info/RECORD,,
|
|
File without changes
|