click-extended 0.3.2__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.
- click_extended/__init__.py +10 -19
- click_extended/classes.py +25 -0
- click_extended/errors.py +336 -220
- click_extended/types.py +3 -23
- click_extended-1.0.0.dist-info/METADATA +331 -0
- click_extended-1.0.0.dist-info/RECORD +10 -0
- click_extended-0.3.2.dist-info/METADATA +0 -257
- click_extended-0.3.2.dist-info/RECORD +0 -9
- {click_extended-0.3.2.dist-info → click_extended-1.0.0.dist-info}/WHEEL +0 -0
- {click_extended-0.3.2.dist-info → click_extended-1.0.0.dist-info}/licenses/AUTHORS.md +0 -0
- {click_extended-0.3.2.dist-info → click_extended-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {click_extended-0.3.2.dist-info → click_extended-1.0.0.dist-info}/top_level.txt +0 -0
click_extended/errors.py
CHANGED
|
@@ -1,334 +1,450 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Error module for the `click_extended` library."""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import inspect
|
|
7
|
-
from typing import IO, Any
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
8
5
|
|
|
9
6
|
import click
|
|
10
|
-
from click import ClickException
|
|
11
|
-
from click._compat import get_text_stderr
|
|
12
7
|
from click.utils import echo
|
|
13
8
|
|
|
14
|
-
from click_extended.utils.
|
|
9
|
+
from click_extended.utils.humanize import humanize_iterable
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
class ClickExtendedError(Exception):
|
|
18
|
-
"""Base exception for
|
|
19
|
-
|
|
13
|
+
"""Base exception for all click-extended errors."""
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
def __init__(self, message: str, tip: str | None = None) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Initialize a ClickExtendedError.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
Args:
|
|
20
|
+
message (str):
|
|
21
|
+
The error message describing what went wrong.
|
|
22
|
+
tip (str):
|
|
23
|
+
Optional helpful guidance for resolving the error.
|
|
24
|
+
"""
|
|
25
|
+
self.message = message
|
|
26
|
+
self.tip = tip
|
|
27
|
+
super().__init__(message)
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def show(self, file: Any = None) -> None:
|
|
30
30
|
"""
|
|
31
|
-
|
|
31
|
+
Display the error message.
|
|
32
|
+
|
|
33
|
+
Subclasses should override this to provide custom formatting.
|
|
32
34
|
|
|
33
35
|
Args:
|
|
34
|
-
|
|
36
|
+
file (Any, optional):
|
|
37
|
+
The file to write to (defaults to sys.stderr).
|
|
35
38
|
"""
|
|
36
|
-
|
|
39
|
+
if file is None:
|
|
40
|
+
file = sys.stderr
|
|
41
|
+
|
|
42
|
+
echo(f"Error: {self.message}", file=file)
|
|
37
43
|
|
|
44
|
+
if self.tip:
|
|
45
|
+
echo(f"\nTip: {self.tip}", file=file)
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
|
|
48
|
+
class ContextAwareError(ClickExtendedError):
|
|
40
49
|
"""
|
|
41
|
-
Base
|
|
42
|
-
|
|
50
|
+
Base exception for errors that occur within Click context.
|
|
51
|
+
|
|
52
|
+
These errors have access to the full node hierarchy and are formatted
|
|
53
|
+
with Click-style usage information and node context.
|
|
54
|
+
|
|
55
|
+
It can only be raised during `phase 3` or `phase 4`.
|
|
43
56
|
"""
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
context: click.Context | None
|
|
59
|
+
|
|
60
|
+
def __init__(self, message: str, tip: str | None = None) -> None:
|
|
46
61
|
"""
|
|
47
|
-
Initialize a new `
|
|
62
|
+
Initialize a new `ContextAwareError` instance.
|
|
48
63
|
|
|
49
64
|
Args:
|
|
50
65
|
message (str):
|
|
51
|
-
The error message
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
The error message describing what went wrong.
|
|
67
|
+
tip (str):
|
|
68
|
+
Optional helpful guidance for resolving the error.
|
|
69
|
+
"""
|
|
70
|
+
super().__init__(message, tip)
|
|
71
|
+
try:
|
|
72
|
+
self.context = click.get_current_context()
|
|
73
|
+
self._node_name = self._resolve_node_name()
|
|
74
|
+
except RuntimeError:
|
|
75
|
+
self.context = None
|
|
76
|
+
self._node_name = "unknown"
|
|
77
|
+
|
|
78
|
+
def _resolve_node_name(self) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Get the most specific node name from context.
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
If inside a child node, that will be used, otherwise it checks if a
|
|
83
|
+
parent is defined, and if not that, the root node will be used.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str:
|
|
87
|
+
The name of the most specific node in the current scope.
|
|
58
88
|
"""
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if frame:
|
|
63
|
-
current_frame = frame.f_back
|
|
64
|
-
depth = 0
|
|
65
|
-
max_depth = 10
|
|
66
|
-
|
|
67
|
-
while current_frame and depth < max_depth:
|
|
68
|
-
caller_locals = current_frame.f_locals
|
|
69
|
-
if "self" in caller_locals:
|
|
70
|
-
self_obj = caller_locals["self"]
|
|
71
|
-
if hasattr(self_obj, "name") and hasattr(
|
|
72
|
-
self_obj, "process"
|
|
73
|
-
):
|
|
74
|
-
self.child_name = self_obj.name
|
|
75
|
-
break
|
|
76
|
-
current_frame = current_frame.f_back
|
|
77
|
-
depth += 1
|
|
78
|
-
|
|
79
|
-
if not self.child_name:
|
|
80
|
-
raise RuntimeError(
|
|
81
|
-
f"{self.__class__.__name__} must be raised from within a "
|
|
82
|
-
f"ChildNode.process() method. The exception was raised outside "
|
|
83
|
-
f"a valid ChildNode context."
|
|
84
|
-
)
|
|
89
|
+
if self.context is None:
|
|
90
|
+
return "unknown"
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
super().__init__(formatted_message)
|
|
92
|
+
meta = self.context.meta.get("click_extended", {})
|
|
88
93
|
|
|
94
|
+
if meta.get("child_node"):
|
|
95
|
+
return str(meta["child_node"].name)
|
|
96
|
+
if meta.get("parent_node"):
|
|
97
|
+
return str(meta["parent_node"].name)
|
|
98
|
+
if meta.get("root_node"):
|
|
99
|
+
return str(meta["root_node"].name)
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
"""Exception raised when a value in the `process()` method is unexpected."""
|
|
101
|
+
return "unknown"
|
|
92
102
|
|
|
93
|
-
def
|
|
103
|
+
def show(self, file: Any = None) -> None:
|
|
94
104
|
"""
|
|
95
|
-
|
|
105
|
+
Display the error with Click-style formatting.
|
|
106
|
+
|
|
107
|
+
Format:
|
|
108
|
+
Usage: cli [OPTIONS] COMMAND [ARGS]...
|
|
109
|
+
Try 'cli --help' for help.
|
|
110
|
+
|
|
111
|
+
Error (node_name): message
|
|
112
|
+
Tip: helpful guidance
|
|
96
113
|
|
|
97
114
|
Args:
|
|
98
|
-
|
|
99
|
-
The
|
|
115
|
+
file (Any, optional):
|
|
116
|
+
The file to write to (defaults to `sys.stderr`).
|
|
100
117
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
if file is None:
|
|
119
|
+
file = sys.stderr
|
|
103
120
|
|
|
121
|
+
if self.context is None:
|
|
122
|
+
super().show(file)
|
|
123
|
+
return
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
"""Exception raised when validation fails in a child node."""
|
|
125
|
+
echo(self.context.get_usage(), file=file, color=self.context.color)
|
|
107
126
|
|
|
127
|
+
if self.context.command.get_help_option(self.context) is not None:
|
|
128
|
+
hint = f"Try '{self.context.command_path} --help' for help."
|
|
129
|
+
echo(hint, file=file, color=self.context.color)
|
|
108
130
|
|
|
109
|
-
|
|
110
|
-
"""Exception raised when transformation fails in a child node."""
|
|
131
|
+
echo("", file=file)
|
|
111
132
|
|
|
133
|
+
exception_name = self.__class__.__name__
|
|
134
|
+
echo(
|
|
135
|
+
f"{exception_name} ({self._node_name}): {self.message}",
|
|
136
|
+
file=file,
|
|
137
|
+
color=self.context.color,
|
|
138
|
+
)
|
|
112
139
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Exception raised when an unexpected error occurs or parts of code
|
|
116
|
-
which is unhandled.
|
|
117
|
-
"""
|
|
140
|
+
if self.tip:
|
|
141
|
+
echo(f"Tip: {self.tip}", file=file, color=self.context.color)
|
|
118
142
|
|
|
119
143
|
|
|
120
|
-
class
|
|
121
|
-
"""
|
|
144
|
+
class MissingValueError(ContextAwareError):
|
|
145
|
+
"""
|
|
146
|
+
Exception raised when a value is missing.
|
|
122
147
|
|
|
123
|
-
This exception is
|
|
124
|
-
|
|
148
|
+
This exception is context-aware and can only be raised during `phase 3` or
|
|
149
|
+
`phase 4`.
|
|
125
150
|
"""
|
|
126
151
|
|
|
127
|
-
|
|
152
|
+
def __init__(self) -> None:
|
|
153
|
+
"""Initialize a new `MissingValueError` instance."""
|
|
154
|
+
super().__init__(
|
|
155
|
+
message="Value not provided.", tip=self._generate_tip()
|
|
156
|
+
)
|
|
128
157
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
) -> None:
|
|
135
|
-
"""
|
|
136
|
-
Initialize a ParameterError.
|
|
158
|
+
def _generate_tip(self) -> str:
|
|
159
|
+
"""Generate a context-aware tip based on the parent node type."""
|
|
160
|
+
try:
|
|
161
|
+
ctx = click.get_current_context()
|
|
162
|
+
meta = ctx.meta.get("click_extended", {})
|
|
137
163
|
|
|
138
|
-
|
|
139
|
-
message (str):
|
|
140
|
-
The error message from the validator/transformer.
|
|
141
|
-
param_hint (str, optional):
|
|
142
|
-
The parameter name (e.g., '--config', 'PATH').
|
|
143
|
-
ctx (click.Context, optional):
|
|
144
|
-
The Click context for displaying usage information.
|
|
145
|
-
"""
|
|
146
|
-
super().__init__(message)
|
|
147
|
-
self.param_hint = param_hint
|
|
148
|
-
self.ctx = ctx
|
|
164
|
+
parent = meta.get("parent_node")
|
|
149
165
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return self.message
|
|
166
|
+
if parent is not None:
|
|
167
|
+
return self._tip_for_parent(parent)
|
|
168
|
+
except (RuntimeError, AttributeError):
|
|
169
|
+
pass
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
file = get_text_stderr()
|
|
171
|
+
return (
|
|
172
|
+
"Provide a value or set the default parameter to make it optional."
|
|
173
|
+
)
|
|
160
174
|
|
|
161
|
-
|
|
175
|
+
# pylint: disable=too-many-return-statements
|
|
176
|
+
def _tip_for_parent(self, parent: Any) -> str:
|
|
177
|
+
"""Generate tip based on parent type."""
|
|
178
|
+
parent_type = parent.__class__.__name__
|
|
179
|
+
parent_name = parent.name
|
|
162
180
|
|
|
163
|
-
if
|
|
164
|
-
|
|
181
|
+
if parent_type == "Option":
|
|
182
|
+
return "".join(
|
|
183
|
+
f"Use --{parent_name.replace('_', '-')} to specify a value, "
|
|
184
|
+
"or set the default parameter to make it optional."
|
|
185
|
+
)
|
|
186
|
+
if parent_type == "Argument":
|
|
187
|
+
return "".join(
|
|
188
|
+
f"Provide the {parent_name} argument, or set the default "
|
|
189
|
+
"parameter to make it optional."
|
|
190
|
+
)
|
|
191
|
+
if parent_type == "Env":
|
|
192
|
+
env_var = getattr(parent, "env_name", parent_name.upper())
|
|
193
|
+
return "".join(
|
|
194
|
+
f"Set the {env_var} environment variable, or set the "
|
|
195
|
+
f"default parameter to make it optional."
|
|
196
|
+
)
|
|
197
|
+
return "".join(
|
|
198
|
+
"Provide a value or set the default parameter to "
|
|
199
|
+
"make it optional."
|
|
200
|
+
)
|
|
165
201
|
|
|
166
|
-
echo(self.ctx.get_usage(), file=file, color=color)
|
|
167
202
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
f"Try '{self.ctx.command_path} "
|
|
171
|
-
f"{self.ctx.help_option_names[0]}' for help."
|
|
172
|
-
)
|
|
173
|
-
echo(hint, file=file, color=color)
|
|
203
|
+
class NoRootError(ContextAwareError):
|
|
204
|
+
"""Exception raised when no root node has been defined."""
|
|
174
205
|
|
|
175
|
-
|
|
206
|
+
def __init__(self, tip: str | None = None) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Initialize a new `NoRootError` instance.
|
|
176
209
|
|
|
177
|
-
|
|
210
|
+
Args:
|
|
211
|
+
tip (str):
|
|
212
|
+
Optional helpful guidance (defaults to standard tip).
|
|
213
|
+
"""
|
|
214
|
+
super().__init__(
|
|
215
|
+
"No root node has been defined",
|
|
216
|
+
tip=tip or "Use @click_extended.root() decorator first",
|
|
217
|
+
)
|
|
178
218
|
|
|
179
219
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"""Exception raised when no `ParentNode` has been defined."""
|
|
220
|
+
class NoParentError(ContextAwareError):
|
|
221
|
+
"""Exception raised when a child node has no parent to attach to."""
|
|
183
222
|
|
|
184
|
-
def __init__(self,
|
|
223
|
+
def __init__(self, child_name: str, tip: str | None = None) -> None:
|
|
185
224
|
"""
|
|
186
225
|
Initialize a new `NoParentError` instance.
|
|
187
226
|
|
|
188
227
|
Args:
|
|
189
|
-
|
|
228
|
+
child_name (str):
|
|
190
229
|
The name of the child node.
|
|
230
|
+
tip (str):
|
|
231
|
+
Optional helpful guidance (defaults to standard tip).
|
|
191
232
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"
|
|
196
|
-
|
|
233
|
+
tip_msg = (
|
|
234
|
+
tip
|
|
235
|
+
or "Ensure a parent node (option/argument) is defined "
|
|
236
|
+
"before child nodes"
|
|
237
|
+
)
|
|
238
|
+
super().__init__(
|
|
239
|
+
f"Cannot register child node '{child_name}' "
|
|
240
|
+
f"as no parent is defined",
|
|
241
|
+
tip=tip_msg,
|
|
197
242
|
)
|
|
198
|
-
super().__init__(message)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
class NoRootError(ClickExtendedError):
|
|
202
|
-
"""Exception raised when there is no `RootNode` defined."""
|
|
203
|
-
|
|
204
|
-
def __init__(self, message: str | None = None) -> None:
|
|
205
|
-
"""Initialize a new `NoRootError` instance."""
|
|
206
|
-
super().__init__(message or "No root node is defined in the tree.")
|
|
207
243
|
|
|
208
244
|
|
|
209
|
-
class
|
|
210
|
-
"""Exception raised when
|
|
245
|
+
class RootExistsError(ContextAwareError):
|
|
246
|
+
"""Exception raised when attempting to define multiple root nodes."""
|
|
211
247
|
|
|
212
|
-
def __init__(self,
|
|
248
|
+
def __init__(self, tip: str | None = None) -> None:
|
|
213
249
|
"""
|
|
214
|
-
Initialize a new `
|
|
250
|
+
Initialize a new `RootExistsError` instance.
|
|
215
251
|
|
|
216
252
|
Args:
|
|
217
|
-
|
|
218
|
-
|
|
253
|
+
tip (str, optional):
|
|
254
|
+
Optional helpful guidance (defaults to standard tip).
|
|
219
255
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
f"Parent node names must be unique within the tree."
|
|
256
|
+
super().__init__(
|
|
257
|
+
"A root node has already been defined",
|
|
258
|
+
tip=tip or "Only one @root() decorator is allowed per command",
|
|
224
259
|
)
|
|
225
|
-
super().__init__(message)
|
|
226
260
|
|
|
227
261
|
|
|
228
|
-
class
|
|
229
|
-
"""Exception raised when
|
|
262
|
+
class ParentExistsError(ContextAwareError):
|
|
263
|
+
"""Exception raised when attempting to register duplicate parent names."""
|
|
230
264
|
|
|
231
|
-
def __init__(self) -> None:
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
265
|
+
def __init__(self, name: str, tip: str | None = None) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Initialize a new `ParentExistsError` instance.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
name (str):
|
|
271
|
+
The name of the duplicate parent node.
|
|
272
|
+
tip (str | None, optional):
|
|
273
|
+
Optional helpful guidance (defaults to standard tip).
|
|
274
|
+
"""
|
|
275
|
+
super().__init__(
|
|
276
|
+
f"Parent node '{name}' already exists",
|
|
277
|
+
tip=tip or "Parent node names must be unique within a command",
|
|
236
278
|
)
|
|
237
|
-
super().__init__(message)
|
|
238
279
|
|
|
239
280
|
|
|
240
|
-
class
|
|
241
|
-
"""
|
|
281
|
+
class TypeMismatchError(ContextAwareError):
|
|
282
|
+
"""
|
|
283
|
+
Exception raised when a child's process() signature is incompatible
|
|
284
|
+
with the parent's type.
|
|
285
|
+
"""
|
|
242
286
|
|
|
243
|
-
|
|
287
|
+
# pylint: disable=too-many-arguments
|
|
288
|
+
# pylint: disable=too-many-positional-arguments
|
|
289
|
+
def __init__(
|
|
290
|
+
self,
|
|
291
|
+
child_name: str,
|
|
292
|
+
parent_name: str,
|
|
293
|
+
parent_type: str,
|
|
294
|
+
supported_types: list[str],
|
|
295
|
+
tip: str | None = None,
|
|
296
|
+
) -> None:
|
|
244
297
|
"""
|
|
245
|
-
Initialize a new `
|
|
298
|
+
Initialize a new `TypeMismatchError` instance.
|
|
246
299
|
|
|
247
300
|
Args:
|
|
248
301
|
child_name (str):
|
|
249
302
|
The name of the child node.
|
|
250
|
-
|
|
251
|
-
The name of the
|
|
303
|
+
parent_name (str):
|
|
304
|
+
The name of the parent node.
|
|
305
|
+
parent_type (str):
|
|
306
|
+
The type of the parent (as string).
|
|
307
|
+
supported_types (list[str]):
|
|
308
|
+
List of supported type names.
|
|
309
|
+
tip (str | None, optional):
|
|
310
|
+
Optional helpful guidance (defaults to supported types).
|
|
252
311
|
"""
|
|
253
312
|
message = (
|
|
254
|
-
f"
|
|
255
|
-
f"'{
|
|
256
|
-
"(no return statement or return None)."
|
|
313
|
+
f"Child '{child_name}' does not support parent '{parent_name}' "
|
|
314
|
+
f"with type '{parent_type}'"
|
|
257
315
|
)
|
|
258
|
-
super().__init__(message)
|
|
259
316
|
|
|
317
|
+
if tip is None:
|
|
318
|
+
types_str = ", ".join(f"<{t}>" for t in supported_types)
|
|
319
|
+
tip = f"Supported types: {types_str}"
|
|
260
320
|
|
|
261
|
-
|
|
321
|
+
super().__init__(message, tip=tip)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class NameExistsError(ContextAwareError):
|
|
262
325
|
"""Exception raised when a name collision is detected."""
|
|
263
326
|
|
|
264
|
-
def __init__(
|
|
265
|
-
self, name: str, type1: str, type2: str, location1: str, location2: str
|
|
266
|
-
) -> None:
|
|
327
|
+
def __init__(self, name: str, tip: str | None = None) -> None:
|
|
267
328
|
"""
|
|
268
|
-
Initialize a new `
|
|
329
|
+
Initialize a new `NameExistsError` instance.
|
|
269
330
|
|
|
270
331
|
Args:
|
|
271
332
|
name (str):
|
|
272
333
|
The conflicting name.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
type2 (str):
|
|
276
|
-
The type of the second node.
|
|
277
|
-
location1 (str):
|
|
278
|
-
Description of where the first node is defined.
|
|
279
|
-
location2 (str):
|
|
280
|
-
Description of where the second node is defined.
|
|
334
|
+
tip (str | None, optional):
|
|
335
|
+
Optional helpful guidance (defaults to standard tip).
|
|
281
336
|
"""
|
|
282
|
-
|
|
283
|
-
f"The name '{name}' is used
|
|
284
|
-
|
|
285
|
-
f"All names (options, arguments, environment variables, and tags) "
|
|
286
|
-
f"must be unique within a command."
|
|
337
|
+
super().__init__(
|
|
338
|
+
f"The name '{name}' is already used",
|
|
339
|
+
tip=tip or "All names must be unique within a command",
|
|
287
340
|
)
|
|
288
|
-
super().__init__(message)
|
|
289
341
|
|
|
290
342
|
|
|
291
|
-
class
|
|
292
|
-
"""
|
|
343
|
+
class UnhandledTypeError(ContextAwareError):
|
|
344
|
+
"""
|
|
345
|
+
Exception raised when a child node doesn't implement a handler
|
|
346
|
+
for the value type.
|
|
347
|
+
"""
|
|
293
348
|
|
|
294
349
|
def __init__(
|
|
295
350
|
self,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
351
|
+
child_name: str,
|
|
352
|
+
value_type: str,
|
|
353
|
+
implemented_handlers: list[str],
|
|
354
|
+
tip: str | None = None,
|
|
300
355
|
) -> None:
|
|
301
356
|
"""
|
|
302
|
-
Initialize a new `
|
|
357
|
+
Initialize a new `UnhandledTypeError` instance.
|
|
303
358
|
|
|
304
359
|
Args:
|
|
305
|
-
|
|
306
|
-
The name of the
|
|
307
|
-
|
|
308
|
-
The
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
360
|
+
child_name (str):
|
|
361
|
+
The name of the child node.
|
|
362
|
+
value_type (str):
|
|
363
|
+
The type of value that couldn't be handled.
|
|
364
|
+
implemented_handlers (list[str]):
|
|
365
|
+
List of handler names that are implemented.
|
|
366
|
+
tip (str, optional):
|
|
367
|
+
Optional helpful guidance (defaults to list of handlers).
|
|
313
368
|
"""
|
|
369
|
+
message = "Child '{}' does not handle values of type '{}'."
|
|
370
|
+
message = message.format(child_name, value_type)
|
|
371
|
+
|
|
372
|
+
if tip is None:
|
|
373
|
+
if implemented_handlers:
|
|
374
|
+
tip = (
|
|
375
|
+
f"Missing handler for '{value_type}', only "
|
|
376
|
+
+ humanize_iterable(
|
|
377
|
+
implemented_handlers,
|
|
378
|
+
wrap="'",
|
|
379
|
+
suffix_singular=" is supported.",
|
|
380
|
+
suffix_plural=" are supported.",
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
tip = "".join(
|
|
385
|
+
"No handlers are implemented. Override handle_all() "
|
|
386
|
+
"or a specific handler method."
|
|
387
|
+
)
|
|
314
388
|
|
|
315
|
-
|
|
316
|
-
"""Get type name, handling both regular types and UnionType."""
|
|
317
|
-
return getattr(type_obj, "__name__", str(type_obj))
|
|
389
|
+
super().__init__(message, tip=tip)
|
|
318
390
|
|
|
319
|
-
parent_type_name = get_type_name(parent_type) if parent_type else "None"
|
|
320
391
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
prefix_singular="Supported type is ",
|
|
325
|
-
prefix_plural="Supported types are ",
|
|
326
|
-
wrap=("<", ">"),
|
|
327
|
-
)
|
|
392
|
+
class ProcessError(ContextAwareError):
|
|
393
|
+
"""
|
|
394
|
+
Exception raised when user code in `child.process()` raises an exception.
|
|
328
395
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
396
|
+
This error wraps standard Python exceptions (ValueError, TypeError, etc.)
|
|
397
|
+
raised by user code and adds node context for better error messages.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
def __init__(self, message: str, tip: str | None = None) -> None:
|
|
401
|
+
"""
|
|
402
|
+
Initialize a new `ProcessError` instance.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
message (str):
|
|
406
|
+
The error message from the wrapped exception.
|
|
407
|
+
tip (str | None, optional):
|
|
408
|
+
Optional helpful guidance for resolving the error.
|
|
409
|
+
"""
|
|
410
|
+
super().__init__(message, tip=tip)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class InvalidHandlerError(ContextAwareError):
|
|
414
|
+
"""Exception raised when a handler returns an invalid value."""
|
|
415
|
+
|
|
416
|
+
def __init__(self, message: str, tip: str | None = None) -> None:
|
|
417
|
+
"""
|
|
418
|
+
Initialize an new `InvalidHandlerError` instance.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
message (str):
|
|
422
|
+
Description of the invalid handler behavior.
|
|
423
|
+
tip (str | None, optional):
|
|
424
|
+
Optional helpful guidance for correcting the handler.
|
|
425
|
+
"""
|
|
426
|
+
super().__init__(message, tip=tip)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class InternalError(ContextAwareError):
|
|
430
|
+
"""
|
|
431
|
+
Exception raised for unexpected errors in framework code.
|
|
432
|
+
|
|
433
|
+
This indicates a bug in `click-extended` or an unreachable code path.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
def __init__(self, message: str, tip: str | None = None) -> None:
|
|
437
|
+
"""
|
|
438
|
+
Initialize a new `InternalError` instance.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
message (str):
|
|
442
|
+
Description of the internal error.
|
|
443
|
+
tip (str | None, optional):
|
|
444
|
+
Optional helpful guidance (defaults to bug report message).
|
|
445
|
+
"""
|
|
446
|
+
super().__init__(
|
|
447
|
+
message,
|
|
448
|
+
tip=tip
|
|
449
|
+
or "This is likely a bug in click-extended. Please report it.",
|
|
333
450
|
)
|
|
334
|
-
super().__init__(message)
|