• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Dennis-van-Gils / python-dvg-debug-functions / 4285921607

pending completion
4285921607

push

github

Dennis van Gils
Added coverage results

33 of 33 new or added lines in 1 file covered. (100.0%)

15 existing lines in 1 file now uncovered.

111 of 127 relevant lines covered (87.4%)

2.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.22
/src/dvg_debug_functions.py
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""Provides functions for neatly printing debug information to the terminal
3✔
4
output, well-suited for multithreaded programs.
5
"""
6
__author__ = "Dennis van Gils"
3✔
7
__authoremail__ = "vangils.dennis@gmail.com"
3✔
8
__url__ = "https://github.com/Dennis-van-Gils/python-dvg-debug-functions"
3✔
9
__date__ = "27-02-2023"
3✔
10
__version__ = "2.4.0"
3✔
11

12
import os
3✔
13
import sys
3✔
14
import time
3✔
15
import traceback
3✔
16
import inspect
3✔
17

18
# Mechanism to support both PyQt and PySide
19
# -----------------------------------------
20

21
PYQT5 = "PyQt5"
3✔
22
PYQT6 = "PyQt6"
3✔
23
PYSIDE2 = "PySide2"
3✔
24
PYSIDE6 = "PySide6"
3✔
25
QT_LIB_ORDER = [PYQT5, PYSIDE2, PYSIDE6, PYQT6]
3✔
26
QT_LIB = None
3✔
27

28
if QT_LIB is None:
3✔
29
    for lib in QT_LIB_ORDER:
3✔
30
        if lib in sys.modules:
3✔
UNCOV
31
            QT_LIB = lib
×
UNCOV
32
            break
×
33

34
if QT_LIB is None:
3✔
35
    for lib in QT_LIB_ORDER:
3✔
36
        try:
3✔
37
            __import__(lib)
3✔
UNCOV
38
            QT_LIB = lib
×
UNCOV
39
            break
×
40
        except ImportError:
3✔
41
            pass
3✔
42

43
# if QT_LIB is None:
44
#    this_file = __file__.split(os.sep)[-1]
45
#    raise ImportError(
46
#        f"{this_file} requires PyQt5, PyQt6, PySide2 or PySide6; "
47
#        "none of these packages could be imported."
48
#    )
49

50
# fmt: off
51
# pylint: disable=import-error, no-name-in-module
52
if QT_LIB == PYQT5:
3✔
UNCOV
53
    from PyQt5 import QtCore                               # type: ignore
×
UNCOV
54
    dprint_mutex = QtCore.QMutex()
×
55
elif QT_LIB == PYQT6:
3✔
UNCOV
56
    from PyQt6 import QtCore                               # type: ignore
×
57
    dprint_mutex = QtCore.QMutex()
×
58
elif QT_LIB == PYSIDE2:
3✔
UNCOV
59
    from PySide2 import QtCore                             # type: ignore
×
UNCOV
60
    dprint_mutex = QtCore.QMutex()
×
61
elif QT_LIB == PYSIDE6:
3✔
UNCOV
62
    from PySide6 import QtCore                             # type: ignore
×
UNCOV
63
    dprint_mutex = QtCore.QMutex()
×
64
# pylint: enable=import-error, no-name-in-module
65
# fmt: on
66

67
# \end[Mechanism to support both PyQt and PySide]
68
# -----------------------------------------------
69

70
# Setting this global module variable to `True` or `False` will overrule the
71
# argument `show_full_paths` in `print_fancy_traceback()`.
72
OVERRULE_SHOW_FULL_PATHS = None
3✔
73

74

75
class ANSI:
3✔
76
    NONE = ""
3✔
77
    RED = "\033[1;31m"
3✔
78
    GREEN = "\033[1;32m"
3✔
79
    YELLOW = "\033[1;33m"
3✔
80
    BLUE = "\033[1;34m"
3✔
81
    PURPLE = "\033[1;35m"  # aka MAGENTA
3✔
82
    MAGENTA = "\033[1;35m"
3✔
83
    CYAN = "\033[1;36m"
3✔
84
    WHITE = "\033[1;37m"
3✔
85

86

87
def dprint(str_msg: str, ANSI_color: str = None):
3✔
88
    """'Debug' print a single line to the terminal with optional ANSI color
89
    codes. There is a lot of overhead using this print statement, but it is
90
    particularly well-suited for multithreaded PyQt programs where multiple
91
    threads are each printing information to the same terminal. The ``dprint()``
92
    function ensure that each line sent to the terminal will remain as a
93
    continious single line, whereas a regular ``print()`` statement will likely
94
    result in the lines getting mixed up.
95

96
    The line will be terminated with a newline character and the terminal output
97
    buffer is forced to flush before and after every print. In addition, if
98
    PyQt5 is present in the Python environment, then a mutex lock will be
99
    obtained and released again for each ``dprint()`` execution.
100
    """
101
    # Explicitly ending the string with a newline '\n' character, instead
102
    # of letting the print statement end it for you (end='\n'), fixes the
103
    # problem of single lines getting printed to the terminal with
104
    # intermittently delayed newlines when coming from different threads.
105
    # I.e. it prevents:
106
    # >: Output line of thread 1Output line of thread 2   (\n)
107
    # >:                                                  (\n)
108
    # and makes sure we get:
109
    # >: Output line of thread 1                          (\n)
110
    # >: Output line of thread 2                          (\n)
111

112
    if QT_LIB is not None:
3✔
UNCOV
113
        locker = QtCore.QMutexLocker(dprint_mutex)
×
114

115
    sys.stdout.flush()
3✔
116
    if ANSI_color is None:
3✔
117
        print("%s\n" % str_msg, end="")
3✔
118
    else:
119
        print("%s%s%s\n" % (ANSI_color, str_msg, ANSI.WHITE), end="")
3✔
120
    sys.stdout.flush()
3✔
121

122
    if QT_LIB is not None:
3✔
UNCOV
123
        locker.unlock()
×
124

125

126
def tprint(str_msg: str, ANSI_color: str = None):
3✔
127
    """Identical to ``dprint()``, but now prepended with a ``time.perf_counter()``
128
    timestamp.
129
    """
130
    dprint("%.4f %s" % (time.perf_counter(), str_msg), ANSI_color)
3✔
131

132

133
def print_fancy_traceback(
3✔
134
    err=None, back: int = 3, show_full_paths: bool = False
135
):
136
    """Print the exception or the current regular call-stack traceback to the
137
    terminal, using ANSI color codes that mimic the IPython command shell.
138

139
    Args:
140
        err (``Exception`` | ``str`` | ``None``, optional):
141
            When ``err`` is of type ``Exception``, then an exception traceback will
142
            be printed. When ``err`` is of another type, then the current regular
143
            call-stack traceback will be printed.
144

145
            Default: ``None``
146

147
        back (``int``, optional):
148
            Depth of the traceback to print.
149

150
            Default: ``3``
151

152
        show_full_paths (``bool``, optional):
153
            Shows the full filepath in the traceback when True, otherwise just
154
            the filename.
155

156
            Default: ``False``
157
    """
158

159
    def print_frame(filename, line_no, frame_name):
3✔
160
        print(
3✔
161
            (
162
                ANSI.CYAN
163
                + "File "
164
                + ANSI.GREEN
165
                + '"%s"'
166
                + ANSI.CYAN
167
                + ", line "
168
                + ANSI.GREEN
169
                + "%s"
170
                + ANSI.CYAN
171
                + ", in "
172
                + ANSI.PURPLE
173
                + "%s"
174
                + ANSI.WHITE
175
            )
176
            % (filename, line_no, frame_name)
177
        )
178

179
    print(
3✔
180
        "\n"
181
        + ANSI.WHITE
182
        + "Fancy traceback "
183
        + ANSI.CYAN
184
        + "(most recent call last)"
185
        + ANSI.WHITE
186
        + ":"
187
    )
188

189
    if isinstance(err, Exception):
3✔
190
        # Exception traceback
191
        etype, evalue, tb = sys.exc_info()
3✔
192
        stack = traceback.extract_tb(tb)
3✔
193
        stack = stack[-back:]
3✔
194

195
        for frame in stack:
3✔
196
            if OVERRULE_SHOW_FULL_PATHS is not None:
3✔
UNCOV
197
                file_descr = (
×
198
                    frame.filename
199
                    if OVERRULE_SHOW_FULL_PATHS
200
                    else os.path.basename(frame.filename)
201
                )
202
            else:
203
                file_descr = (
3✔
204
                    frame.filename
205
                    if show_full_paths
206
                    else os.path.basename(frame.filename)
207
                )
208

209
            print_frame(file_descr, frame.lineno, frame.name)
3✔
210

211
        print("----> %s" % stack[-1].line)
3✔
212
        print(
3✔
213
            (ANSI.RED + "%s: " + ANSI.WHITE + "%s") % (etype.__name__, evalue)
214
        )
215

216
    else:
217
        # Regular call stack traceback
218
        stack = inspect.stack()
3✔
219
        stack.reverse()
3✔
220
        stack = stack[-back - 1 : -1]
3✔
221

222
        for frame in stack:
3✔
223
            if OVERRULE_SHOW_FULL_PATHS is not None:
3✔
UNCOV
224
                file_descr = (
×
225
                    frame.filename
226
                    if OVERRULE_SHOW_FULL_PATHS
227
                    else os.path.basename(frame.filename)
228
                )
229
            else:
230
                file_descr = (
3✔
231
                    frame.filename
232
                    if show_full_paths
233
                    else os.path.basename(frame.filename)
234
                )
235

236
            print_frame(file_descr, frame.lineno, frame.function)
3✔
237

238
        if isinstance(err, str):
3✔
239
            print((ANSI.RED + "Error: " + ANSI.WHITE + "%s") % err)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc