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

domdfcoding / consolekit / 28810091048

06 Jul 2026 05:21PM UTC coverage: 94.269% (-1.1%) from 95.322%
28810091048

Pull #135

github

web-flow
Merge 053bda3aa into a444b4663
Pull Request #135: [pre-commit.ci] pre-commit autoupdate

806 of 855 relevant lines covered (94.27%)

0.94 hits per line

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

96.55
/consolekit/tracebacks.py
1
#!/usr/bin/env python3
2
#
3
#  tracebacks.py
4
"""
5
Functions for handling exceptions and their tracebacks.
6

7
.. versionadded:: 1.0.0
8
.. latex:vspace:: -10px
9
"""
10
#
11
#  Copyright © 2020-2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
12
#
13
#  Permission is hereby granted, free of charge, to any person obtaining a copy
14
#  of this software and associated documentation files (the "Software"), to deal
15
#  in the Software without restriction, including without limitation the rights
16
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
#  copies of the Software, and to permit persons to whom the Software is
18
#  furnished to do so, subject to the following conditions:
19
#
20
#  The above copyright notice and this permission notice shall be included in all
21
#  copies or substantial portions of the Software.
22
#
23
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
28
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
29
#  OR OTHER DEALINGS IN THE SOFTWARE.
30
#
31

32
# stdlib
33
import contextlib
1✔
34
import sys
1✔
35
from typing import TYPE_CHECKING, Callable, ContextManager, List, Type, TypeVar, Union
1✔
36

37
# 3rd party
38
import click
1✔
39
from domdf_python_tools.compat import nullcontext
1✔
40

41
if sys.version_info >= (3, 7) or TYPE_CHECKING:
1✔
42
        # stdlib
43
        from typing import NoReturn
1✔
44

45
__all__ = ("TracebackHandler", "handle_tracebacks", "traceback_handler", "traceback_option")
1✔
46

47
_C = TypeVar("_C", bound=click.Command)
1✔
48

49

50
class TracebackHandler:
1✔
51
        """
52
        Context manager to abort execution with a short error message
53
        on the following exception types:
54

55
        * :exc:`FileNotFoundError`
56
        * :exc:`FileExistsError`
57

58
        Other custom exception classes inheriting from :exc:`Exception` are also handled,
59
        but with a generic message.
60

61
        The following exception classes are ignored:
62

63
        * :exc:`EOFError`
64
        * :exc:`KeyboardInterrupt`
65
        * :exc:`click.ClickException`
66
        * :exc:`SystemExit` (new in version 1.1.2)
67

68
        How these exceptions are handled can be changed, and supported can be added for
69
        further exception classes by subclassing this class.
70
        Each method is named in the form :file:`handle_{<exception>}`, where ``exception``
71
        is the name of the exception class to handle.
72

73
        .. versionadded:: 1.0.0
74

75
        :param exception: The exception to raise after handling the traceback.
76
                If not running within a click command or group you'll likely
77
                want to set this to :exc:`SystemExit(1) <SystemExit>`.
78
        :default exception: :class:`click.Abort() <click.Abort>`
79

80
        .. versionchanged:: 1.4.0  Added the ``exception`` argument.
81

82
        .. seealso:: :func:`~.handle_tracebacks`.
83
        .. latex:clearpage::
84
        """  # noqa: D400
85

86
        exception: Exception
1✔
87
        """
88
        The exception to raise after handling the traceback.
89

90
        .. versionadded:: 1.4.0
91
        """
92

93
        def __init__(self, exception: BaseException = click.Abort()):
1✔
94
                self.exception: BaseException = exception  # type: ignore[assignment]
1✔
95

96
        def abort(self, msg: Union[str, List[str]]) -> "NoReturn":
1✔
97
                """
98
                Abort the current process by calling ``self.exception``.
99

100
                .. versionadded:: 1.4.0
101

102
                :param msg: The message to write to stderr before raising the exception.
103
                        If a list of strings the strings are concatenated (i.e. ``''.join(msg)``).
104
                """
105

106
                if not isinstance(msg, str):
1✔
107
                        msg = ''.join(msg)
1✔
108

109
                if msg:
1✔
110
                        click.echo(msg, err=True, color=False)
1✔
111

112
                raise self.exception
1✔
113

114
        def handle_EOFError(self, e: EOFError) -> "NoReturn":  # noqa: D102
1✔
115
                raise e
1✔
116

117
        def handle_KeyboardInterrupt(self, e: KeyboardInterrupt) -> "NoReturn":  # noqa: D102
1✔
118
                raise e
1✔
119

120
        def handle_ClickException(self, e: click.ClickException) -> "NoReturn":  # noqa: D102
1✔
121
                raise e
1✔
122

123
        def handle_Abort(self, e: click.Abort) -> "NoReturn":  # noqa: D102
1✔
124
                raise e
1✔
125

126
        def handle_SystemExit(self, e: SystemExit) -> "NoReturn":  # noqa: D102
1✔
127
                raise e
1✔
128

129
        def handle_FileNotFoundError(self, e: FileNotFoundError) -> "NoReturn":  # noqa: D102
1✔
130
                self.abort(f"File Not Found: {e}")
1✔
131

132
        def handle_FileExistsError(self, e: FileExistsError) -> "NoReturn":  # noqa: D102
1✔
133
                self.abort(f"File Exists: {e}")
1✔
134

135
        def handle(self, e: BaseException) -> bool:
1✔
136
                """
137
                Handle the given exception.
138

139
                :param e:
140
                """
141

142
                exception_name = e.__class__.__name__
1✔
143

144
                if hasattr(self, f"handle_{exception_name}"):
1✔
145
                        return getattr(self, f"handle_{exception_name}")(e)
1✔
146

147
                for base in e.__class__.__mro__:
1✔
148
                        if hasattr(self, f"handle_{base.__name__}"):
1✔
149
                                return getattr(self, f"handle_{base.__name__}")(e)
1✔
150

151
                self.abort(f"An error occurred: {e}")
1✔
152

153
        @contextlib.contextmanager
1✔
154
        def __call__(self):  # noqa: MAN002
×
155
                """
156
                Use the :class:`~.TracebackHandler` with a :keyword:`with` block, and handle any exceptions raised within.
157
                """
158

159
                try:
1✔
160
                        yield
1✔
161
                except BaseException as e:
1✔
162
                        self.handle(e)
1✔
163

164

165
@contextlib.contextmanager
1✔
166
def traceback_handler():  # noqa: MAN002
×
167
        """
168
        Context manager to abort execution with a short error message on the following exception types:
169

170
        * :exc:`FileNotFoundError`
171
        * :exc:`FileExistsError`
172

173
        Other custom exception classes inheriting from :exc:`Exception` are also handled,
174
        but with a generic message.
175

176
        The following exception classes are ignored:
177

178
        * :exc:`EOFError`
179
        * :exc:`KeyboardInterrupt`
180
        * :exc:`click.ClickException`
181

182
        .. versionadded:: 0.8.0
183

184
        .. seealso:: :func:`~.handle_tracebacks` and :class:`~.TracebackHandler`
185
        """  # noqa: D400
186

187
        with TracebackHandler()():
1✔
188
                yield
1✔
189

190

191
def handle_tracebacks(
1✔
192
                show_traceback: bool = False,
193
                cls: Type[TracebackHandler] = TracebackHandler,
194
                ) -> ContextManager:
195
        """
196
        Context manager to conditionally handle tracebacks, usually based on the value of a command line flag.
197

198
        .. versionadded:: 0.8.0
199

200
        :param show_traceback: If :py:obj:`True`, the full Python traceback will be shown on errors.
201
                If :py:obj:`False`, only the summary of the traceback will be shown.
202
                In either case the program execution will stop on error.
203
        :param cls: The class to use to handle the tracebacks.
204

205
        :rtype:
206

207
        .. versionchanged:: 1.0.0  Added the ``cls`` parameter.
208
        .. seealso:: :func:`~.traceback_handler` and :class:`~.TracebackHandler`
209
        """
210

211
        if show_traceback:
1✔
212
                return nullcontext()
1✔
213
        else:
214
                return cls()()
1✔
215

216

217
def traceback_option(help_text: str = "Show the complete traceback on error.") -> Callable[[_C], _C]:
1✔
218
        """
219
        Decorator to add the ``-T / --traceback`` option to a click command.
220

221
        The value is exposed via the parameter ``show_traceback``: :class:`bool`.
222

223
        .. versionadded:: 1.0.0
224

225
        :param help_text: The help text for the option.
226
        """
227

228
        # this package
229
        from consolekit.options import flag_option
1✔
230

231
        return flag_option("-T", "--traceback", "show_traceback", help=help_text)
1✔
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

© 2026 Coveralls, Inc