jc-debug 1.0.2__tar.gz → 1.0.4__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.
- {jc_debug-1.0.2 → jc_debug-1.0.4}/PKG-INFO +76 -29
- {jc_debug-1.0.2 → jc_debug-1.0.4}/README.md +75 -28
- {jc_debug-1.0.2 → jc_debug-1.0.4}/pyproject.toml +1 -1
- {jc_debug-1.0.2 → jc_debug-1.0.4}/src/debug/__init__.py +104 -82
- {jc_debug-1.0.2 → jc_debug-1.0.4}/src/jc_debug.egg-info/PKG-INFO +76 -29
- {jc_debug-1.0.2 → jc_debug-1.0.4}/tests/test_operations.py +16 -6
- {jc_debug-1.0.2 → jc_debug-1.0.4}/LICENSE +0 -0
- {jc_debug-1.0.2 → jc_debug-1.0.4}/setup.cfg +0 -0
- {jc_debug-1.0.2 → jc_debug-1.0.4}/src/jc_debug.egg-info/SOURCES.txt +0 -0
- {jc_debug-1.0.2 → jc_debug-1.0.4}/src/jc_debug.egg-info/dependency_links.txt +0 -0
- {jc_debug-1.0.2 → jc_debug-1.0.4}/src/jc_debug.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: jc-debug
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Makes debugging to the console both simpler and much more powerful.
|
5
5
|
Author-email: Jeff Clough <jeff@cloughcottage.com>
|
6
6
|
License: MIT License
|
@@ -42,8 +42,8 @@ Dynamic: license-file
|
|
42
42
|
|
43
43
|
# debug
|
44
44
|
|
45
|
-
This debug module a class named DebugChannel, instances of which are
|
46
|
-
|
45
|
+
This debug module a class named DebugChannel, instances of which are useful for
|
46
|
+
adding temporary or conditional debug output to CLI scripts.
|
47
47
|
|
48
48
|
The minimal boilerplate is pretty simple:
|
49
49
|
|
@@ -53,9 +53,9 @@ from debug import DebugChannel
|
|
53
53
|
dc=DebugChannel(True)
|
54
54
|
```
|
55
55
|
|
56
|
-
By default, DebugChannels are created disabled (the write no output), so
|
57
|
-
|
58
|
-
|
56
|
+
By default, DebugChannels are created disabled (the write no output), so the
|
57
|
+
`True` above enables `dc` during its instantiation so it needn't be enabled
|
58
|
+
later.
|
59
59
|
|
60
60
|
A more common way of handling this is ...
|
61
61
|
|
@@ -73,18 +73,18 @@ dc.enable(opt.debug)
|
|
73
73
|
...
|
74
74
|
```
|
75
75
|
|
76
|
-
This enables the `dc` DebugChannel instance only if --debug is given on
|
77
|
-
|
76
|
+
This enables the `dc` DebugChannel instance only if --debug is given on the
|
77
|
+
script's command line.
|
78
78
|
|
79
79
|
By default, output is sent to stdandard error and formatted as:
|
80
80
|
|
81
81
|
'{label}: [{pid}] {basename}:{line}:{function}: {indent}{message}\n'
|
82
82
|
|
83
|
-
There are several variables you can include in DebugChannel's output.
|
84
|
-
|
83
|
+
There are several variables you can include in DebugChannel's output. See the
|
84
|
+
DebugChannel docs below for a list.
|
85
85
|
|
86
|
-
So, for example, if you want to see how your variables are behaving in a
|
87
|
-
|
86
|
+
So, for example, if you want to see how your variables are behaving in a loop,
|
87
|
+
you might do something like this:
|
88
88
|
|
89
89
|
```python
|
90
90
|
from debug import DebugChannel
|
@@ -96,15 +96,15 @@ dc=DebugChannel(
|
|
96
96
|
|
97
97
|
dc("Entering loop ...").indent()
|
98
98
|
for i in range(5):
|
99
|
-
dc(f"i=
|
99
|
+
dc(f"{i=}").indent()
|
100
100
|
for j in range(3):
|
101
|
-
dc(f"j=
|
101
|
+
dc(f"{j=}")
|
102
102
|
dc.undent()("Done with j loop.")
|
103
103
|
dc.undent()("Done with i loop.")
|
104
104
|
```
|
105
105
|
|
106
|
-
That gives you this necely indented output. The indent() and undent()
|
107
|
-
|
106
|
+
That gives you this necely indented output. The indent() and undent() methods
|
107
|
+
are one thing that makes DebugChannels so nice to work with.
|
108
108
|
|
109
109
|
DC: 8: Entering loop ...
|
110
110
|
DC: 10: i=0
|
@@ -134,8 +134,8 @@ methods are one thing that makes DebugChannels so nice to work with.
|
|
134
134
|
DC: 13: Done with j loop.
|
135
135
|
DC: 14: Done with i loop.
|
136
136
|
|
137
|
-
That's a simple example, but you might be starting to get an idea of
|
138
|
-
|
137
|
+
That's a simple example, but you might be starting to get an idea of how
|
138
|
+
versatile DebugChannel instances can be.
|
139
139
|
|
140
140
|
A DebugChannel can also be used as a function decorator:
|
141
141
|
|
@@ -163,9 +163,9 @@ example2("First test",3)
|
|
163
163
|
example2("Second test",2)
|
164
164
|
```
|
165
165
|
|
166
|
-
This causes entry into and exit from the decorated function to be
|
167
|
-
|
168
|
-
|
166
|
+
This causes entry into and exit from the decorated function to be recorded in
|
167
|
+
the given DebugChannel's output. If you put that into a file named foo.py and
|
168
|
+
then run "python3 -m foo", you'll get this:
|
169
169
|
|
170
170
|
```
|
171
171
|
DC: __main__: example2('First test',3) ...
|
@@ -189,6 +189,54 @@ DC: example2: example1(...) returns None after 23µs.
|
|
189
189
|
DC: __main__: example2(...) returns None after 423ms.
|
190
190
|
```
|
191
191
|
|
192
|
+
When outputting a data structure, it's nice to be a little structured about it.
|
193
|
+
So if you send a list, tuple, dict, or set to a DebugChannel instance as the
|
194
|
+
whole message, it will be formatted one item at a time in the output.
|
195
|
+
|
196
|
+
```python
|
197
|
+
from debug import DebugChannel
|
198
|
+
|
199
|
+
dc=DebugChannel(True,fmt="{label}: {line:3}: {indent}{message}\n")
|
200
|
+
|
201
|
+
l="this is a test".split()
|
202
|
+
s=set(l)
|
203
|
+
d=dict(zip('abcd',l))
|
204
|
+
|
205
|
+
dc(l,'l')
|
206
|
+
dc(s,'s')
|
207
|
+
dc(d,'d')
|
208
|
+
```
|
209
|
+
|
210
|
+
Notice the first parameter is a data structure, and the second is the name of that data structure. This idiom creates output like this:
|
211
|
+
|
212
|
+
```python
|
213
|
+
DC: 12: l:
|
214
|
+
DC: 12: [
|
215
|
+
DC: 12: 'this',
|
216
|
+
DC: 12: 'is',
|
217
|
+
DC: 12: 'a',
|
218
|
+
DC: 12: 'test'
|
219
|
+
DC: 12: ]
|
220
|
+
DC: 13: s:
|
221
|
+
DC: 13: {
|
222
|
+
DC: 13: 'a',
|
223
|
+
DC: 13: 'is',
|
224
|
+
DC: 13: 'test',
|
225
|
+
DC: 13: 'this'
|
226
|
+
DC: 13: }
|
227
|
+
DC: 14: d:
|
228
|
+
DC: 14: {
|
229
|
+
DC: 14: 'a': 'this',
|
230
|
+
DC: 14: 'b': 'is',
|
231
|
+
DC: 14: 'c': 'a',
|
232
|
+
DC: 14: 'd': 'test'
|
233
|
+
DC: 14: }
|
234
|
+
```
|
235
|
+
|
236
|
+
Notice sets are output in alphabetical order (according to their repr()
|
237
|
+
values). Since sets are unordered by nature, this makes them easier to inspect
|
238
|
+
visually without misrepresenting their contents.
|
239
|
+
|
192
240
|
That's a very general start. See DebugChannel's class docs for more.
|
193
241
|
|
194
242
|
<a id="debug.DebugChannel"></a>
|
@@ -240,8 +288,8 @@ line number reported in its output are something helpful to the
|
|
240
288
|
caller. For instance, the source line shouldn't be anything in this
|
241
289
|
DebugChannel class.
|
242
290
|
|
243
|
-
Use the ignoreModule() method to tell the DebugChannel object
|
244
|
-
other modules, and optionally, specific functions within that
|
291
|
+
Use the ignoreModule() method to tell the DebugChannel object to
|
292
|
+
ignore other modules, and optionally, specific functions within that
|
245
293
|
module.
|
246
294
|
|
247
295
|
<a id="debug.DebugChannel.__init__"></a>
|
@@ -457,11 +505,11 @@ supported.
|
|
457
505
|
#### \_\_call\_\_
|
458
506
|
|
459
507
|
```python
|
460
|
-
def __call__(
|
508
|
+
def __call__(*args, **kwargs)
|
461
509
|
```
|
462
510
|
|
463
511
|
If this DebugChannel instance is simply being called, this
|
464
|
-
method is a very simple wrapper around the write(...)
|
512
|
+
method is a very simple wrapper around the write(...) method. If
|
465
513
|
it is being used as a function decorator, that function entry
|
466
514
|
and exit are recorded to the DebugChannel, and this becomes a
|
467
515
|
more featuresome wrapper around the write(...) method.
|
@@ -482,13 +530,12 @@ output stream.
|
|
482
530
|
#### write
|
483
531
|
|
484
532
|
```python
|
485
|
-
def write(message)
|
533
|
+
def write(message, var=None)
|
486
534
|
```
|
487
535
|
|
488
536
|
If this debug instance is enabled, write the given message
|
489
|
-
using the our current format.
|
490
|
-
|
491
|
-
it. E.g.:
|
537
|
+
using the our current format. Return this DebugChannel instance
|
538
|
+
so further operations can be performed on it. E.g.:
|
492
539
|
|
493
540
|
```python
|
494
541
|
debug=DebugChannel(opt.debug)
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
# debug
|
4
4
|
|
5
|
-
This debug module a class named DebugChannel, instances of which are
|
6
|
-
|
5
|
+
This debug module a class named DebugChannel, instances of which are useful for
|
6
|
+
adding temporary or conditional debug output to CLI scripts.
|
7
7
|
|
8
8
|
The minimal boilerplate is pretty simple:
|
9
9
|
|
@@ -13,9 +13,9 @@ from debug import DebugChannel
|
|
13
13
|
dc=DebugChannel(True)
|
14
14
|
```
|
15
15
|
|
16
|
-
By default, DebugChannels are created disabled (the write no output), so
|
17
|
-
|
18
|
-
|
16
|
+
By default, DebugChannels are created disabled (the write no output), so the
|
17
|
+
`True` above enables `dc` during its instantiation so it needn't be enabled
|
18
|
+
later.
|
19
19
|
|
20
20
|
A more common way of handling this is ...
|
21
21
|
|
@@ -33,18 +33,18 @@ dc.enable(opt.debug)
|
|
33
33
|
...
|
34
34
|
```
|
35
35
|
|
36
|
-
This enables the `dc` DebugChannel instance only if --debug is given on
|
37
|
-
|
36
|
+
This enables the `dc` DebugChannel instance only if --debug is given on the
|
37
|
+
script's command line.
|
38
38
|
|
39
39
|
By default, output is sent to stdandard error and formatted as:
|
40
40
|
|
41
41
|
'{label}: [{pid}] {basename}:{line}:{function}: {indent}{message}\n'
|
42
42
|
|
43
|
-
There are several variables you can include in DebugChannel's output.
|
44
|
-
|
43
|
+
There are several variables you can include in DebugChannel's output. See the
|
44
|
+
DebugChannel docs below for a list.
|
45
45
|
|
46
|
-
So, for example, if you want to see how your variables are behaving in a
|
47
|
-
|
46
|
+
So, for example, if you want to see how your variables are behaving in a loop,
|
47
|
+
you might do something like this:
|
48
48
|
|
49
49
|
```python
|
50
50
|
from debug import DebugChannel
|
@@ -56,15 +56,15 @@ dc=DebugChannel(
|
|
56
56
|
|
57
57
|
dc("Entering loop ...").indent()
|
58
58
|
for i in range(5):
|
59
|
-
dc(f"i=
|
59
|
+
dc(f"{i=}").indent()
|
60
60
|
for j in range(3):
|
61
|
-
dc(f"j=
|
61
|
+
dc(f"{j=}")
|
62
62
|
dc.undent()("Done with j loop.")
|
63
63
|
dc.undent()("Done with i loop.")
|
64
64
|
```
|
65
65
|
|
66
|
-
That gives you this necely indented output. The indent() and undent()
|
67
|
-
|
66
|
+
That gives you this necely indented output. The indent() and undent() methods
|
67
|
+
are one thing that makes DebugChannels so nice to work with.
|
68
68
|
|
69
69
|
DC: 8: Entering loop ...
|
70
70
|
DC: 10: i=0
|
@@ -94,8 +94,8 @@ methods are one thing that makes DebugChannels so nice to work with.
|
|
94
94
|
DC: 13: Done with j loop.
|
95
95
|
DC: 14: Done with i loop.
|
96
96
|
|
97
|
-
That's a simple example, but you might be starting to get an idea of
|
98
|
-
|
97
|
+
That's a simple example, but you might be starting to get an idea of how
|
98
|
+
versatile DebugChannel instances can be.
|
99
99
|
|
100
100
|
A DebugChannel can also be used as a function decorator:
|
101
101
|
|
@@ -123,9 +123,9 @@ example2("First test",3)
|
|
123
123
|
example2("Second test",2)
|
124
124
|
```
|
125
125
|
|
126
|
-
This causes entry into and exit from the decorated function to be
|
127
|
-
|
128
|
-
|
126
|
+
This causes entry into and exit from the decorated function to be recorded in
|
127
|
+
the given DebugChannel's output. If you put that into a file named foo.py and
|
128
|
+
then run "python3 -m foo", you'll get this:
|
129
129
|
|
130
130
|
```
|
131
131
|
DC: __main__: example2('First test',3) ...
|
@@ -149,6 +149,54 @@ DC: example2: example1(...) returns None after 23µs.
|
|
149
149
|
DC: __main__: example2(...) returns None after 423ms.
|
150
150
|
```
|
151
151
|
|
152
|
+
When outputting a data structure, it's nice to be a little structured about it.
|
153
|
+
So if you send a list, tuple, dict, or set to a DebugChannel instance as the
|
154
|
+
whole message, it will be formatted one item at a time in the output.
|
155
|
+
|
156
|
+
```python
|
157
|
+
from debug import DebugChannel
|
158
|
+
|
159
|
+
dc=DebugChannel(True,fmt="{label}: {line:3}: {indent}{message}\n")
|
160
|
+
|
161
|
+
l="this is a test".split()
|
162
|
+
s=set(l)
|
163
|
+
d=dict(zip('abcd',l))
|
164
|
+
|
165
|
+
dc(l,'l')
|
166
|
+
dc(s,'s')
|
167
|
+
dc(d,'d')
|
168
|
+
```
|
169
|
+
|
170
|
+
Notice the first parameter is a data structure, and the second is the name of that data structure. This idiom creates output like this:
|
171
|
+
|
172
|
+
```python
|
173
|
+
DC: 12: l:
|
174
|
+
DC: 12: [
|
175
|
+
DC: 12: 'this',
|
176
|
+
DC: 12: 'is',
|
177
|
+
DC: 12: 'a',
|
178
|
+
DC: 12: 'test'
|
179
|
+
DC: 12: ]
|
180
|
+
DC: 13: s:
|
181
|
+
DC: 13: {
|
182
|
+
DC: 13: 'a',
|
183
|
+
DC: 13: 'is',
|
184
|
+
DC: 13: 'test',
|
185
|
+
DC: 13: 'this'
|
186
|
+
DC: 13: }
|
187
|
+
DC: 14: d:
|
188
|
+
DC: 14: {
|
189
|
+
DC: 14: 'a': 'this',
|
190
|
+
DC: 14: 'b': 'is',
|
191
|
+
DC: 14: 'c': 'a',
|
192
|
+
DC: 14: 'd': 'test'
|
193
|
+
DC: 14: }
|
194
|
+
```
|
195
|
+
|
196
|
+
Notice sets are output in alphabetical order (according to their repr()
|
197
|
+
values). Since sets are unordered by nature, this makes them easier to inspect
|
198
|
+
visually without misrepresenting their contents.
|
199
|
+
|
152
200
|
That's a very general start. See DebugChannel's class docs for more.
|
153
201
|
|
154
202
|
<a id="debug.DebugChannel"></a>
|
@@ -200,8 +248,8 @@ line number reported in its output are something helpful to the
|
|
200
248
|
caller. For instance, the source line shouldn't be anything in this
|
201
249
|
DebugChannel class.
|
202
250
|
|
203
|
-
Use the ignoreModule() method to tell the DebugChannel object
|
204
|
-
other modules, and optionally, specific functions within that
|
251
|
+
Use the ignoreModule() method to tell the DebugChannel object to
|
252
|
+
ignore other modules, and optionally, specific functions within that
|
205
253
|
module.
|
206
254
|
|
207
255
|
<a id="debug.DebugChannel.__init__"></a>
|
@@ -417,11 +465,11 @@ supported.
|
|
417
465
|
#### \_\_call\_\_
|
418
466
|
|
419
467
|
```python
|
420
|
-
def __call__(
|
468
|
+
def __call__(*args, **kwargs)
|
421
469
|
```
|
422
470
|
|
423
471
|
If this DebugChannel instance is simply being called, this
|
424
|
-
method is a very simple wrapper around the write(...)
|
472
|
+
method is a very simple wrapper around the write(...) method. If
|
425
473
|
it is being used as a function decorator, that function entry
|
426
474
|
and exit are recorded to the DebugChannel, and this becomes a
|
427
475
|
more featuresome wrapper around the write(...) method.
|
@@ -442,13 +490,12 @@ output stream.
|
|
442
490
|
#### write
|
443
491
|
|
444
492
|
```python
|
445
|
-
def write(message)
|
493
|
+
def write(message, var=None)
|
446
494
|
```
|
447
495
|
|
448
496
|
If this debug instance is enabled, write the given message
|
449
|
-
using the our current format.
|
450
|
-
|
451
|
-
it. E.g.:
|
497
|
+
using the our current format. Return this DebugChannel instance
|
498
|
+
so further operations can be performed on it. E.g.:
|
452
499
|
|
453
500
|
```python
|
454
501
|
debug=DebugChannel(opt.debug)
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "jc-debug"
|
7
|
-
version = "1.0.
|
7
|
+
version = "1.0.4" # Consider using a proper versioning scheme
|
8
8
|
authors = [
|
9
9
|
{ name="Jeff Clough", email="jeff@cloughcottage.com" },
|
10
10
|
]
|
@@ -155,7 +155,7 @@ __all__ = [
|
|
155
155
|
"DebugChannel",
|
156
156
|
"line_iter",
|
157
157
|
]
|
158
|
-
__version__ = "1.0.
|
158
|
+
__version__ = "1.0.4"
|
159
159
|
|
160
160
|
import inspect, os, sys, traceback
|
161
161
|
|
@@ -397,7 +397,7 @@ class DebugChannel:
|
|
397
397
|
|
398
398
|
return self.write(seq)
|
399
399
|
|
400
|
-
def __call__(self,
|
400
|
+
def __call__(self, *args, **kwargs):
|
401
401
|
"""If this DebugChannel instance is simply being called, this
|
402
402
|
method is a very simple wrapper around the write(...) emthod. If
|
403
403
|
it is being used as a function decorator, that function entry
|
@@ -406,6 +406,7 @@ class DebugChannel:
|
|
406
406
|
|
407
407
|
lines = inspect.stack(context=2)[1].code_context
|
408
408
|
if lines and any(l.lstrip().startswith("@") for l in lines):
|
409
|
+
arg=args[0]
|
409
410
|
# We're being called as a decorator.
|
410
411
|
def f(*args, **kwargs):
|
411
412
|
# Record how this function is being called.
|
@@ -453,7 +454,7 @@ class DebugChannel:
|
|
453
454
|
return f
|
454
455
|
|
455
456
|
# This DebugChannel instance is being called as if it were a function.
|
456
|
-
return self.write(
|
457
|
+
return self.write(*args)
|
457
458
|
|
458
459
|
def writeTraceback(self, exc):
|
459
460
|
"""Write the given exception with traceback information to our
|
@@ -463,11 +464,14 @@ class DebugChannel:
|
|
463
464
|
for line in traceback.format_exception(exc):
|
464
465
|
self.write(line.rstrip())
|
465
466
|
|
466
|
-
def write(self, message):
|
467
|
+
def write(self, message, var=None):
|
467
468
|
"""If this debug instance is enabled, write the given message
|
468
|
-
using the our current format.
|
469
|
-
|
470
|
-
|
469
|
+
using the our current format. If the var argument is given and
|
470
|
+
message is of type list, tuple, set, or dict, then var is
|
471
|
+
treated as the caller's name for the `message` parameter.
|
472
|
+
|
473
|
+
In any case, return this DebugChannel instance so further
|
474
|
+
operations can be performed on it. E.g.:
|
471
475
|
|
472
476
|
```python
|
473
477
|
debug=DebugChannel(opt.debug)
|
@@ -494,88 +498,106 @@ class DebugChannel:
|
|
494
498
|
If message is a dictionary, each key/value pair is written out
|
495
499
|
as "key: value" to its own log line."""
|
496
500
|
|
497
|
-
if self.enabled:
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
501
|
+
if not self.enabled:
|
502
|
+
return self
|
503
|
+
|
504
|
+
# Update our formatted date and time if necessary.
|
505
|
+
t = int(get_time()) # Let's truncate at whole seconds.
|
506
|
+
if self._t != t:
|
507
|
+
t = self.time_tupler(t)
|
508
|
+
self._t = t
|
509
|
+
self.date = strftime(self.date_fmt, t)
|
510
|
+
self.time = strftime(self.time_fmt, t)
|
511
|
+
# Set local variables for date and time so they're available for output.
|
512
|
+
date = self.date
|
513
|
+
time = self.time
|
514
|
+
# Find the first non-ignored stack frame whence we were called. Bail out
|
515
|
+
# if the calling code is to be ignored for debugging purposes.
|
516
|
+
pathname, basename, line = None, None, None
|
517
|
+
for i, frame in enumerate(inspect.stack()):
|
518
|
+
# This is for debugging debug.py. It turns out Python 3.6 has a bug in
|
519
|
+
# inspect.stack() that can return outrageous values for frame.index.
|
520
|
+
# (So I'm asking for only one line of context, and I've stopped using the
|
521
|
+
# frame's untrustworthy index value.)
|
522
|
+
# print(f"""{i}:
|
523
|
+
# frame: {frame.frame!r}
|
524
|
+
# filename: {frame.filename!r}
|
525
|
+
# lineno: {frame.lineno!r}
|
526
|
+
# function: {frame.function!r}
|
527
|
+
# code_context: {frame.code_context!r}
|
528
|
+
# index: {frame.index!r}""")
|
529
|
+
p = os.path.normpath(frame.filename)
|
530
|
+
if p not in self.ignore:
|
531
|
+
break
|
532
|
+
if frame.function not in self.ignore[p]:
|
524
533
|
break
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
function =
|
534
|
-
|
535
|
-
|
536
|
-
code =
|
537
|
-
|
538
|
-
|
534
|
+
# Set some local variables so they'll be available to our callback
|
535
|
+
# function and for formatting.
|
536
|
+
pid = self.pid
|
537
|
+
pathname = os.path.normpath(frame.filename)
|
538
|
+
basename = os.path.basename(pathname)
|
539
|
+
line = frame.lineno
|
540
|
+
function = frame.function
|
541
|
+
if str(function) == "<module>":
|
542
|
+
function = "__main__"
|
543
|
+
code = frame.code_context
|
544
|
+
if code:
|
545
|
+
code = code[0].rstrip()
|
546
|
+
else:
|
547
|
+
code = None
|
548
|
+
indent = self.indstr * self.indlev
|
549
|
+
label = self.label
|
550
|
+
|
551
|
+
# If our caller provided a callback function, call that now.
|
552
|
+
if self.callback:
|
553
|
+
if not self.callback(**locals()):
|
554
|
+
return self # Return without writing any output.
|
555
|
+
|
556
|
+
# Format our message and write it to the debug stream.
|
557
|
+
if isinstance(message, (list, set, tuple)):
|
558
|
+
if isinstance(message, tuple):
|
559
|
+
left, right = "()"
|
560
|
+
elif isinstance(message, set):
|
561
|
+
left, right = "{}"
|
562
|
+
message=sorted(list(message),key=lambda val:repr(val))
|
539
563
|
else:
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
# If our caller provided a callback function, call that now.
|
545
|
-
if self.callback:
|
546
|
-
if not self.callback(**locals()):
|
547
|
-
return self # Return without writing any output.
|
548
|
-
|
549
|
-
# Format our message and write it to the debug stream.
|
550
|
-
if isinstance(message, (list, tuple)):
|
551
|
-
if isinstance(message, tuple):
|
552
|
-
left, right = "()"
|
553
|
-
else:
|
554
|
-
left, right = "[]"
|
555
|
-
messages = message
|
556
|
-
message = left
|
564
|
+
left, right = "[]"
|
565
|
+
messages = message
|
566
|
+
if var:
|
567
|
+
message = f"{var} ({len(messages)}):"
|
557
568
|
self.stream.write(self.fmt.format(**locals()))
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
569
|
+
message = left
|
570
|
+
self.stream.write(self.fmt.format(**locals()))
|
571
|
+
for i in range(len(messages)):
|
572
|
+
m = messages[i]
|
573
|
+
message = f"{self.indstr}{m!r}"
|
574
|
+
if i<len(messages)-1:
|
575
|
+
message+=','
|
562
576
|
self.stream.write(self.fmt.format(**locals()))
|
563
|
-
|
564
|
-
|
565
|
-
|
577
|
+
message = right
|
578
|
+
self.stream.write(self.fmt.format(**locals()))
|
579
|
+
elif isinstance(message, dict):
|
580
|
+
messages = dict(message)
|
581
|
+
if var:
|
582
|
+
message = f"{var} ({len(messages)}):"
|
566
583
|
self.stream.write(self.fmt.format(**locals()))
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
message = "}"
|
584
|
+
message = "{"
|
585
|
+
self.stream.write(self.fmt.format(**locals()))
|
586
|
+
for i,(k,v) in enumerate(messages.items()):
|
587
|
+
message = f"{self.indstr}{k!r}: {v!r}"
|
588
|
+
if i<len(messages)-1:
|
589
|
+
message+=','
|
571
590
|
self.stream.write(self.fmt.format(**locals()))
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
591
|
+
message = "}"
|
592
|
+
self.stream.write(self.fmt.format(**locals()))
|
593
|
+
elif isinstance(message, str) and os.linesep in message:
|
594
|
+
# Handle multiline strings here.
|
595
|
+
messages = message
|
596
|
+
for message in line_iter(messages):
|
577
597
|
self.stream.write(self.fmt.format(**locals()))
|
578
|
-
|
598
|
+
else:
|
599
|
+
self.stream.write(self.fmt.format(**locals()))
|
600
|
+
self.stream.flush()
|
579
601
|
|
580
602
|
# The caller can call other DebugChannel methods on our return value.
|
581
603
|
return self
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: jc-debug
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Makes debugging to the console both simpler and much more powerful.
|
5
5
|
Author-email: Jeff Clough <jeff@cloughcottage.com>
|
6
6
|
License: MIT License
|
@@ -42,8 +42,8 @@ Dynamic: license-file
|
|
42
42
|
|
43
43
|
# debug
|
44
44
|
|
45
|
-
This debug module a class named DebugChannel, instances of which are
|
46
|
-
|
45
|
+
This debug module a class named DebugChannel, instances of which are useful for
|
46
|
+
adding temporary or conditional debug output to CLI scripts.
|
47
47
|
|
48
48
|
The minimal boilerplate is pretty simple:
|
49
49
|
|
@@ -53,9 +53,9 @@ from debug import DebugChannel
|
|
53
53
|
dc=DebugChannel(True)
|
54
54
|
```
|
55
55
|
|
56
|
-
By default, DebugChannels are created disabled (the write no output), so
|
57
|
-
|
58
|
-
|
56
|
+
By default, DebugChannels are created disabled (the write no output), so the
|
57
|
+
`True` above enables `dc` during its instantiation so it needn't be enabled
|
58
|
+
later.
|
59
59
|
|
60
60
|
A more common way of handling this is ...
|
61
61
|
|
@@ -73,18 +73,18 @@ dc.enable(opt.debug)
|
|
73
73
|
...
|
74
74
|
```
|
75
75
|
|
76
|
-
This enables the `dc` DebugChannel instance only if --debug is given on
|
77
|
-
|
76
|
+
This enables the `dc` DebugChannel instance only if --debug is given on the
|
77
|
+
script's command line.
|
78
78
|
|
79
79
|
By default, output is sent to stdandard error and formatted as:
|
80
80
|
|
81
81
|
'{label}: [{pid}] {basename}:{line}:{function}: {indent}{message}\n'
|
82
82
|
|
83
|
-
There are several variables you can include in DebugChannel's output.
|
84
|
-
|
83
|
+
There are several variables you can include in DebugChannel's output. See the
|
84
|
+
DebugChannel docs below for a list.
|
85
85
|
|
86
|
-
So, for example, if you want to see how your variables are behaving in a
|
87
|
-
|
86
|
+
So, for example, if you want to see how your variables are behaving in a loop,
|
87
|
+
you might do something like this:
|
88
88
|
|
89
89
|
```python
|
90
90
|
from debug import DebugChannel
|
@@ -96,15 +96,15 @@ dc=DebugChannel(
|
|
96
96
|
|
97
97
|
dc("Entering loop ...").indent()
|
98
98
|
for i in range(5):
|
99
|
-
dc(f"i=
|
99
|
+
dc(f"{i=}").indent()
|
100
100
|
for j in range(3):
|
101
|
-
dc(f"j=
|
101
|
+
dc(f"{j=}")
|
102
102
|
dc.undent()("Done with j loop.")
|
103
103
|
dc.undent()("Done with i loop.")
|
104
104
|
```
|
105
105
|
|
106
|
-
That gives you this necely indented output. The indent() and undent()
|
107
|
-
|
106
|
+
That gives you this necely indented output. The indent() and undent() methods
|
107
|
+
are one thing that makes DebugChannels so nice to work with.
|
108
108
|
|
109
109
|
DC: 8: Entering loop ...
|
110
110
|
DC: 10: i=0
|
@@ -134,8 +134,8 @@ methods are one thing that makes DebugChannels so nice to work with.
|
|
134
134
|
DC: 13: Done with j loop.
|
135
135
|
DC: 14: Done with i loop.
|
136
136
|
|
137
|
-
That's a simple example, but you might be starting to get an idea of
|
138
|
-
|
137
|
+
That's a simple example, but you might be starting to get an idea of how
|
138
|
+
versatile DebugChannel instances can be.
|
139
139
|
|
140
140
|
A DebugChannel can also be used as a function decorator:
|
141
141
|
|
@@ -163,9 +163,9 @@ example2("First test",3)
|
|
163
163
|
example2("Second test",2)
|
164
164
|
```
|
165
165
|
|
166
|
-
This causes entry into and exit from the decorated function to be
|
167
|
-
|
168
|
-
|
166
|
+
This causes entry into and exit from the decorated function to be recorded in
|
167
|
+
the given DebugChannel's output. If you put that into a file named foo.py and
|
168
|
+
then run "python3 -m foo", you'll get this:
|
169
169
|
|
170
170
|
```
|
171
171
|
DC: __main__: example2('First test',3) ...
|
@@ -189,6 +189,54 @@ DC: example2: example1(...) returns None after 23µs.
|
|
189
189
|
DC: __main__: example2(...) returns None after 423ms.
|
190
190
|
```
|
191
191
|
|
192
|
+
When outputting a data structure, it's nice to be a little structured about it.
|
193
|
+
So if you send a list, tuple, dict, or set to a DebugChannel instance as the
|
194
|
+
whole message, it will be formatted one item at a time in the output.
|
195
|
+
|
196
|
+
```python
|
197
|
+
from debug import DebugChannel
|
198
|
+
|
199
|
+
dc=DebugChannel(True,fmt="{label}: {line:3}: {indent}{message}\n")
|
200
|
+
|
201
|
+
l="this is a test".split()
|
202
|
+
s=set(l)
|
203
|
+
d=dict(zip('abcd',l))
|
204
|
+
|
205
|
+
dc(l,'l')
|
206
|
+
dc(s,'s')
|
207
|
+
dc(d,'d')
|
208
|
+
```
|
209
|
+
|
210
|
+
Notice the first parameter is a data structure, and the second is the name of that data structure. This idiom creates output like this:
|
211
|
+
|
212
|
+
```python
|
213
|
+
DC: 12: l:
|
214
|
+
DC: 12: [
|
215
|
+
DC: 12: 'this',
|
216
|
+
DC: 12: 'is',
|
217
|
+
DC: 12: 'a',
|
218
|
+
DC: 12: 'test'
|
219
|
+
DC: 12: ]
|
220
|
+
DC: 13: s:
|
221
|
+
DC: 13: {
|
222
|
+
DC: 13: 'a',
|
223
|
+
DC: 13: 'is',
|
224
|
+
DC: 13: 'test',
|
225
|
+
DC: 13: 'this'
|
226
|
+
DC: 13: }
|
227
|
+
DC: 14: d:
|
228
|
+
DC: 14: {
|
229
|
+
DC: 14: 'a': 'this',
|
230
|
+
DC: 14: 'b': 'is',
|
231
|
+
DC: 14: 'c': 'a',
|
232
|
+
DC: 14: 'd': 'test'
|
233
|
+
DC: 14: }
|
234
|
+
```
|
235
|
+
|
236
|
+
Notice sets are output in alphabetical order (according to their repr()
|
237
|
+
values). Since sets are unordered by nature, this makes them easier to inspect
|
238
|
+
visually without misrepresenting their contents.
|
239
|
+
|
192
240
|
That's a very general start. See DebugChannel's class docs for more.
|
193
241
|
|
194
242
|
<a id="debug.DebugChannel"></a>
|
@@ -240,8 +288,8 @@ line number reported in its output are something helpful to the
|
|
240
288
|
caller. For instance, the source line shouldn't be anything in this
|
241
289
|
DebugChannel class.
|
242
290
|
|
243
|
-
Use the ignoreModule() method to tell the DebugChannel object
|
244
|
-
other modules, and optionally, specific functions within that
|
291
|
+
Use the ignoreModule() method to tell the DebugChannel object to
|
292
|
+
ignore other modules, and optionally, specific functions within that
|
245
293
|
module.
|
246
294
|
|
247
295
|
<a id="debug.DebugChannel.__init__"></a>
|
@@ -457,11 +505,11 @@ supported.
|
|
457
505
|
#### \_\_call\_\_
|
458
506
|
|
459
507
|
```python
|
460
|
-
def __call__(
|
508
|
+
def __call__(*args, **kwargs)
|
461
509
|
```
|
462
510
|
|
463
511
|
If this DebugChannel instance is simply being called, this
|
464
|
-
method is a very simple wrapper around the write(...)
|
512
|
+
method is a very simple wrapper around the write(...) method. If
|
465
513
|
it is being used as a function decorator, that function entry
|
466
514
|
and exit are recorded to the DebugChannel, and this becomes a
|
467
515
|
more featuresome wrapper around the write(...) method.
|
@@ -482,13 +530,12 @@ output stream.
|
|
482
530
|
#### write
|
483
531
|
|
484
532
|
```python
|
485
|
-
def write(message)
|
533
|
+
def write(message, var=None)
|
486
534
|
```
|
487
535
|
|
488
536
|
If this debug instance is enabled, write the given message
|
489
|
-
using the our current format.
|
490
|
-
|
491
|
-
it. E.g.:
|
537
|
+
using the our current format. Return this DebugChannel instance
|
538
|
+
so further operations can be performed on it. E.g.:
|
492
539
|
|
493
540
|
```python
|
494
541
|
debug=DebugChannel(opt.debug)
|
@@ -92,8 +92,8 @@ DC: test_basics: Message 2
|
|
92
92
|
dc(('a',2,True))
|
93
93
|
assert outstr()=="""\
|
94
94
|
DC: test_basics: (
|
95
|
-
DC: test_basics: 'a'
|
96
|
-
DC: test_basics: 2
|
95
|
+
DC: test_basics: 'a',
|
96
|
+
DC: test_basics: 2,
|
97
97
|
DC: test_basics: True
|
98
98
|
DC: test_basics: )
|
99
99
|
"""
|
@@ -102,18 +102,28 @@ DC: test_basics: )
|
|
102
102
|
dc(['a',2,True])
|
103
103
|
assert outstr()=="""\
|
104
104
|
DC: test_basics: [
|
105
|
-
DC: test_basics: 'a'
|
106
|
-
DC: test_basics: 2
|
105
|
+
DC: test_basics: 'a',
|
106
|
+
DC: test_basics: 2,
|
107
107
|
DC: test_basics: True
|
108
108
|
DC: test_basics: ]
|
109
|
+
"""
|
110
|
+
|
111
|
+
# Test set output.
|
112
|
+
dc(set(['a',2,True]))
|
113
|
+
assert outstr()=="""\
|
114
|
+
DC: test_basics: {
|
115
|
+
DC: test_basics: 'a',
|
116
|
+
DC: test_basics: 2,
|
117
|
+
DC: test_basics: True
|
118
|
+
DC: test_basics: }
|
109
119
|
"""
|
110
120
|
|
111
121
|
# Test dictionary output.
|
112
122
|
dc({'a':1,2:'b','c':True})
|
113
123
|
assert outstr()=="""\
|
114
124
|
DC: test_basics: {
|
115
|
-
DC: test_basics: 'a': 1
|
116
|
-
DC: test_basics: 2: 'b'
|
125
|
+
DC: test_basics: 'a': 1,
|
126
|
+
DC: test_basics: 2: 'b',
|
117
127
|
DC: test_basics: 'c': True
|
118
128
|
DC: test_basics: }
|
119
129
|
"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|