or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

commands-groups.mdcontext-management.mdexception-handling.mdformatting.mdindex.mdparameter-types.mdparameters.mdterminal-ui.mdtesting-support.md

exception-handling.mddocs/

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

```