jc-debug 1.0.2__py3-none-any.whl → 1.0.4__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.
- debug/__init__.py +104 -82
- {jc_debug-1.0.2.dist-info → jc_debug-1.0.4.dist-info}/METADATA +76 -29
- jc_debug-1.0.4.dist-info/RECORD +6 -0
- jc_debug-1.0.2.dist-info/RECORD +0 -6
- {jc_debug-1.0.2.dist-info → jc_debug-1.0.4.dist-info}/WHEEL +0 -0
- {jc_debug-1.0.2.dist-info → jc_debug-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {jc_debug-1.0.2.dist-info → jc_debug-1.0.4.dist-info}/top_level.txt +0 -0
debug/__init__.py
CHANGED
@@ -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)
|
@@ -0,0 +1,6 @@
|
|
1
|
+
debug/__init__.py,sha256=Wf9H5YeflRQGniJN0mV25VFh-ziSRizTAq-fJ2Wo0vk,21914
|
2
|
+
jc_debug-1.0.4.dist-info/licenses/LICENSE,sha256=q63VTXVTnH1QywnYTQQ1TgL2NE0XLV9pmNTxBh8TnSY,1068
|
3
|
+
jc_debug-1.0.4.dist-info/METADATA,sha256=32QSBaZbOTa4PVeanxuYnK1m4-tukUv-7fHkNvI9nac,15976
|
4
|
+
jc_debug-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
5
|
+
jc_debug-1.0.4.dist-info/top_level.txt,sha256=mcOe8k8TNE9HfR15fLTnDFWyd3DViA-MH52TaEvPL4A,6
|
6
|
+
jc_debug-1.0.4.dist-info/RECORD,,
|
jc_debug-1.0.2.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
debug/__init__.py,sha256=YMDCZf2W-xANOxLRBPZAplu5ZpI_iJDXCR-BtABwqJY,21269
|
2
|
-
jc_debug-1.0.2.dist-info/licenses/LICENSE,sha256=q63VTXVTnH1QywnYTQQ1TgL2NE0XLV9pmNTxBh8TnSY,1068
|
3
|
-
jc_debug-1.0.2.dist-info/METADATA,sha256=6fhq0I8wOKrO28O6Rmm2FcesTZET3GHJEdNip86B9EI,14842
|
4
|
-
jc_debug-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
5
|
-
jc_debug-1.0.2.dist-info/top_level.txt,sha256=mcOe8k8TNE9HfR15fLTnDFWyd3DViA-MH52TaEvPL4A,6
|
6
|
-
jc_debug-1.0.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|