or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-definition.mdcore-classes.mdexception-handling.mdindex.mdparameter-types.mdterminal-ui.mdutilities.md

exception-handling.mddocs/

0

# Exception Handling and Error Management

1

2

Comprehensive exception classes for handling various error conditions in command-line applications, with proper error reporting and user-friendly messages. Click's exception system provides structured error handling that maintains good user experience.

3

4

## Capabilities

5

6

### Base Exception Classes

7

8

Foundation exception classes that provide structured error handling for CLI applications.

9

10

```python { .api }

11

class ClickException(Exception):

12

"""

13

Base exception that Click can handle and show to user.

14

15

Attributes:

16

- exit_code: int, exit code when exception causes termination (default: 1)

17

- message: str, error message to display

18

"""

19

20

def __init__(self, message):

21

"""

22

Create a Click exception.

23

24

Parameters:

25

- message: Error message to display to user

26

"""

27

28

def format_message(self):

29

"""Format the error message for display."""

30

31

def show(self, file=None):

32

"""

33

Display the error message to user.

34

35

Parameters:

36

- file: File object to write to (defaults to stderr)

37

"""

38

39

class UsageError(ClickException):

40

"""

41

Exception that signals incorrect usage and aborts further handling.

42

43

Attributes:

44

- exit_code: int, exit code (default: 2)

45

- ctx: Context, context where error occurred

46

- cmd: Command, command that caused the error

47

"""

48

49

def __init__(self, message, ctx=None):

50

"""

51

Create a usage error.

52

53

Parameters:

54

- message: Error message

55

- ctx: Context where error occurred

56

"""

57

58

def show(self, file=None):

59

"""Show error with usage information."""

60

```

61

62

**Usage Examples:**

63

64

```python

65

@click.command()

66

@click.argument('filename')

67

def process_file(filename):

68

"""Process a file."""

69

import os

70

71

if not os.path.exists(filename):

72

raise click.ClickException(f'File "{filename}" does not exist.')

73

74

if not filename.endswith('.txt'):

75

raise click.UsageError('Only .txt files are supported.')

76

77

click.echo(f'Processing {filename}...')

78

79

# Custom exception handling

80

@click.command()

81

@click.pass_context

82

def custom_error(ctx):

83

"""Demonstrate custom error handling."""

84

try:

85

# Some operation that might fail

86

result = risky_operation()

87

except SomeError as e:

88

ctx.fail(f'Operation failed: {e}')

89

```

90

91

### Parameter-Related Exceptions

92

93

Exceptions for handling parameter validation and parsing errors.

94

95

```python { .api }

96

class BadParameter(UsageError):

97

"""

98

Exception for bad parameter values.

99

100

Attributes:

101

- param: Parameter, parameter object that caused error

102

- param_hint: str, alternative parameter name for error display

103

"""

104

105

def __init__(self, message, ctx=None, param=None, param_hint=None):

106

"""

107

Create a bad parameter error.

108

109

Parameters:

110

- message: Error message

111

- ctx: Current context

112

- param: Parameter that caused error

113

- param_hint: Alternative name for parameter in error

114

"""

115

116

class MissingParameter(BadParameter):

117

"""

118

Exception raised when a required parameter is missing.

119

120

Attributes:

121

- param_type: str, type of parameter ("option", "argument", "parameter")

122

"""

123

124

def __init__(self, message=None, ctx=None, param=None, param_hint=None, param_type=None):

125

"""

126

Create a missing parameter error.

127

128

Parameters:

129

- message: Custom error message

130

- ctx: Current context

131

- param: Missing parameter

132

- param_hint: Alternative parameter name

133

- param_type: Type of parameter for error message

134

"""

135

136

class BadArgumentUsage(UsageError):

137

"""Exception for incorrect argument usage."""

138

139

class BadOptionUsage(UsageError):

140

"""

141

Exception for incorrect option usage.

142

143

Attributes:

144

- option_name: str, name of the incorrectly used option

145

"""

146

147

def __init__(self, option_name, message, ctx=None):

148

"""

149

Create a bad option usage error.

150

151

Parameters:

152

- option_name: Name of the problematic option

153

- message: Error message

154

- ctx: Current context

155

"""

156

157

class NoSuchOption(UsageError):

158

"""

159

Exception when an option doesn't exist.

160

161

Attributes:

162

- option_name: str, name of the invalid option

163

- possibilities: list, suggested alternative options

164

"""

165

166

def __init__(self, option_name, message=None, possibilities=None, ctx=None):

167

"""

168

Create a no such option error.

169

170

Parameters:

171

- option_name: Name of invalid option

172

- message: Custom error message

173

- possibilities: List of suggested alternatives

174

- ctx: Current context

175

"""

176

```

177

178

**Usage Examples:**

179

180

```python

181

# Custom parameter validation

182

def validate_email(ctx, param, value):

183

"""Validate email parameter."""

184

import re

185

if not re.match(r'^[^@]+@[^@]+\.[^@]+$', value):

186

raise click.BadParameter('Invalid email format', ctx, param)

187

return value

188

189

@click.command()

190

@click.option('--email', callback=validate_email, required=True)

191

def send_email(email):

192

"""Send email with validation."""

193

click.echo(f'Sending email to {email}')

194

195

# Custom parameter type with exceptions

196

class PortType(click.ParamType):

197

name = 'port'

198

199

def convert(self, value, param, ctx):

200

try:

201

port = int(value)

202

except ValueError:

203

self.fail(f'{value} is not a valid integer', param, ctx)

204

205

if port < 1 or port > 65535:

206

self.fail(f'{port} is not in valid range 1-65535', param, ctx)

207

208

return port

209

210

@click.command()

211

@click.option('--port', type=PortType(), default=8080)

212

def start_server(port):

213

"""Start server with port validation."""

214

click.echo(f'Starting server on port {port}')

215

216

# Handling missing parameters

217

@click.command()

218

@click.option('--config', required=True,

219

help='Configuration file (required)')

220

def run_with_config(config):

221

"""Command requiring configuration."""

222

click.echo(f'Using config: {config}')

223

```

224

225

### File-Related Exceptions

226

227

Exceptions for file operations and I/O errors.

228

229

```python { .api }

230

class FileError(ClickException):

231

"""

232

Exception when a file cannot be opened.

233

234

Attributes:

235

- ui_filename: str, formatted filename for display

236

- filename: str, original filename

237

"""

238

239

def __init__(self, filename, hint=None):

240

"""

241

Create a file error.

242

243

Parameters:

244

- filename: Name of problematic file

245

- hint: Additional hint for the error

246

"""

247

248

def format_message(self):

249

"""Format error message with filename."""

250

```

251

252

**Usage Examples:**

253

254

```python

255

@click.command()

256

@click.argument('input_file', type=click.File('r'))

257

@click.argument('output_file', type=click.File('w'))

258

def copy_file(input_file, output_file):

259

"""Copy file with error handling."""

260

try:

261

content = input_file.read()

262

output_file.write(content)

263

click.echo('File copied successfully')

264

except IOError as e:

265

raise click.FileError(input_file.name, str(e))

266

267

# Manual file handling with exceptions

268

@click.command()

269

@click.argument('filename')

270

def read_file(filename):

271

"""Read file with custom error handling."""

272

try:

273

with open(filename, 'r') as f:

274

content = f.read()

275

click.echo(content)

276

except FileNotFoundError:

277

raise click.FileError(filename, 'File not found')

278

except PermissionError:

279

raise click.FileError(filename, 'Permission denied')

280

except IOError as e:

281

raise click.FileError(filename, str(e))

282

```

283

284

### Control Flow Exceptions

285

286

Special exceptions for controlling application flow and termination.

287

288

```python { .api }

289

class Abort(RuntimeError):

290

"""

291

Internal exception to signal that Click should abort.

292

Used internally by Click for graceful termination.

293

"""

294

295

class Exit(RuntimeError):

296

"""

297

Exception that indicates the application should exit with a status code.

298

299

Attributes:

300

- exit_code: int, status code to exit with

301

"""

302

303

def __init__(self, code=0):

304

"""

305

Create an exit exception.

306

307

Parameters:

308

- code: Exit status code (0 for success)

309

"""

310

```

311

312

**Usage Examples:**

313

314

```python

315

@click.command()

316

@click.option('--force', is_flag=True, help='Force operation')

317

def dangerous_operation(force):

318

"""Perform dangerous operation."""

319

if not force:

320

click.echo('This is a dangerous operation!')

321

if not click.confirm('Are you sure?'):

322

raise click.Abort()

323

324

click.echo('Performing dangerous operation...')

325

326

@click.command()

327

@click.argument('exit_code', type=int, default=0)

328

def exit_with_code(exit_code):

329

"""Exit with specified code."""

330

click.echo(f'Exiting with code {exit_code}')

331

raise click.Exit(exit_code)

332

333

# Context methods for error handling

334

@click.command()

335

@click.pass_context

336

def context_errors(ctx):

337

"""Demonstrate context error methods."""

338

339

# ctx.fail() raises UsageError

340

if some_condition:

341

ctx.fail('Something went wrong with usage')

342

343

# ctx.abort() raises Abort

344

if another_condition:

345

ctx.abort()

346

347

# ctx.exit() raises Exit

348

if success_condition:

349

ctx.exit(0)

350

```

351

352

### Exception Handling Best Practices

353

354

Comprehensive examples showing proper exception handling patterns in Click applications.

355

356

```python

357

# Comprehensive error handling example

358

@click.group()

359

@click.option('--verbose', is_flag=True, help='Enable verbose output')

360

@click.pass_context

361

def cli(ctx, verbose):

362

"""Main CLI with error handling."""

363

ctx.ensure_object(dict)

364

ctx.obj['verbose'] = verbose

365

366

@cli.command()

367

@click.argument('config_file', type=click.Path(exists=True, readable=True))

368

@click.option('--output', type=click.Path(writable=True), required=True)

369

@click.pass_context

370

def process_config(ctx, config_file, output):

371

"""Process configuration file with comprehensive error handling."""

372

import json

373

import yaml

374

375

try:

376

# Try to determine file format

377

if config_file.endswith('.json'):

378

with open(config_file, 'r') as f:

379

config = json.load(f)

380

elif config_file.endswith(('.yml', '.yaml')):

381

with open(config_file, 'r') as f:

382

config = yaml.safe_load(f)

383

else:

384

raise click.UsageError(

385

'Configuration file must be JSON (.json) or YAML (.yml/.yaml)'

386

)

387

388

except json.JSONDecodeError as e:

389

raise click.FileError(config_file, f'Invalid JSON: {e}')

390

except yaml.YAMLError as e:

391

raise click.FileError(config_file, f'Invalid YAML: {e}')

392

except PermissionError:

393

raise click.FileError(config_file, 'Permission denied')

394

395

# Validate configuration

396

required_keys = ['name', 'version']

397

for key in required_keys:

398

if key not in config:

399

raise click.BadParameter(

400

f'Missing required key: {key}',

401

ctx=ctx,

402

param_hint='config_file'

403

)

404

405

# Process and write output

406

try:

407

processed = process_configuration(config)

408

with open(output, 'w') as f:

409

json.dump(processed, f, indent=2)

410

411

if ctx.obj['verbose']:

412

click.echo(f'Successfully processed {config_file} -> {output}')

413

414

except IOError as e:

415

raise click.FileError(output, f'Cannot write output: {e}')

416

except Exception as e:

417

# Catch-all for unexpected errors

418

raise click.ClickException(f'Unexpected error: {e}')

419

420

# Error recovery example

421

@cli.command()

422

@click.option('--retry', default=3, help='Number of retries')

423

@click.pass_context

424

def unreliable_operation(ctx, retry):

425

"""Operation with retry logic."""

426

import random

427

import time

428

429

for attempt in range(retry + 1):

430

try:

431

if random.random() < 0.7: # 70% chance of failure

432

raise Exception('Random failure')

433

434

click.echo('Operation succeeded!')

435

return

436

437

except Exception as e:

438

if attempt < retry:

439

if ctx.obj['verbose']:

440

click.echo(f'Attempt {attempt + 1} failed: {e}')

441

click.echo(f'Retrying in 1 second...')

442

time.sleep(1)

443

else:

444

raise click.ClickException(

445

f'Operation failed after {retry + 1} attempts: {e}'

446

)

447

```