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 CHANGED
@@ -155,7 +155,7 @@ __all__ = [
155
155
  "DebugChannel",
156
156
  "line_iter",
157
157
  ]
158
- __version__ = "1.0.2"
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, arg, *args, **kwargs):
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(arg)
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. In any case, return this
469
- DebugChannel instance so further operations can be performed on
470
- it. E.g.:
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
- # Update our formatted date and time if necessary.
499
- t = int(get_time()) # Let's truncate at whole seconds.
500
- if self._t != t:
501
- t = self.time_tupler(t)
502
- self._t = t
503
- self.date = strftime(self.date_fmt, t)
504
- self.time = strftime(self.time_fmt, t)
505
- # Set local variables for date and time so they're available for output.
506
- date = self.date
507
- time = self.time
508
- # Find the first non-ignored stack frame whence we were called.
509
- pathname, basename, line = None, None, None
510
- for i, frame in enumerate(inspect.stack()):
511
- # This is for debugging debug.py. It turns out Python 3.6 has a bug in
512
- # inspect.stack() that can return outrageous values for frame.index.
513
- # (So I'm asking for only one line of context, and I've stopped using the
514
- # frame's untrustworthy index value.)
515
- # print(f"""{i}:
516
- # frame: {frame.frame!r}
517
- # filename: {frame.filename!r}
518
- # lineno: {frame.lineno!r}
519
- # function: {frame.function!r}
520
- # code_context: {frame.code_context!r}
521
- # index: {frame.index!r}""")
522
- p = os.path.normpath(frame.filename)
523
- if p not in self.ignore:
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
- if frame.function not in self.ignore[p]:
526
- break
527
- # Set some local variables so they'll be available to our callback
528
- # function and for formatting.
529
- pid = self.pid
530
- pathname = os.path.normpath(frame.filename)
531
- basename = os.path.basename(pathname)
532
- line = frame.lineno
533
- function = frame.function
534
- if str(function) == "<module>":
535
- function = "__main__"
536
- code = frame.code_context
537
- if code:
538
- code = code[0].rstrip()
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
- code = None
541
- indent = self.indstr * self.indlev
542
- label = self.label
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
- for message in messages:
559
- message = self.indstr + repr(message)
560
- self.stream.write(self.fmt.format(**locals()))
561
- message = right
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
- elif isinstance(message, dict):
564
- messages = dict(message)
565
- message = "{"
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
- for k in messages.keys():
568
- message = f"{self.indstr}{k!r}: {messages[k]!r}"
569
- self.stream.write(self.fmt.format(**locals()))
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
- elif isinstance(message, str) and os.linesep in message:
573
- messages = message
574
- for message in line_iter(messages):
575
- self.stream.write(self.fmt.format(**locals()))
576
- else:
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
- self.stream.flush()
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.2
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
- useful for adding temporary or conditional debug output to CLI scripts.
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
- the `True` above enables `dc` during its instantiation so it needn't be
58
- enabled later.
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
- the script's command line.
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
- See the DebugChannel docs below for a list.
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
- loop, you might do something like this:
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={i}").indent()
99
+ dc(f"{i=}").indent()
100
100
  for j in range(3):
101
- dc(f"j={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
- methods are one thing that makes DebugChannels so nice to work with.
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
- how versatile DebugChannel instances can be.
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
- recorded in the given DebugChannel's output. If you put that into a file
168
- named foo.py and then run "python3 -m foo", you'll get this:
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 ignore
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__(arg, *args, **kwargs)
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(...) emthod. If
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. In any case, return this
490
- DebugChannel instance so further operations can be performed on
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,,
@@ -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,,