cs-trace 20260912__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.
- cs_trace-20260912/PKG-INFO +170 -0
- cs_trace-20260912/pyproject.toml +194 -0
- cs_trace-20260912/src/cs/trace.py +185 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cs-trace
|
|
3
|
+
Version: 20260912
|
|
4
|
+
Summary: Utilities for tracing operations.
|
|
5
|
+
Keywords: python3
|
|
6
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Requires-Dist: cs.fs>=20260610
|
|
16
|
+
Requires-Dist: cs.lex>=20260912
|
|
17
|
+
Requires-Dist: cs.py.stack>=20250724
|
|
18
|
+
Requires-Dist: cs.threads>=20260912
|
|
19
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
20
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
21
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
22
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/trace.py
|
|
23
|
+
|
|
24
|
+
Utilities for tracing operations.
|
|
25
|
+
|
|
26
|
+
*Latest release 20260912*:
|
|
27
|
+
Initial PyPI release: Trace class for recording salient decisions and actions for later debugging.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Short summary:
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
* `Trace`: A class/decorator to trace control flow and decisions. This makes it possible to record function calls and their inner decision chains, and to show these in a nice printout after the fact.
|
|
35
|
+
|
|
36
|
+
# Classes
|
|
37
|
+
|
|
38
|
+
## class Trace(cs.threads.HasThreadState)
|
|
39
|
+
|
|
40
|
+
A class/decorator to trace control flow and decisions.
|
|
41
|
+
This makes it possible to record function calls and their
|
|
42
|
+
inner decision chains, and to show these in a nice printout
|
|
43
|
+
after the fact.
|
|
44
|
+
|
|
45
|
+
A new trace object adds itself to the records of the ambient trace object.
|
|
46
|
+
|
|
47
|
+
A trace object supports the context manager protocol, making
|
|
48
|
+
it the ambient object, so that it accrues any new trace objects
|
|
49
|
+
make inside the context.
|
|
50
|
+
|
|
51
|
+
with Trace("name') as T:
|
|
52
|
+
... add records via T ...
|
|
53
|
+
|
|
54
|
+
Calling a trace object adds a new record to the trace
|
|
55
|
+
|
|
56
|
+
if T("test x==2", x==2):
|
|
57
|
+
T("acting on x==2")
|
|
58
|
+
else:
|
|
59
|
+
T("x != 2")
|
|
60
|
+
|
|
61
|
+
As a trace object:
|
|
62
|
+
|
|
63
|
+
>>> from builtins import print
|
|
64
|
+
>>> with Trace("decide!") as T:
|
|
65
|
+
... print("start")
|
|
66
|
+
... if T("test 1 for never", 1==2):
|
|
67
|
+
... print("never")
|
|
68
|
+
... elif T("test 2 for always", 1==1):
|
|
69
|
+
... print("always")
|
|
70
|
+
... with Trace("inside test 2", T) as T2:
|
|
71
|
+
... assert T2 in T.tests
|
|
72
|
+
... if T2("inside1",1==1):
|
|
73
|
+
... print("true")
|
|
74
|
+
... else:
|
|
75
|
+
... print("false")
|
|
76
|
+
...
|
|
77
|
+
start
|
|
78
|
+
always
|
|
79
|
+
true
|
|
80
|
+
>>> T.printt()
|
|
81
|
+
decide!
|
|
82
|
+
├─test 1 for never -> bool False
|
|
83
|
+
├─test 2 for always -> bool True
|
|
84
|
+
╰─inside test 2
|
|
85
|
+
╰─inside1 -> bool True
|
|
86
|
+
|
|
87
|
+
As a decorator it calls the function with an additional named
|
|
88
|
+
argument `T` which is the `Trace` instance for that call of
|
|
89
|
+
the function:
|
|
90
|
+
|
|
91
|
+
>>> @Trace
|
|
92
|
+
... def func(x, T):
|
|
93
|
+
... x2 = T(f'{x=} + 2', x+2)
|
|
94
|
+
... return x2
|
|
95
|
+
...
|
|
96
|
+
>>> with Trace("func trace") as T:
|
|
97
|
+
... x2 = T("call func with 3", func(3))
|
|
98
|
+
... print("x2", x2)
|
|
99
|
+
...
|
|
100
|
+
x2 5
|
|
101
|
+
>>> T.printt() # doctest: +ELLIPSIS
|
|
102
|
+
func trace
|
|
103
|
+
├─func(....)
|
|
104
|
+
│ │ from <module>() <doctest cs.trace.Trace[4]>:2
|
|
105
|
+
│ │ x2 = T("call func with 3", func(3))
|
|
106
|
+
│ ├─x=3 + 2 -> int 5
|
|
107
|
+
│ ╰─return -> int 5
|
|
108
|
+
╰─call func with 3 -> int 5
|
|
109
|
+
|
|
110
|
+
### `Trace.__call__(self, label: str, result='', print=False)`
|
|
111
|
+
|
|
112
|
+
Calling the trace object records `(abel,result)` and
|
|
113
|
+
optionally `print`s.
|
|
114
|
+
|
|
115
|
+
### `Trace.__firstlineno__`
|
|
116
|
+
|
|
117
|
+
int([x]) -> integer
|
|
118
|
+
int(x, base=10) -> integer
|
|
119
|
+
|
|
120
|
+
Convert a number or string to an integer, or return 0 if no arguments
|
|
121
|
+
are given. If x is a number, return x.__int__(). For floating-point
|
|
122
|
+
numbers, this truncates towards zero.
|
|
123
|
+
|
|
124
|
+
If x is not a number or if base is given, then x must be a string,
|
|
125
|
+
bytes, or bytearray instance representing an integer literal in the
|
|
126
|
+
given base. The literal can be preceded by '+' or '-' and be surrounded
|
|
127
|
+
by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
|
|
128
|
+
Base 0 means to interpret the base from the string as an integer literal.
|
|
129
|
+
>>> int('0b100', base=0)
|
|
130
|
+
4
|
|
131
|
+
|
|
132
|
+
### `Trace.__static_attributes__`
|
|
133
|
+
|
|
134
|
+
Built-in immutable sequence.
|
|
135
|
+
|
|
136
|
+
If no argument is given, the constructor returns an empty tuple.
|
|
137
|
+
If iterable is specified the tuple is initialized from iterable's items.
|
|
138
|
+
|
|
139
|
+
If the argument is a tuple, the return value is the same object.
|
|
140
|
+
|
|
141
|
+
### `Trace.perthread_state`
|
|
142
|
+
|
|
143
|
+
A `Thread` local object with attributes
|
|
144
|
+
which can be used as a context manager to stack attribute values.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
|
|
148
|
+
from cs.threads import ThreadState
|
|
149
|
+
|
|
150
|
+
S = ThreadState(verbose=False)
|
|
151
|
+
|
|
152
|
+
with S(verbose=True) as prev_attrs:
|
|
153
|
+
if S.verbose:
|
|
154
|
+
print("verbose! (formerly verbose=%s)" % prev_attrs['verbose'])
|
|
155
|
+
|
|
156
|
+
### `Trace.printt(self, **printt_kw)`
|
|
157
|
+
|
|
158
|
+
Use `cs.lex.printt()` to print this trace object.
|
|
159
|
+
Keyword arguments are passed through.
|
|
160
|
+
|
|
161
|
+
### `Trace.tabulate(self)`
|
|
162
|
+
|
|
163
|
+
Tabulate this trace object for use with `cs.lex.printt()`.
|
|
164
|
+
|
|
165
|
+
# Release Log
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
*Release 20260912*:
|
|
170
|
+
Initial PyPI release: Trace class for recording salient decisions and actions for later debugging.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cs-trace"
|
|
3
|
+
description = "Utilities for tracing operations."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Cameron Simpson", email = "cs@cskk.id.au" },
|
|
6
|
+
]
|
|
7
|
+
keywords = [
|
|
8
|
+
"python3",
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cs.fs>=20260610",
|
|
12
|
+
"cs.lex>=20260912",
|
|
13
|
+
"cs.py.stack>=20250724",
|
|
14
|
+
"cs.threads>=20260912",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
24
|
+
]
|
|
25
|
+
version = "20260912"
|
|
26
|
+
|
|
27
|
+
[project.license]
|
|
28
|
+
text = "GNU General Public License v3 or later (GPLv3+)"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
"Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
|
|
32
|
+
"Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
|
|
33
|
+
"MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
|
|
34
|
+
Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/trace.py"
|
|
35
|
+
|
|
36
|
+
[project.readme]
|
|
37
|
+
text = """
|
|
38
|
+
Utilities for tracing operations.
|
|
39
|
+
|
|
40
|
+
*Latest release 20260912*:
|
|
41
|
+
Initial PyPI release: Trace class for recording salient decisions and actions for later debugging.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Short summary:
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
* `Trace`: A class/decorator to trace control flow and decisions. This makes it possible to record function calls and their inner decision chains, and to show these in a nice printout after the fact.
|
|
49
|
+
|
|
50
|
+
# Classes
|
|
51
|
+
|
|
52
|
+
## class Trace(cs.threads.HasThreadState)
|
|
53
|
+
|
|
54
|
+
A class/decorator to trace control flow and decisions.
|
|
55
|
+
This makes it possible to record function calls and their
|
|
56
|
+
inner decision chains, and to show these in a nice printout
|
|
57
|
+
after the fact.
|
|
58
|
+
|
|
59
|
+
A new trace object adds itself to the records of the ambient trace object.
|
|
60
|
+
|
|
61
|
+
A trace object supports the context manager protocol, making
|
|
62
|
+
it the ambient object, so that it accrues any new trace objects
|
|
63
|
+
make inside the context.
|
|
64
|
+
|
|
65
|
+
with Trace(\"name') as T:
|
|
66
|
+
... add records via T ...
|
|
67
|
+
|
|
68
|
+
Calling a trace object adds a new record to the trace
|
|
69
|
+
|
|
70
|
+
if T(\"test x==2\", x==2):
|
|
71
|
+
T(\"acting on x==2\")
|
|
72
|
+
else:
|
|
73
|
+
T(\"x != 2\")
|
|
74
|
+
|
|
75
|
+
As a trace object:
|
|
76
|
+
|
|
77
|
+
>>> from builtins import print
|
|
78
|
+
>>> with Trace(\"decide!\") as T:
|
|
79
|
+
... print(\"start\")
|
|
80
|
+
... if T(\"test 1 for never\", 1==2):
|
|
81
|
+
... print(\"never\")
|
|
82
|
+
... elif T(\"test 2 for always\", 1==1):
|
|
83
|
+
... print(\"always\")
|
|
84
|
+
... with Trace(\"inside test 2\", T) as T2:
|
|
85
|
+
... assert T2 in T.tests
|
|
86
|
+
... if T2(\"inside1\",1==1):
|
|
87
|
+
... print(\"true\")
|
|
88
|
+
... else:
|
|
89
|
+
... print(\"false\")
|
|
90
|
+
...
|
|
91
|
+
start
|
|
92
|
+
always
|
|
93
|
+
true
|
|
94
|
+
>>> T.printt()
|
|
95
|
+
decide!
|
|
96
|
+
├─test 1 for never -> bool False
|
|
97
|
+
├─test 2 for always -> bool True
|
|
98
|
+
╰─inside test 2
|
|
99
|
+
╰─inside1 -> bool True
|
|
100
|
+
|
|
101
|
+
As a decorator it calls the function with an additional named
|
|
102
|
+
argument `T` which is the `Trace` instance for that call of
|
|
103
|
+
the function:
|
|
104
|
+
|
|
105
|
+
>>> @Trace
|
|
106
|
+
... def func(x, T):
|
|
107
|
+
... x2 = T(f'{x=} + 2', x+2)
|
|
108
|
+
... return x2
|
|
109
|
+
...
|
|
110
|
+
>>> with Trace(\"func trace\") as T:
|
|
111
|
+
... x2 = T(\"call func with 3\", func(3))
|
|
112
|
+
... print(\"x2\", x2)
|
|
113
|
+
...
|
|
114
|
+
x2 5
|
|
115
|
+
>>> T.printt() # doctest: +ELLIPSIS
|
|
116
|
+
func trace
|
|
117
|
+
├─func(....)
|
|
118
|
+
│ │ from <module>() <doctest cs.trace.Trace[4]>:2
|
|
119
|
+
│ │ x2 = T(\"call func with 3\", func(3))
|
|
120
|
+
│ ├─x=3 + 2 -> int 5
|
|
121
|
+
│ ╰─return -> int 5
|
|
122
|
+
╰─call func with 3 -> int 5
|
|
123
|
+
|
|
124
|
+
### `Trace.__call__(self, label: str, result='', print=False)`
|
|
125
|
+
|
|
126
|
+
Calling the trace object records `(abel,result)` and
|
|
127
|
+
optionally `print`s.
|
|
128
|
+
|
|
129
|
+
### `Trace.__firstlineno__`
|
|
130
|
+
|
|
131
|
+
int([x]) -> integer
|
|
132
|
+
int(x, base=10) -> integer
|
|
133
|
+
|
|
134
|
+
Convert a number or string to an integer, or return 0 if no arguments
|
|
135
|
+
are given. If x is a number, return x.__int__(). For floating-point
|
|
136
|
+
numbers, this truncates towards zero.
|
|
137
|
+
|
|
138
|
+
If x is not a number or if base is given, then x must be a string,
|
|
139
|
+
bytes, or bytearray instance representing an integer literal in the
|
|
140
|
+
given base. The literal can be preceded by '+' or '-' and be surrounded
|
|
141
|
+
by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
|
|
142
|
+
Base 0 means to interpret the base from the string as an integer literal.
|
|
143
|
+
>>> int('0b100', base=0)
|
|
144
|
+
4
|
|
145
|
+
|
|
146
|
+
### `Trace.__static_attributes__`
|
|
147
|
+
|
|
148
|
+
Built-in immutable sequence.
|
|
149
|
+
|
|
150
|
+
If no argument is given, the constructor returns an empty tuple.
|
|
151
|
+
If iterable is specified the tuple is initialized from iterable's items.
|
|
152
|
+
|
|
153
|
+
If the argument is a tuple, the return value is the same object.
|
|
154
|
+
|
|
155
|
+
### `Trace.perthread_state`
|
|
156
|
+
|
|
157
|
+
A `Thread` local object with attributes
|
|
158
|
+
which can be used as a context manager to stack attribute values.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
|
|
162
|
+
from cs.threads import ThreadState
|
|
163
|
+
|
|
164
|
+
S = ThreadState(verbose=False)
|
|
165
|
+
|
|
166
|
+
with S(verbose=True) as prev_attrs:
|
|
167
|
+
if S.verbose:
|
|
168
|
+
print(\"verbose! (formerly verbose=%s)\" % prev_attrs['verbose'])
|
|
169
|
+
|
|
170
|
+
### `Trace.printt(self, **printt_kw)`
|
|
171
|
+
|
|
172
|
+
Use `cs.lex.printt()` to print this trace object.
|
|
173
|
+
Keyword arguments are passed through.
|
|
174
|
+
|
|
175
|
+
### `Trace.tabulate(self)`
|
|
176
|
+
|
|
177
|
+
Tabulate this trace object for use with `cs.lex.printt()`.
|
|
178
|
+
|
|
179
|
+
# Release Log
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
*Release 20260912*:
|
|
184
|
+
Initial PyPI release: Trace class for recording salient decisions and actions for later debugging."""
|
|
185
|
+
content-type = "text/markdown"
|
|
186
|
+
|
|
187
|
+
[build-system]
|
|
188
|
+
build-backend = "flit_core.buildapi"
|
|
189
|
+
requires = [
|
|
190
|
+
"flit_core >=3.2,<4",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
[tool.flit.module]
|
|
194
|
+
name = "cs.trace"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
''' Utilities for tracing operations.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
from os.path import relpath
|
|
8
|
+
|
|
9
|
+
from cs.fs import shortpath
|
|
10
|
+
from cs.lex import printt
|
|
11
|
+
from cs.py.stack import caller
|
|
12
|
+
from cs.threads import HasThreadState, ThreadState
|
|
13
|
+
|
|
14
|
+
__version__ = '20260912'
|
|
15
|
+
|
|
16
|
+
DISTINFO = {
|
|
17
|
+
'keywords': ["python3"],
|
|
18
|
+
'classifiers': [
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
],
|
|
22
|
+
'install_requires': [
|
|
23
|
+
'cs.fs',
|
|
24
|
+
'cs.lex',
|
|
25
|
+
'cs.py.stack',
|
|
26
|
+
'cs.threads',
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class Trace(HasThreadState):
|
|
31
|
+
''' A class/decorator to trace control flow and decisions.
|
|
32
|
+
This makes it possible to record function calls and their
|
|
33
|
+
inner decision chains, and to show these in a nice printout
|
|
34
|
+
after the fact.
|
|
35
|
+
|
|
36
|
+
A new trace object adds itself to the records of the ambient trace object.
|
|
37
|
+
|
|
38
|
+
A trace object supports the context manager protocol, making
|
|
39
|
+
it the ambient object, so that it accrues any new trace objects
|
|
40
|
+
make inside the context.
|
|
41
|
+
|
|
42
|
+
with Trace("name') as T:
|
|
43
|
+
... add records via T ...
|
|
44
|
+
|
|
45
|
+
Calling a trace object adds a new record to the trace
|
|
46
|
+
|
|
47
|
+
if T("test x==2", x==2):
|
|
48
|
+
T("acting on x==2")
|
|
49
|
+
else:
|
|
50
|
+
T("x != 2")
|
|
51
|
+
|
|
52
|
+
As a trace object:
|
|
53
|
+
|
|
54
|
+
>>> from builtins import print
|
|
55
|
+
>>> with Trace("decide!") as T:
|
|
56
|
+
... print("start")
|
|
57
|
+
... if T("test 1 for never", 1==2):
|
|
58
|
+
... print("never")
|
|
59
|
+
... elif T("test 2 for always", 1==1):
|
|
60
|
+
... print("always")
|
|
61
|
+
... with Trace("inside test 2", T) as T2:
|
|
62
|
+
... assert T2 in T.tests
|
|
63
|
+
... if T2("inside1",1==1):
|
|
64
|
+
... print("true")
|
|
65
|
+
... else:
|
|
66
|
+
... print("false")
|
|
67
|
+
...
|
|
68
|
+
start
|
|
69
|
+
always
|
|
70
|
+
true
|
|
71
|
+
>>> T.printt()
|
|
72
|
+
decide!
|
|
73
|
+
├─test 1 for never -> bool False
|
|
74
|
+
├─test 2 for always -> bool True
|
|
75
|
+
╰─inside test 2
|
|
76
|
+
╰─inside1 -> bool True
|
|
77
|
+
|
|
78
|
+
As a decorator it calls the function with an additional named
|
|
79
|
+
argument `T` which is the `Trace` instance for that call of
|
|
80
|
+
the function:
|
|
81
|
+
|
|
82
|
+
>>> @Trace
|
|
83
|
+
... def func(x, T):
|
|
84
|
+
... x2 = T(f'{x=} + 2', x+2)
|
|
85
|
+
... return x2
|
|
86
|
+
...
|
|
87
|
+
>>> with Trace("func trace") as T:
|
|
88
|
+
... x2 = T("call func with 3", func(3))
|
|
89
|
+
... print("x2", x2)
|
|
90
|
+
...
|
|
91
|
+
x2 5
|
|
92
|
+
>>> T.printt() # doctest: +ELLIPSIS
|
|
93
|
+
func trace
|
|
94
|
+
├─func(....)
|
|
95
|
+
│ │ from <module>() <doctest cs.trace.Trace[4]>:2
|
|
96
|
+
│ │ x2 = T("call func with 3", func(3))
|
|
97
|
+
│ ├─x=3 + 2 -> int 5
|
|
98
|
+
│ ╰─return -> int 5
|
|
99
|
+
╰─call func with 3 -> int 5
|
|
100
|
+
|
|
101
|
+
'''
|
|
102
|
+
|
|
103
|
+
# class attribute holding the per-thread state stack
|
|
104
|
+
perthread_state = ThreadState()
|
|
105
|
+
|
|
106
|
+
def __new__(cls, func, *_, print=False): # noqa: A002
|
|
107
|
+
''' Intercept object creation for use as a decorator.
|
|
108
|
+
If `func` is a callable, decorate it.
|
|
109
|
+
otherwise fall through to normal class instantiation.
|
|
110
|
+
|
|
111
|
+
The decorated function is passed an additional named `T`
|
|
112
|
+
keyword parameter being the `Trace` instance created for
|
|
113
|
+
the function call and return, ready for additional records.
|
|
114
|
+
'''
|
|
115
|
+
if callable(func):
|
|
116
|
+
# class being used as a decorator
|
|
117
|
+
|
|
118
|
+
def traced_func(*func_a, **func_kw):
|
|
119
|
+
c = caller(-4)
|
|
120
|
+
path = relpath(c.filename)
|
|
121
|
+
if path.startswith('../'):
|
|
122
|
+
path = shortpath(c.filename)
|
|
123
|
+
with cls(f'{func.__name__}(....)'
|
|
124
|
+
f'\n from {c.name}() {path}:{c.lineno}'
|
|
125
|
+
f'\n {c.line}') as T:
|
|
126
|
+
try:
|
|
127
|
+
result = func(*func_a, T=T, **func_kw)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
T('RAISE', e, print=print)
|
|
130
|
+
raise
|
|
131
|
+
else:
|
|
132
|
+
T('return', result, print=print)
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
return traced_func
|
|
136
|
+
assert print is False
|
|
137
|
+
return super().__new__(cls)
|
|
138
|
+
|
|
139
|
+
def __init__(self, name: str, upT=None):
|
|
140
|
+
self.name = name
|
|
141
|
+
self.tests = []
|
|
142
|
+
if upT is None:
|
|
143
|
+
upT = type(self).default()
|
|
144
|
+
if upT is not None:
|
|
145
|
+
upT.tests.append(self)
|
|
146
|
+
|
|
147
|
+
def tabulate(self):
|
|
148
|
+
''' Tabulate this trace object for use with `cs.lex.printt()`.
|
|
149
|
+
'''
|
|
150
|
+
table = [self.name]
|
|
151
|
+
subtable = []
|
|
152
|
+
for subtest in self.tests:
|
|
153
|
+
if isinstance(subtest, tuple):
|
|
154
|
+
label, result = subtest
|
|
155
|
+
subtable.append([f' {label}', result])
|
|
156
|
+
else:
|
|
157
|
+
subtable.extend(subtest.tabulate())
|
|
158
|
+
if subtable:
|
|
159
|
+
table.append(tuple(subtable))
|
|
160
|
+
return table
|
|
161
|
+
|
|
162
|
+
def printt(self, **printt_kw):
|
|
163
|
+
''' Use `cs.lex.printt()` to print this trace object.
|
|
164
|
+
Keyword arguments are passed through.
|
|
165
|
+
'''
|
|
166
|
+
printt(*self.tabulate(), **printt_kw)
|
|
167
|
+
|
|
168
|
+
def __call__(self, label: str, result='', print=False): # noqa: A002
|
|
169
|
+
''' Calling the trace object records `(abel,result)` and
|
|
170
|
+
optionally `print`s.
|
|
171
|
+
'''
|
|
172
|
+
if print is False:
|
|
173
|
+
print = lambda *_, **__: None
|
|
174
|
+
elif print is True:
|
|
175
|
+
print = builtins.print
|
|
176
|
+
elif not callable(print):
|
|
177
|
+
raise TypeError(
|
|
178
|
+
f'print should be False, True or a print()-compatible callable, got {print=}'
|
|
179
|
+
)
|
|
180
|
+
assert isinstance(label, str)
|
|
181
|
+
print(
|
|
182
|
+
f'{type(self).__name__}: {label}: {type(result).__name__}:{result!r}'
|
|
183
|
+
)
|
|
184
|
+
self.tests.append((f'{label} -> {type(result).__name__}', result))
|
|
185
|
+
return result
|