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

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

22 Jun 2024 06:06PM UTC coverage: 94.286% (-2.6%) from 96.85%
9627347522

push

github

Dennis-van-Gils
Using pyqt and f-strings

22 of 24 new or added lines in 2 files covered. (91.67%)

2 existing lines in 1 file now uncovered.

99 of 105 relevant lines covered (94.29%)

14.14 hits per line

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

91.18
/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
4
output, well-suited for multithreaded programs.
5
"""
6
__author__ = "Dennis van Gils"
15✔
7
__authoremail__ = "vangils.dennis@gmail.com"
15✔
8
__url__ = "https://github.com/Dennis-van-Gils/python-dvg-debug-functions"
15✔
9
__date__ = "22-06-2024"
15✔
10
__version__ = "2.5.0"
15✔
11

12
import os
15✔
13
import sys
15✔
14
import time
15✔
15
import traceback
15✔
16
import inspect
15✔
17
from typing import Union
15✔
18

19
dprint_mutex = None
15✔
20
try:
15✔
21
    from qtpy import QtCore
15✔
22

NEW
23
    dprint_mutex = QtCore.QMutex()
×
24
except ImportError:
15✔
25
    pass
15✔
26

27
# Setting this global module variable to `True` or `False` will overrule the
28
# argument `show_full_paths` in `print_fancy_traceback()`.
29
OVERRULE_SHOW_FULL_PATHS = None
15✔
30

31

32
class ANSI:
15✔
33
    NONE = ""
15✔
34
    RED = "\033[1;31m"
15✔
35
    GREEN = "\033[1;32m"
15✔
36
    YELLOW = "\033[1;33m"
15✔
37
    BLUE = "\033[1;34m"
15✔
38
    PURPLE = "\033[1;35m"  # aka MAGENTA
15✔
39
    MAGENTA = "\033[1;35m"
15✔
40
    CYAN = "\033[1;36m"
15✔
41
    WHITE = "\033[1;37m"
15✔
42

43

44
def dprint(str_msg: str, ANSI_color: Union[str, None] = None):
15✔
45
    """'Debug' print a single line to the terminal with optional ANSI color
46
    codes. There is a lot of overhead using this print statement, but it is
47
    particularly well-suited for multithreaded PyQt programs where multiple
48
    threads are each printing information to the same terminal. The ``dprint()``
49
    function ensure that each line sent to the terminal will remain as a
50
    continious single line, whereas a regular ``print()`` statement will likely
51
    result in the lines getting mixed up.
52

53
    The line will be terminated with a newline character and the terminal output
54
    buffer is forced to flush before and after every print. In addition, if
55
    PyQt5 is present in the Python environment, then a mutex lock will be
56
    obtained and released again for each ``dprint()`` execution.
57
    """
58
    # Explicitly ending the string with a newline '\n' character, instead
59
    # of letting the print statement end it for you (end='\n'), fixes the
60
    # problem of single lines getting printed to the terminal with
61
    # intermittently delayed newlines when coming from different threads.
62
    # I.e. it prevents:
63
    # >: Output line of thread 1Output line of thread 2   (\n)
64
    # >:                                                  (\n)
65
    # and makes sure we get:
66
    # >: Output line of thread 1                          (\n)
67
    # >: Output line of thread 2                          (\n)
68

69
    if dprint_mutex is not None:
15✔
UNCOV
70
        locker = QtCore.QMutexLocker(dprint_mutex)
×
71

72
    sys.stdout.flush()
15✔
73
    if ANSI_color is None:
15✔
74
        print(f"{str_msg}\n", end="")
15✔
75
    else:
76
        print(f"{ANSI_color}{str_msg}{ANSI.WHITE}\n", end="")
15✔
77
    sys.stdout.flush()
15✔
78

79
    if dprint_mutex is not None:
15✔
UNCOV
80
        locker.unlock()
×
81

82

83
def tprint(str_msg: str, ANSI_color: Union[str, None] = None):
15✔
84
    """Identical to ``dprint()``, but now prepended with a ``time.perf_counter()``
85
    timestamp.
86
    """
87
    dprint(f"{time.perf_counter():.4f} {str_msg}", ANSI_color)
15✔
88

89

90
def print_fancy_traceback(
15✔
91
    err=None, back: int = 3, show_full_paths: bool = False
92
):
93
    """Print the exception or the current regular call-stack traceback to the
94
    terminal, using ANSI color codes that mimic the IPython command shell.
95

96
    Args:
97
        err (``Exception`` | ``str`` | ``None``, optional):
98
            When ``err`` is of type ``Exception``, then an exception traceback
99
            will be printed. When ``err`` is of another type, then the current
100
            regular call-stack traceback will be printed.
101

102
            Default: ``None``
103

104
        back (``int``, optional):
105
            Depth of the traceback to print.
106

107
            Default: ``3``
108

109
        show_full_paths (``bool``, optional):
110
            Shows the full filepath in the traceback when True, otherwise just
111
            the filename.
112

113
            Default: ``False``
114
    """
115

116
    def print_frame(filename, line_no, frame_name):
15✔
117
        print(
15✔
118
            ANSI.CYAN
119
            + "File "
120
            + ANSI.GREEN
121
            + f'"{filename}"'
122
            + ANSI.CYAN
123
            + ", line "
124
            + ANSI.GREEN
125
            + f"{line_no}"
126
            + ANSI.CYAN
127
            + ", in "
128
            + ANSI.PURPLE
129
            + f"{frame_name}"
130
            + ANSI.WHITE
131
        )
132

133
    print(
15✔
134
        "\n"
135
        + ANSI.WHITE
136
        + "Fancy traceback "
137
        + ANSI.CYAN
138
        + "(most recent call last)"
139
        + ANSI.WHITE
140
        + ":"
141
    )
142

143
    if isinstance(err, Exception):
15✔
144
        # Exception traceback
145
        etype, evalue, tb = sys.exc_info()
15✔
146
        stack = traceback.extract_tb(tb)
15✔
147
        stack = stack[-back:]
15✔
148

149
        for frame in stack:
15✔
150
            if OVERRULE_SHOW_FULL_PATHS is not None:
15✔
151
                file_descr = (
×
152
                    frame.filename
153
                    if OVERRULE_SHOW_FULL_PATHS
154
                    else os.path.basename(frame.filename)
155
                )
156
            else:
157
                file_descr = (
15✔
158
                    frame.filename
159
                    if show_full_paths
160
                    else os.path.basename(frame.filename)
161
                )
162

163
            print_frame(file_descr, frame.lineno, frame.name)
15✔
164

165
        print(f"----> {stack[-1].line}")
15✔
166
        if etype is None:
15✔
NEW
167
            print(": ", end="")
×
168
        else:
169
            print(ANSI.RED + f"{etype.__name__}: ", end="")
15✔
170
        print(ANSI.WHITE + f"{evalue}")
15✔
171

172
    else:
173
        # Regular call stack traceback
174
        stack = inspect.stack()
15✔
175
        stack.reverse()
15✔
176
        stack = stack[-back - 1 : -1]
15✔
177

178
        for frame in stack:
15✔
179
            if OVERRULE_SHOW_FULL_PATHS is not None:
15✔
180
                file_descr = (
×
181
                    frame.filename
182
                    if OVERRULE_SHOW_FULL_PATHS
183
                    else os.path.basename(frame.filename)
184
                )
185
            else:
186
                file_descr = (
15✔
187
                    frame.filename
188
                    if show_full_paths
189
                    else os.path.basename(frame.filename)
190
                )
191

192
            print_frame(file_descr, frame.lineno, frame.function)
15✔
193

194
        if isinstance(err, str):
15✔
195
            print(ANSI.RED + "Error: " + ANSI.WHITE + f"{err}")
15✔
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