0
# Exception Handling
1
2
Comprehensive exception hierarchy for handling CLI errors with proper error messages, exit codes, and user feedback. Includes parameter validation errors and usage errors.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
Foundation exception classes that provide common error handling functionality.
9
10
```python { .api }
11
class ClickException(Exception):
12
exit_code: int
13
message: str
14
15
def __init__(self, message: str) -> None:
16
"""
17
Base exception for all click-related errors.
18
19
Parameters:
20
- message: Error message
21
22
Attributes:
23
- exit_code: Exit code for the application (default: 1)
24
- message: Error message text
25
26
Usage:
27
raise click.ClickException('Something went wrong')
28
"""
29
30
def format_message(self) -> str:
31
"""
32
Format the exception message.
33
34
Returns:
35
Formatted error message
36
"""
37
38
def show(self, file: Any | None = None) -> None:
39
"""
40
Show the exception message to the user.
41
42
Parameters:
43
- file: File to write to (defaults to stderr)
44
"""
45
```
46
47
### Usage Error Exceptions
48
49
Exceptions for command usage and parameter errors.
50
51
```python { .api }
52
class UsageError(ClickException):
53
ctx: Context | None
54
cmd: Command | None
55
56
def __init__(self, message: str, ctx: Context | None = None) -> None:
57
"""
58
Exception for usage errors (wrong parameters, invalid commands, etc.).
59
60
Parameters:
61
- message: Error message
62
- ctx: Context where error occurred
63
64
Usage:
65
if len(args) == 0:
66
raise click.UsageError('At least one argument is required')
67
"""
68
69
def show(self, file: IO[Any] | None = None) -> None:
70
"""Show usage error with command usage information."""
71
```
72
73
### Parameter Exception Classes
74
75
Specific exceptions for parameter validation and processing errors.
76
77
```python { .api }
78
class BadParameter(UsageError):
79
param: Parameter | None
80
param_hint: str | None
81
82
def __init__(
83
self,
84
message: str,
85
ctx: Context | None = None,
86
param: Parameter | None = None,
87
param_hint: str | None = None,
88
) -> None:
89
"""
90
Exception for invalid parameter values.
91
92
Parameters:
93
- message: Error message
94
- ctx: Context where error occurred
95
- param: Parameter that caused the error
96
- param_hint: Hint about which parameter failed
97
98
Usage:
99
# Usually raised by parameter types
100
if not os.path.exists(path):
101
raise click.BadParameter(f'Path {path} does not exist')
102
"""
103
104
class MissingParameter(BadParameter):
105
param_type: str
106
107
def __init__(
108
self,
109
message: str | None = None,
110
ctx: Context | None = None,
111
param: Parameter | None = None,
112
param_hint: str | None = None,
113
param_type: str | None = None,
114
) -> None:
115
"""
116
Exception for missing required parameters.
117
118
Parameters:
119
- message: Error message
120
- ctx: Context where error occurred
121
- param: Missing parameter
122
- param_hint: Hint about which parameter is missing
123
- param_type: Type of parameter ('parameter', 'option', 'argument')
124
125
Usage:
126
# Usually raised automatically by click
127
if required_param is None:
128
raise click.MissingParameter('Required parameter missing')
129
"""
130
```
131
132
### Option and Argument Exceptions
133
134
Specific exceptions for option and argument processing errors.
135
136
```python { .api }
137
class NoSuchOption(UsageError):
138
option_name: str
139
possibilities: list[str] | None
140
141
def __init__(
142
self,
143
option_name: str,
144
message: str | None = None,
145
possibilities: list[str] | None = None,
146
ctx: Context | None = None,
147
) -> None:
148
"""
149
Exception for unknown options.
150
151
Parameters:
152
- option_name: Name of unknown option
153
- message: Error message
154
- possibilities: List of similar option names
155
- ctx: Context where error occurred
156
157
Usage:
158
# Usually raised automatically by click
159
if option not in valid_options:
160
raise click.NoSuchOption(option, possibilities=['--help', '--version'])
161
"""
162
163
class BadOptionUsage(UsageError):
164
option_name: str
165
166
def __init__(self, option_name: str, message: str, ctx: Context | None = None) -> None:
167
"""
168
Exception for incorrect option usage.
169
170
Parameters:
171
- option_name: Name of the option
172
- message: Error message
173
- ctx: Context where error occurred
174
175
Usage:
176
# When option is used incorrectly
177
raise click.BadOptionUsage('--count', 'Option requires a value')
178
"""
179
180
class BadArgumentUsage(UsageError):
181
def __init__(self, message: str, ctx: Context | None = None) -> None:
182
"""
183
Exception for incorrect argument usage.
184
185
Parameters:
186
- message: Error message
187
- ctx: Context where error occurred
188
189
Usage:
190
# When arguments are used incorrectly
191
raise click.BadArgumentUsage('Too many arguments provided')
192
"""
193
```
194
195
### File Operation Exceptions
196
197
Exceptions for file-related operations.
198
199
```python { .api }
200
class FileError(ClickException):
201
ui_filename: str
202
filename: str
203
204
def __init__(self, filename: str, hint: str | None = None) -> None:
205
"""
206
Exception for file operation errors.
207
208
Parameters:
209
- filename: Name of the file that caused the error
210
- hint: Additional hint about the error
211
212
Usage:
213
try:
214
with open(filename, 'r') as f:
215
content = f.read()
216
except IOError:
217
raise click.FileError(filename, 'Could not read file')
218
"""
219
```
220
221
### Control Flow Exceptions
222
223
Exceptions used for controlling command execution flow.
224
225
```python { .api }
226
class Abort(RuntimeError):
227
"""
228
Exception for aborting command execution.
229
230
Raised by Context.abort() and confirmation dialogs when user
231
chooses to abort.
232
233
Usage:
234
if not click.confirm('Continue with dangerous operation?'):
235
raise click.Abort()
236
237
# Or use context method
238
ctx.abort()
239
"""
240
241
class Exit(RuntimeError):
242
exit_code: int
243
244
def __init__(self, code: int = 0) -> None:
245
"""
246
Exception for exiting with specific code.
247
248
Parameters:
249
- code: Exit code
250
251
Usage:
252
if success:
253
raise click.Exit(0)
254
else:
255
raise click.Exit(1)
256
257
# Or use context method
258
ctx.exit(code)
259
"""
260
```
261
262
### Exception Handling Patterns
263
264
**Custom Validation with Exceptions:**
265
266
```python
267
@click.command()
268
@click.option('--port', type=int)
269
def start_server(port):
270
if port and (port < 1 or port > 65535):
271
raise click.BadParameter('Port must be between 1 and 65535',
272
param_hint='--port')
273
274
if port and port < 1024:
275
if not click.confirm(f'Port {port} requires root privileges. Continue?'):
276
raise click.Abort()
277
```
278
279
**Custom Parameter Type with Exceptions:**
280
281
```python
282
class PortType(click.ParamType):
283
name = 'port'
284
285
def convert(self, value, param, ctx):
286
if value is None:
287
return None
288
289
try:
290
port = int(value)
291
except ValueError:
292
self.fail(f'{value} is not a valid port number', param, ctx)
293
294
if not (1 <= port <= 65535):
295
self.fail(f'Port {port} is not in valid range 1-65535', param, ctx)
296
297
return port
298
299
@click.option('--port', type=PortType())
300
def connect(port):
301
click.echo(f'Connecting to port {port}')
302
```
303
304
**Error Handling with Context:**
305
306
```python
307
@click.command()
308
@click.argument('filename')
309
@click.pass_context
310
def process_file(ctx, filename):
311
try:
312
if not os.path.exists(filename):
313
ctx.fail(f'File {filename} does not exist')
314
315
with open(filename, 'r') as f:
316
data = f.read()
317
318
# Process data
319
result = process_data(data)
320
321
except PermissionError:
322
ctx.fail(f'Permission denied accessing {filename}')
323
except json.JSONDecodeError as e:
324
ctx.fail(f'Invalid JSON in {filename}: {e}')
325
except Exception as e:
326
if ctx.obj and ctx.obj.get('debug'):
327
raise # Re-raise in debug mode
328
else:
329
ctx.fail(f'Error processing {filename}: {e}')
330
```
331
332
**Graceful Error Messages:**
333
334
```python
335
@click.command()
336
@click.option('--config', type=click.Path(exists=True), required=True)
337
def deploy(config):
338
try:
339
with open(config, 'r') as f:
340
config_data = json.load(f)
341
except json.JSONDecodeError as e:
342
# Convert generic exception to click exception
343
raise click.BadParameter(
344
f'Configuration file contains invalid JSON: {e}',
345
param_hint='--config'
346
)
347
348
required_keys = ['host', 'port', 'database']
349
missing_keys = [key for key in required_keys if key not in config_data]
350
351
if missing_keys:
352
raise click.BadParameter(
353
f'Configuration missing required keys: {", ".join(missing_keys)}',
354
param_hint='--config'
355
)
356
```
357
358
**Custom Exception Classes:**
359
360
```python
361
class DeploymentError(click.ClickException):
362
def __init__(self, message, deployment_id=None):
363
super().__init__(message)
364
self.deployment_id = deployment_id
365
self.exit_code = 2 # Custom exit code
366
367
def format_message(self):
368
msg = super().format_message()
369
if self.deployment_id:
370
msg += f' (Deployment ID: {self.deployment_id})'
371
return msg
372
373
@click.command()
374
def deploy():
375
try:
376
deployment_id = start_deployment()
377
monitor_deployment(deployment_id)
378
except DeploymentFailure as e:
379
raise DeploymentError(str(e), deployment_id) from e
380
```