or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcommands.mdexceptions.mdindex.mdio.mdstyling.mdtesting.mdui.md

exceptions.mddocs/

0

# Exception Handling System

1

2

The exception handling system provides a comprehensive hierarchy of exception classes for different error conditions in CLI applications. These exceptions enable proper error handling, user feedback, and application exit codes.

3

4

## Capabilities

5

6

### Base Exception Class

7

8

The root exception class for all Cleo-specific errors with exit code support.

9

10

```python { .api }

11

class CleoError(Exception):

12

"""Base exception class for all Cleo errors."""

13

14

exit_code: int | None = None

15

16

def __init__(self, message: str = "", exit_code: int | None = None) -> None:

17

"""

18

Create a Cleo error.

19

20

Args:

21

message (str): Error message

22

exit_code (int | None): Suggested exit code for the application

23

"""

24

super().__init__(message)

25

if exit_code is not None:

26

self.exit_code = exit_code

27

```

28

29

### Configuration and Logic Errors

30

31

Errors related to incorrect configuration or programming logic issues.

32

33

```python { .api }

34

class CleoLogicError(CleoError):

35

"""

36

Raised when there is error in command arguments

37

and/or options configuration logic.

38

"""

39

pass

40

```

41

42

### Runtime Execution Errors

43

44

Errors that occur during command execution at runtime.

45

46

```python { .api }

47

class CleoRuntimeError(CleoError):

48

"""

49

Raised when command is called with invalid options or arguments.

50

"""

51

pass

52

```

53

54

### Value and Type Errors

55

56

Errors related to incorrect values or type mismatches.

57

58

```python { .api }

59

class CleoValueError(CleoError):

60

"""

61

Raised when wrong value was given to Cleo components.

62

"""

63

pass

64

```

65

66

### User Input Errors

67

68

Base class for errors caused by user input issues.

69

70

```python { .api }

71

class CleoUserError(CleoError):

72

"""

73

Base exception for user input errors.

74

75

These errors are typically caused by incorrect user input

76

and should result in helpful error messages.

77

"""

78

pass

79

```

80

81

### Specific User Input Error Types

82

83

Specialized exceptions for common user input problems.

84

85

```python { .api }

86

class CleoNoSuchOptionError(CleoError):

87

"""

88

Exception for undefined or invalid options.

89

90

Raised when command does not have given option.

91

"""

92

pass

93

94

class CleoMissingArgumentsError(CleoUserError):

95

"""

96

Exception for missing required arguments.

97

98

Raised when called command was not given required arguments.

99

"""

100

pass

101

102

class CleoCommandNotFoundError(CleoUserError):

103

"""

104

Exception for unknown commands.

105

106

Raised when called command does not exist.

107

"""

108

109

def __init__(self, name: str, commands: list[str] | None = None) -> None:

110

"""

111

Create command not found error with optional command suggestions.

112

113

Args:

114

name (str): The command name that was not found

115

commands (list[str] | None): Available commands for suggestions

116

"""

117

118

class CleoNamespaceNotFoundError(CleoUserError):

119

"""

120

Exception for unknown command namespaces.

121

122

Raised when called namespace has no commands.

123

"""

124

125

def __init__(self, name: str, namespaces: list[str] | None = None) -> None:

126

"""

127

Create namespace not found error with optional namespace suggestions.

128

129

Args:

130

name (str): The namespace name that was not found

131

namespaces (list[str] | None): Available namespaces for suggestions

132

"""

133

```

134

135

## Usage Examples

136

137

### Basic Exception Handling in Commands

138

139

```python

140

from cleo.commands.command import Command

141

from cleo.exceptions import CleoError, CleoUserError, CleoRuntimeError

142

143

class ProcessCommand(Command):

144

name = "process"

145

description = "Process data files"

146

147

def handle(self):

148

try:

149

filename = self.argument("file")

150

151

# Validate input

152

if not filename:

153

raise CleoUserError("Filename is required", exit_code=1)

154

155

if not os.path.exists(filename):

156

raise CleoUserError(f"File '{filename}' not found", exit_code=2)

157

158

# Process file

159

result = self.process_file(filename)

160

161

if not result:

162

raise CleoRuntimeError("Processing failed", exit_code=3)

163

164

self.line(f"<info>Processed {filename} successfully</info>")

165

return 0

166

167

except CleoUserError as e:

168

self.line(f"<error>Error: {e}</error>")

169

return e.exit_code or 1

170

171

except CleoRuntimeError as e:

172

self.line(f"<error>Runtime error: {e}</error>")

173

return e.exit_code or 1

174

175

except Exception as e:

176

self.line(f"<error>Unexpected error: {e}</error>")

177

return 1

178

179

def process_file(self, filename):

180

# File processing logic

181

try:

182

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

183

data = f.read()

184

185

if len(data) == 0:

186

raise CleoValueError("File is empty")

187

188

# Process data...

189

return True

190

191

except IOError as e:

192

raise CleoRuntimeError(f"Failed to read file: {e}")

193

```

194

195

### Application-Level Exception Handling

196

197

```python

198

from cleo.application import Application

199

from cleo.exceptions import CleoCommandNotFoundError, CleoNamespaceNotFoundError

200

201

class MyApplication(Application):

202

def run(self, input=None, output=None, error_output=None):

203

try:

204

return super().run(input, output, error_output)

205

206

except CleoCommandNotFoundError as e:

207

self.render_error(f"Command not found: {e}", output or error_output)

208

209

# Suggest similar commands

210

similar = self.find_similar_commands(str(e))

211

if similar:

212

self.render_error(f"Did you mean: {', '.join(similar)}", output or error_output)

213

214

return 1

215

216

except CleoNamespaceNotFoundError as e:

217

self.render_error(f"Namespace not found: {e}", output or error_output)

218

return 1

219

220

except KeyboardInterrupt:

221

self.render_error("\nOperation cancelled by user", output or error_output)

222

return 130 # Standard exit code for SIGINT

223

224

except Exception as e:

225

self.render_error(f"Unexpected error: {e}", output or error_output)

226

return 1

227

228

def render_error(self, message, output):

229

if output:

230

output.write_line(f"<error>{message}</error>")

231

```

232

233

### Custom Exception Classes

234

235

```python

236

from cleo.exceptions import CleoUserError, CleoRuntimeError

237

238

class DatabaseConnectionError(CleoRuntimeError):

239

"""Exception for database connection failures."""

240

241

def __init__(self, host, port, message="Database connection failed"):

242

super().__init__(f"{message}: {host}:{port}", exit_code=10)

243

244

class ConfigurationError(CleoUserError):

245

"""Exception for configuration file errors."""

246

247

def __init__(self, config_file, issue):

248

super().__init__(f"Configuration error in {config_file}: {issue}", exit_code=5)

249

250

class ValidationError(CleoValueError):

251

"""Exception for input validation failures."""

252

253

def __init__(self, field, value, expected):

254

super().__init__(

255

f"Invalid value '{value}' for {field}. Expected: {expected}",

256

exit_code=2

257

)

258

259

# Usage in commands

260

class DatabaseCommand(Command):

261

def handle(self):

262

try:

263

host = self.option("host")

264

port = int(self.option("port"))

265

266

# Validate port

267

if not 1 <= port <= 65535:

268

raise ValidationError("port", port, "1-65535")

269

270

# Connect to database

271

if not self.connect_database(host, port):

272

raise DatabaseConnectionError(host, port)

273

274

self.line("<info>Database connection successful</info>")

275

276

except ValidationError as e:

277

self.line(f"<error>{e}</error>")

278

return e.exit_code

279

280

except DatabaseConnectionError as e:

281

self.line(f"<error>{e}</error>")

282

self.line("<comment>Check your database server and network connectivity</comment>")

283

return e.exit_code

284

```

285

286

### Exception Handling with Recovery

287

288

```python

289

class BackupCommand(Command):

290

def handle(self):

291

backup_locations = ['/primary/backup', '/secondary/backup', '/tertiary/backup']

292

293

for i, location in enumerate(backup_locations):

294

try:

295

self.create_backup(location)

296

self.line(f"<info>Backup created at {location}</info>")

297

return 0

298

299

except CleoRuntimeError as e:

300

self.line(f"<comment>Backup failed at {location}: {e}</comment>")

301

302

if i == len(backup_locations) - 1:

303

# Last location, no more fallbacks

304

self.line("<error>All backup locations failed</error>")

305

return e.exit_code or 1

306

else:

307

# Try next location

308

self.line(f"<info>Trying next location...</info>")

309

continue

310

311

def create_backup(self, location):

312

if not os.path.exists(location):

313

raise CleoRuntimeError(f"Backup location does not exist: {location}")

314

315

if not os.access(location, os.W_OK):

316

raise CleoRuntimeError(f"No write access to backup location: {location}")

317

318

# Create backup...

319

```

320

321

### Exception Handling in Testing

322

323

```python

324

import pytest

325

from cleo.testers.command_tester import CommandTester

326

from cleo.exceptions import CleoUserError, CleoRuntimeError

327

328

def test_command_error_handling():

329

command = ProcessCommand()

330

tester = CommandTester(command)

331

332

# Test missing argument error

333

exit_code = tester.execute("")

334

assert exit_code == 1

335

assert "Filename is required" in tester.get_display()

336

337

# Test file not found error

338

exit_code = tester.execute("nonexistent.txt")

339

assert exit_code == 2

340

assert "File 'nonexistent.txt' not found" in tester.get_display()

341

342

def test_custom_exception_handling():

343

command = DatabaseCommand()

344

tester = CommandTester(command)

345

346

# Test invalid port

347

exit_code = tester.execute("--port 99999")

348

assert exit_code == 2

349

assert "Invalid value '99999' for port" in tester.get_display()

350

351

def test_exception_propagation():

352

"""Test that exceptions are properly caught and handled."""

353

command = FailingCommand()

354

tester = CommandTester(command)

355

356

# Should handle exception gracefully

357

exit_code = tester.execute("")

358

assert exit_code != 0 # Should indicate failure

359

assert "error" in tester.get_display().lower() # Should show error message

360

```

361

362

### Global Exception Handler

363

364

```python

365

import sys

366

import logging

367

from cleo.exceptions import CleoError

368

369

def setup_global_exception_handler():

370

"""Set up global exception handling for the application."""

371

372

def handle_exception(exc_type, exc_value, exc_traceback):

373

if isinstance(exc_value, CleoError):

374

# Cleo exceptions are handled by the application

375

return

376

377

# Log unexpected exceptions

378

logging.error(

379

"Uncaught exception",

380

exc_info=(exc_type, exc_value, exc_traceback)

381

)

382

383

# Show user-friendly message

384

print("An unexpected error occurred. Please check the logs for details.")

385

sys.exit(1)

386

387

sys.excepthook = handle_exception

388

389

# Use in main application

390

if __name__ == "__main__":

391

setup_global_exception_handler()

392

393

app = MyApplication()

394

try:

395

exit_code = app.run()

396

sys.exit(exit_code)

397

except KeyboardInterrupt:

398

print("\nOperation cancelled by user")

399

sys.exit(130)

400

```

401

402

### Error Reporting and Logging

403

404

```python

405

import logging

406

from cleo.exceptions import CleoError

407

408

class ReportingCommand(Command):

409

def __init__(self):

410

super().__init__()

411

self.logger = logging.getLogger(__name__)

412

413

def handle(self):

414

try:

415

# Command logic here

416

self.process_data()

417

418

except CleoUserError as e:

419

# User errors don't need full logging

420

self.line(f"<error>{e}</error>")

421

return e.exit_code or 1

422

423

except CleoRuntimeError as e:

424

# Runtime errors should be logged for debugging

425

self.logger.error(f"Runtime error in {self.name}: {e}")

426

self.line(f"<error>Operation failed: {e}</error>")

427

return e.exit_code or 1

428

429

except Exception as e:

430

# Unexpected errors need full logging

431

self.logger.exception(f"Unexpected error in {self.name}")

432

self.line("<error>An unexpected error occurred. Check logs for details.</error>")

433

return 1

434

435

def process_data(self):

436

# Processing logic that might raise exceptions

437

pass

438

```