jc-debug 0.1.1__py3-none-any.whl → 1.0.0__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.
@@ -0,0 +1,530 @@
1
+ Metadata-Version: 2.4
2
+ Name: jc-debug
3
+ Version: 1.0.0
4
+ Summary: Makes debugging to the console both simpler and much more powerful.
5
+ Author-email: Jeff Clough <jeff@cloughcottage.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Jeff Clough
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/jc-handy/debug
29
+ Keywords: utility,toolkit,convenience
30
+ Classifier: Development Status :: 5 - Production/Stable
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Operating System :: OS Independent
36
+ Requires-Python: >=3.11
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Dynamic: license-file
40
+
41
+ <a id="debug"></a>
42
+
43
+ # debug
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.
47
+
48
+ The minimal boilerplate is pretty simple:
49
+
50
+ ```python
51
+ from debug import DebugChannel
52
+
53
+ dc=DebugChannel(True)
54
+ ```
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.
59
+
60
+ A more common way of handling this is ...
61
+
62
+ ```python
63
+ from argparse import ArgumentParser
64
+ from debug import DebugChannel
65
+
66
+ dc=DebugChannel()
67
+
68
+ ap=ArgumentParser()
69
+ ap.add_argument('--debug',action='store_true',help="Enable debug output.")
70
+ opt=ap.parse_args()
71
+ dc.enable(opt.debug)
72
+
73
+ ...
74
+ ```
75
+
76
+ This enables the `dc` DebugChannel instance only if --debug is given on
77
+ the script's command line.
78
+
79
+ By default, output is sent to stdandard error and formatted as:
80
+
81
+ '{label}: [{pid}] {basename}:{line}:{function}: {indent}{message}\n'
82
+
83
+ There are several variables you can include in DebugChannel's output.
84
+ See the DebugChannel docs below for a list.
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:
88
+
89
+ ```python
90
+ from debug import DebugChannel
91
+
92
+ dc=DebugChannel(
93
+ True,
94
+ fmt="{label}: {line:3}: {indent}{message}\n"
95
+ )
96
+
97
+ dc("Entering loop ...").indent()
98
+ for i in range(5):
99
+ dc(f"i={i}").indent()
100
+ for j in range(3):
101
+ dc(f"j={j}")
102
+ dc.undent()("Done with j loop.")
103
+ dc.undent()("Done with i loop.")
104
+ ```
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.
108
+
109
+ DC: 8: Entering loop ...
110
+ DC: 10: i=0
111
+ DC: 12: j=0
112
+ DC: 12: j=1
113
+ DC: 12: j=2
114
+ DC: 13: Done with j loop.
115
+ DC: 10: i=1
116
+ DC: 12: j=0
117
+ DC: 12: j=1
118
+ DC: 12: j=2
119
+ DC: 13: Done with j loop.
120
+ DC: 10: i=2
121
+ DC: 12: j=0
122
+ DC: 12: j=1
123
+ DC: 12: j=2
124
+ DC: 13: Done with j loop.
125
+ DC: 10: i=3
126
+ DC: 12: j=0
127
+ DC: 12: j=1
128
+ DC: 12: j=2
129
+ DC: 13: Done with j loop.
130
+ DC: 10: i=4
131
+ DC: 12: j=0
132
+ DC: 12: j=1
133
+ DC: 12: j=2
134
+ DC: 13: Done with j loop.
135
+ DC: 14: Done with i loop.
136
+
137
+ That's a simple example, but you might be starting to get an idea of
138
+ how versatile DebugChannel instances can be.
139
+
140
+ A DebugChannel can also be used as a function decorator:
141
+
142
+ ```python
143
+ import time
144
+ from src.debug import DebugChannel
145
+
146
+ def delay(**kwargs):
147
+ time.sleep(.1)
148
+ return True
149
+
150
+ dc=DebugChannel(True,callback=delay)
151
+ dc.setFormat("{label}: {function}: {indent}{message}\n")
152
+
153
+ @dc
154
+ def example1(msg):
155
+ print(msg)
156
+
157
+ @dc
158
+ def example2(msg,count):
159
+ for i in range(count):
160
+ example1(f"{i+1}: {msg}")
161
+
162
+ example2("First test",3)
163
+ example2("Second test",2)
164
+ ```
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:
169
+
170
+ ```
171
+ DC: __main__: example2('First test',3) ...
172
+ DC: example2: example1('1: First test') ...
173
+ 1: First test
174
+ DC: example2: example1(...) returns None after 45µs.
175
+ DC: example2: example1('2: First test') ...
176
+ 2: First test
177
+ DC: example2: example1(...) returns None after 29µs.
178
+ DC: example2: example1('3: First test') ...
179
+ 3: First test
180
+ DC: example2: example1(...) returns None after 26µs.
181
+ DC: __main__: example2(...) returns None after 630ms.
182
+ DC: __main__: example2('Second test',2) ...
183
+ DC: example2: example1('1: Second test') ...
184
+ 1: Second test
185
+ DC: example2: example1(...) returns None after 28µs.
186
+ DC: example2: example1('2: Second test') ...
187
+ 2: Second test
188
+ DC: example2: example1(...) returns None after 23µs.
189
+ DC: __main__: example2(...) returns None after 423ms.
190
+ ```
191
+
192
+ That's a very general start. See DebugChannel's class docs for more.
193
+
194
+ <a id="debug.DebugChannel"></a>
195
+
196
+ ## DebugChannel Objects
197
+
198
+ ```python
199
+ class DebugChannel()
200
+ ```
201
+
202
+ Objects of this class are useful for debugging, and this is even
203
+ more powerful when combined with loggy.LogStream to write all debug
204
+ output to some appropriate syslog facility. Here's an example, put
205
+ into an executable script called dc-log-test:
206
+
207
+ ```python
208
+ #!/usr/bin/env python
209
+
210
+ from debug import DebugChannel
211
+ from loggy import LogStream
212
+
213
+ dc=DebugChannel(
214
+ True,
215
+ stream=LogStream(facility='user'),
216
+ fmt='{label}: {basename}({line}): {indent}{message}\n'
217
+ )
218
+ dc('Testing')
219
+ ```
220
+
221
+ The output in /var/log/user.log (which might be a different path on
222
+ your system) might look like this:
223
+
224
+ Aug 16 22:58:16 pi4 x[18478] DC: dc-log-test(11): Testing
225
+
226
+ What I really like about this is that the source filename and line
227
+ number are included in the log output. The "dc('Testing')" call is on
228
+ line 11 of dc-log-test.
229
+
230
+ Run this module directly with
231
+
232
+ python3 -m debug
233
+
234
+ to see a demonstration of indenture. The example code for that demo
235
+ is at the bottom of the debug.py source file.
236
+
237
+ IGNORING MODULES:
238
+ DebugChannel.write() goes to some length to ensure the filename and
239
+ line number reported in its output are something helpful to the
240
+ caller. For instance, the source line shouldn't be anything in this
241
+ DebugChannel class.
242
+
243
+ Use the ignoreModule() method to tell the DebugChannel object ignore
244
+ other modules, and optionally, specific functions within that
245
+ module.
246
+
247
+ <a id="debug.DebugChannel.__init__"></a>
248
+
249
+ #### \_\_init\_\_
250
+
251
+ ```python
252
+ def __init__(enabled=False,
253
+ stream=sys.stderr,
254
+ label="DC",
255
+ indent_with=" ",
256
+ fmt="{label}: {basename}:{line}:{function}: {indent}{message}\n",
257
+ date_fmt="%Y-%m-%d",
258
+ time_fmt="%H:%M:%S",
259
+ time_tupler=localtime,
260
+ callback=None)
261
+ ```
262
+
263
+ Initialize this new DebugChannel instance.
264
+
265
+ **Arguments**:
266
+
267
+
268
+ * enabled: True if this DebugChannel object is allowed to
269
+ output messages. False if it should be quiet.
270
+ * stream: The stream (or stream-like object) to write
271
+ messages to.
272
+ * label: A string indicated what we're doing.
273
+ * indent_with: Indenting uses this string value.
274
+ * fmt: Format of debug output. See setFormat().
275
+ * date_fmt: strftime() uses this string to format dates.
276
+ * time_fmt: strftime() uses this string to format times.
277
+ * time_tupler: This is either time.localtime or time.gmtime and
278
+ defaults to localtime.
279
+ * callback: A function accepting keyword arguments and returning
280
+ True if the current message is to be output. The keyword
281
+ arguments are all the local variables of DebugChannel.write().
282
+ Of particular interest might be "stack" and all the variables
283
+ available for formatting (label, basename, pid, function, line,
284
+ indent, and message).
285
+
286
+ <a id="debug.DebugChannel.__bool__"></a>
287
+
288
+ #### \_\_bool\_\_
289
+
290
+ ```python
291
+ def __bool__()
292
+ ```
293
+
294
+ Return the Enabled state of this DebugChannel object. It is
295
+ somtimes necessary to logically test whether our code is in
296
+ debug mode at runtime, and this method makes that very simple.
297
+
298
+ ```python
299
+ d=DebugChannel(opt.debug)
300
+ .
301
+ .
302
+ .
303
+ if d:
304
+ d("Getting diagnostics ...")
305
+ diagnostics=get_some_computationally_expensive_data()
306
+ d(diagnostics)
307
+ ```
308
+
309
+ <a id="debug.DebugChannel.enable"></a>
310
+
311
+ #### enable
312
+
313
+ ```python
314
+ def enable(state=True)
315
+ ```
316
+
317
+ Allow this DebugChannel object to write messages if state is
318
+ True. Return the previous state as a boolean.
319
+
320
+ <a id="debug.DebugChannel.disable"></a>
321
+
322
+ #### disable
323
+
324
+ ```python
325
+ def disable()
326
+ ```
327
+
328
+ Inhibit output from this DebugChannel object, and return its
329
+ previous "enabled" state.
330
+
331
+ <a id="debug.DebugChannel.ignoreModule"></a>
332
+
333
+ #### ignoreModule
334
+
335
+ ```python
336
+ def ignoreModule(name, *args)
337
+ ```
338
+
339
+ Given the name of a module, e.g. "debug"), ignore any entries
340
+ in our call stack from that module. Any subsequent arguments
341
+ must be the names of functions to be ignored within that module.
342
+ If no such functions are named, all calls from that module will
343
+ be ignored.
344
+
345
+ <a id="debug.DebugChannel.setDateFormat"></a>
346
+
347
+ #### setDateFormat
348
+
349
+ ```python
350
+ def setDateFormat(fmt)
351
+ ```
352
+
353
+ Use the formatting rules of strftime() to format the "date"
354
+ value to be output in debug messages. Return the previous date
355
+ format string.
356
+
357
+ <a id="debug.DebugChannel.setTimeFormat"></a>
358
+
359
+ #### setTimeFormat
360
+
361
+ ```python
362
+ def setTimeFormat(fmt)
363
+ ```
364
+
365
+ Use the formatting rules of strftime() to format the "time"
366
+ value to be output in debug messages. Return the previous time
367
+ format string.
368
+
369
+ <a id="debug.DebugChannel.setIndentString"></a>
370
+
371
+ #### setIndentString
372
+
373
+ ```python
374
+ def setIndentString(s)
375
+ ```
376
+
377
+ Set the string to indent string. Return this DebugChannel
378
+ object. E.g. at indent level 3, the "{indent}" portion of the
379
+ formatted debug will contain 3 copies of the string you set with
380
+ this function. So ' ' will indent two spaces per indention
381
+ level. Another popular choice is '| ' to make longer indention
382
+ runs easier to follow in the debug output.
383
+
384
+ <a id="debug.DebugChannel.setFormat"></a>
385
+
386
+ #### setFormat
387
+
388
+ ```python
389
+ def setFormat(fmt)
390
+ ```
391
+
392
+ Set the format of our debug statements. The format defaults
393
+ to:
394
+
395
+ '{label}: {basename}:{line}:{function}: {indent}{message}\n'
396
+
397
+ Fields:
398
+ * {date}: current date (see setDateFormat())
399
+ * {time}: current time (see setTimeFormat())
400
+ * {pid}: numeric ID of the current process
401
+ * {label}: what type of thing is getting logged (default: 'DC')
402
+ * {pathname}: full path of the calling source file
403
+ * {basename}: base name of the calling source file
404
+ * {function}: name of function debug.write() was called from
405
+ * {line}: number of the calling line of code in its source file
406
+ * {code}: the Python code at the given line of the given file
407
+ * {indent}: indent string multiplied by the indention level
408
+ * {message}: the message to be written
409
+
410
+ All non-field text is literal text. The '\n' at the end is
411
+ required if you want a line ending at the end of each message.
412
+ If your DebugChannel object is configured to write to a
413
+ LogStream object that writes to syslog or something similar, you
414
+ might want to remove the {date} and {time} (and maybe {label})
415
+ fields from the default format string to avoid logging these
416
+ values redundantly.
417
+
418
+ <a id="debug.DebugChannel.indent"></a>
419
+
420
+ #### indent
421
+
422
+ ```python
423
+ def indent(indent=1)
424
+ ```
425
+
426
+ Increase this object's current indenture by this value (which
427
+ might be negative. Return this DebugChannel opject with the
428
+ adjusted indenture. See write() for how this might be used.
429
+
430
+ <a id="debug.DebugChannel.undent"></a>
431
+
432
+ #### undent
433
+
434
+ ```python
435
+ def undent(indent=1)
436
+ ```
437
+
438
+ Decrease this object's current indenture by this value (which
439
+ might be negative. Return this DebugChannel object with the
440
+ adjusted indenture. See write() for how this might be used.
441
+
442
+ <a id="debug.DebugChannel.writelines"></a>
443
+
444
+ #### writelines
445
+
446
+ ```python
447
+ def writelines(seq)
448
+ ```
449
+
450
+ Just a wrapper around write(), since that method handles
451
+ sequences (and other things) just fine. writelines() is only
452
+ provided for compatibility with code that expects it to be
453
+ supported.
454
+
455
+ <a id="debug.DebugChannel.__call__"></a>
456
+
457
+ #### \_\_call\_\_
458
+
459
+ ```python
460
+ def __call__(arg, *args, **kwargs)
461
+ ```
462
+
463
+ If this DebugChannel instance is simply being called, this
464
+ method is a very simple wrapper around the write(...) emthod. If
465
+ it is being used as a function decorator, that function entry
466
+ and exit are recorded to the DebugChannel, and this becomes a
467
+ more featuresome wrapper around the write(...) method.
468
+
469
+ <a id="debug.DebugChannel.writeTraceback"></a>
470
+
471
+ #### writeTraceback
472
+
473
+ ```python
474
+ def writeTraceback(exc)
475
+ ```
476
+
477
+ Write the given exception with traceback information to our
478
+ output stream.
479
+
480
+ <a id="debug.DebugChannel.write"></a>
481
+
482
+ #### write
483
+
484
+ ```python
485
+ def write(message)
486
+ ```
487
+
488
+ 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.:
492
+
493
+ ```python
494
+ debug=DebugChannel(opt.debug)
495
+ debug('Testing')
496
+
497
+ def func(arg):
498
+ debug.write("Entering func(arg=%r)"%(arg,)).indent(1)
499
+ for i in range(3):
500
+ debug(f"{i=}")
501
+ debug.indent(-1).write("Leaving func(...) normally")
502
+ ```
503
+
504
+ This lets the caller decide whether to change indenture among
505
+ other things before or after the message is written.
506
+
507
+ If message is a single string containing no line endings, that
508
+ single value will be outout. if message contains at least one
509
+ newline (the value of os.linesep), each line is output on a
510
+ debug line of its own.
511
+
512
+ If message is a list or tuple, each item in that sequence will
513
+ be output on its own line.
514
+
515
+ If message is a dictionary, each key/value pair is written out
516
+ as "key: value" to its own log line.
517
+
518
+ <a id="debug.line_iter"></a>
519
+
520
+ #### line\_iter
521
+
522
+ ```python
523
+ def line_iter(s)
524
+ ```
525
+
526
+ This iterator facilitates stepping through each line of a multi-
527
+ line string in place, without having to create a list containing
528
+ those lines. This is similar to `str.splitlines()`, but it yields
529
+ slices of the original string rather than returning a list of copies
530
+ of segments of the original.
@@ -0,0 +1,6 @@
1
+ debug/__init__.py,sha256=gP7bEal0DPhxS_XuGQTh3YsdfJOSqfEwPYX9QlzYoBg,21259
2
+ jc_debug-1.0.0.dist-info/licenses/LICENSE,sha256=q63VTXVTnH1QywnYTQQ1TgL2NE0XLV9pmNTxBh8TnSY,1068
3
+ jc_debug-1.0.0.dist-info/METADATA,sha256=kvbLuNaqZELXhsITzsXRjMMaeWpCff9-TBGIGe4qXus,14842
4
+ jc_debug-1.0.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
5
+ jc_debug-1.0.0.dist-info/top_level.txt,sha256=mcOe8k8TNE9HfR15fLTnDFWyd3DViA-MH52TaEvPL4A,6
6
+ jc_debug-1.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.1)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,47 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: jc-debug
3
- Version: 0.1.1
4
- Summary: Makes debugging to the console both simpler and much more powerful.
5
- Author-email: Jeff Clough <jeff@cloughcottage.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2025 Jeff Clough
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
- Project-URL: Homepage, https://github.com/jc-handy/debug
29
- Keywords: utility,toolkit,convenience
30
- Classifier: Development Status :: 3 - Alpha
31
- Classifier: Intended Audience :: Developers
32
- Classifier: License :: OSI Approved :: MIT License
33
- Classifier: Programming Language :: Python :: 3
34
- Classifier: Programming Language :: Python :: 3.11
35
- Classifier: Operating System :: OS Independent
36
- Requires-Python: >=3.11
37
- Description-Content-Type: text/markdown
38
- License-File: LICENSE
39
- Dynamic: license-file
40
-
41
- # Debug
42
-
43
- ## Description
44
- This package simplifies debug output and makes it more powerful.
45
-
46
- ## Installation
47
- Run `python3 -m pip install jc-debug` to install it. This will install the package named "debug" in your site-packages.
@@ -1,6 +0,0 @@
1
- debug/__init__.py,sha256=AsUg7VGA26-1bB7GDZ8xV7rjjFssgbTr4ylXQyEpL48,19030
2
- jc_debug-0.1.1.dist-info/licenses/LICENSE,sha256=q63VTXVTnH1QywnYTQQ1TgL2NE0XLV9pmNTxBh8TnSY,1068
3
- jc_debug-0.1.1.dist-info/METADATA,sha256=KBG9T7quKGjkF7ALtYuQxgzr3Cvi0PN8MsHcrZ3Tc74,2141
4
- jc_debug-0.1.1.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
5
- jc_debug-0.1.1.dist-info/top_level.txt,sha256=mcOe8k8TNE9HfR15fLTnDFWyd3DViA-MH52TaEvPL4A,6
6
- jc_debug-0.1.1.dist-info/RECORD,,