or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-structures.mddev-server.mdexceptions.mdhttp-utilities.mdindex.mdmiddleware.mdrequest-response.mdrouting.mdsecurity.mdtesting.mdurl-wsgi-utils.md

exceptions.mddocs/

0

# Exception Handling

1

2

Complete HTTP exception hierarchy for proper error handling and response codes with WSGI integration. These exceptions provide a clean way to handle HTTP errors and generate appropriate responses in web applications.

3

4

## Capabilities

5

6

### Base HTTP Exception

7

8

The foundation class for all HTTP exceptions with WSGI application integration.

9

10

```python { .api }

11

class HTTPException(Exception):

12

def __init__(self, description=None, response=None):

13

"""

14

Base class for all HTTP exceptions.

15

16

Parameters:

17

- description: Custom error description

18

- response: Pre-built Response object to use

19

"""

20

21

# Class attributes

22

code: int # HTTP status code

23

description: str # Default error description

24

25

# Properties

26

name: str # Status code name (e.g., "Not Found")

27

28

def get_description(self, environ=None, scope=None):

29

"""

30

Get the error description for display.

31

32

Parameters:

33

- environ: WSGI environment (optional)

34

- scope: ASGI scope (optional)

35

36

Returns:

37

Error description string

38

"""

39

40

def get_body(self, environ=None, scope=None):

41

"""

42

Get the HTML error page body.

43

44

Parameters:

45

- environ: WSGI environment (optional)

46

- scope: ASGI scope (optional)

47

48

Returns:

49

HTML error page content

50

"""

51

52

def get_headers(self, environ=None, scope=None):

53

"""

54

Get response headers for the exception.

55

56

Parameters:

57

- environ: WSGI environment (optional)

58

- scope: ASGI scope (optional)

59

60

Returns:

61

List of (name, value) header tuples

62

"""

63

64

def get_response(self, environ=None, scope=None):

65

"""

66

Get a Response object for this exception.

67

68

Parameters:

69

- environ: WSGI environment (optional)

70

- scope: ASGI scope (optional)

71

72

Returns:

73

Response object with appropriate status and content

74

"""

75

76

def __call__(self, environ, start_response):

77

"""

78

WSGI application interface - allows exceptions to be called directly.

79

80

Parameters:

81

- environ: WSGI environment

82

- start_response: WSGI start_response callable

83

84

Returns:

85

WSGI response iterable

86

"""

87

```

88

89

### 4xx Client Error Exceptions

90

91

Exceptions for client-side errors (400-499 status codes).

92

93

```python { .api }

94

class BadRequest(HTTPException):

95

"""400 Bad Request - The request cannot be fulfilled due to bad syntax."""

96

code = 400

97

description = "The browser (or proxy) sent a request that this server could not understand."

98

99

class BadRequestKeyError(BadRequest, KeyError):

100

"""400 Bad Request caused by missing key in MultiDict-like object."""

101

102

def __init__(self, arg=None, *args, **kwargs):

103

"""

104

Parameters:

105

- arg: The missing key that caused the error

106

"""

107

108

class SecurityError(BadRequest):

109

"""400 Bad Request - Security violation detected."""

110

111

class Unauthorized(HTTPException):

112

"""401 Unauthorized - Authentication is required."""

113

code = 401

114

description = "The server could not verify that you are authorized to access the URL requested."

115

116

def __init__(self, description=None, response=None, www_authenticate=None):

117

"""

118

Parameters:

119

- description: Custom error description

120

- response: Pre-built response

121

- www_authenticate: WWW-Authenticate header value

122

"""

123

124

class Forbidden(HTTPException):

125

"""403 Forbidden - You don't have permission to access the resource."""

126

code = 403

127

description = "You don't have the permission to access the requested resource."

128

129

class NotFound(HTTPException):

130

"""404 Not Found - The requested resource could not be found."""

131

code = 404

132

description = "The requested URL was not found on the server."

133

134

class MethodNotAllowed(HTTPException):

135

"""405 Method Not Allowed - The method is not allowed for this resource."""

136

code = 405

137

description = "The method is not allowed for the requested URL."

138

139

def __init__(self, valid_methods=None, description=None):

140

"""

141

Parameters:

142

- valid_methods: List of allowed HTTP methods

143

- description: Custom error description

144

"""

145

146

class NotAcceptable(HTTPException):

147

"""406 Not Acceptable - Cannot satisfy Accept headers."""

148

code = 406

149

description = "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."

150

151

class RequestTimeout(HTTPException):

152

"""408 Request Timeout - The request took too long to process."""

153

code = 408

154

description = "The server closed the network connection because the browser didn't finish the request within the specified time."

155

156

class Conflict(HTTPException):

157

"""409 Conflict - Request conflicts with current state of resource."""

158

code = 409

159

description = "A conflict happened while processing the request."

160

161

class Gone(HTTPException):

162

"""410 Gone - The resource is no longer available."""

163

code = 410

164

description = "The requested URL is no longer available on this server and there is no forwarding address."

165

166

class LengthRequired(HTTPException):

167

"""411 Length Required - Content-Length header is required."""

168

code = 411

169

description = "A request with this method requires a valid Content-Length header."

170

171

class PreconditionFailed(HTTPException):

172

"""412 Precondition Failed - Precondition in headers failed."""

173

code = 412

174

description = "The precondition on the request for the URL failed positive evaluation."

175

176

class RequestEntityTooLarge(HTTPException):

177

"""413 Payload Too Large - Request entity is too large."""

178

code = 413

179

description = "The data value transmitted exceeds the capacity limit."

180

181

class RequestURITooLarge(HTTPException):

182

"""414 URI Too Long - The request URI is too long."""

183

code = 414

184

description = "The length of the requested URL exceeds the capacity limit for this server."

185

186

class UnsupportedMediaType(HTTPException):

187

"""415 Unsupported Media Type - Media type not supported."""

188

code = 415

189

description = "The server does not support the media type transmitted in the request."

190

191

class RequestedRangeNotSatisfiable(HTTPException):

192

"""416 Range Not Satisfiable - Cannot satisfy Range header."""

193

code = 416

194

description = "The server cannot provide the requested range."

195

196

def __init__(self, length=None, units="bytes", description=None):

197

"""

198

Parameters:

199

- length: Total content length

200

- units: Range units (default: "bytes")

201

- description: Custom error description

202

"""

203

204

class ExpectationFailed(HTTPException):

205

"""417 Expectation Failed - Cannot meet Expect header requirements."""

206

code = 417

207

description = "The server could not meet the requirements of the Expect header."

208

209

class ImATeapot(HTTPException):

210

"""418 I'm a teapot - RFC 2324 April Fools' joke."""

211

code = 418

212

description = "This server is a teapot, not a coffee machine."

213

214

class MisdirectedRequest(HTTPException):

215

"""421 Misdirected Request - Request was directed at wrong server."""

216

code = 421

217

description = "The request was directed at a server that is not able to produce a response."

218

219

class UnprocessableEntity(HTTPException):

220

"""422 Unprocessable Entity - Request is well-formed but semantically incorrect."""

221

code = 422

222

description = "The request was well-formed but was unable to be followed due to semantic errors."

223

224

class Locked(HTTPException):

225

"""423 Locked - Resource is locked."""

226

code = 423

227

description = "The resource that is being accessed is locked."

228

229

class FailedDependency(HTTPException):

230

"""424 Failed Dependency - Request failed due to failure of previous request."""

231

code = 424

232

description = "The request failed because it depended on another request and that request failed."

233

234

class PreconditionRequired(HTTPException):

235

"""428 Precondition Required - Precondition headers are required."""

236

code = 428

237

description = "This request is required to be conditional."

238

239

class TooManyRequests(HTTPException):

240

"""429 Too Many Requests - Rate limit exceeded."""

241

code = 429

242

description = "This user has exceeded an allotted request count."

243

244

def __init__(self, description=None, response=None, retry_after=None):

245

"""

246

Parameters:

247

- description: Custom error description

248

- response: Pre-built response

249

- retry_after: Seconds to wait before retrying

250

"""

251

252

class RequestHeaderFieldsTooLarge(HTTPException):

253

"""431 Request Header Fields Too Large - Headers are too large."""

254

code = 431

255

description = "One or more header fields exceeds the maximum size."

256

257

class UnavailableForLegalReasons(HTTPException):

258

"""451 Unavailable For Legal Reasons - Content blocked for legal reasons."""

259

code = 451

260

description = "Unavailable for legal reasons."

261

```

262

263

### 5xx Server Error Exceptions

264

265

Exceptions for server-side errors (500-599 status codes).

266

267

```python { .api }

268

class InternalServerError(HTTPException):

269

"""500 Internal Server Error - Generic server error."""

270

code = 500

271

description = "The server encountered an internal error and was unable to complete your request."

272

273

class NotImplemented(HTTPException):

274

"""501 Not Implemented - Functionality not implemented."""

275

code = 501

276

description = "The server does not support the action requested by the browser."

277

278

class BadGateway(HTTPException):

279

"""502 Bad Gateway - Invalid response from upstream server."""

280

code = 502

281

description = "The proxy server received an invalid response from an upstream server."

282

283

class ServiceUnavailable(HTTPException):

284

"""503 Service Unavailable - Service temporarily unavailable."""

285

code = 503

286

description = "The server is temporarily unable to service your request due to maintenance downtime or capacity problems."

287

288

def __init__(self, description=None, response=None, retry_after=None):

289

"""

290

Parameters:

291

- description: Custom error description

292

- response: Pre-built response

293

- retry_after: Seconds until service may be available again

294

"""

295

296

class GatewayTimeout(HTTPException):

297

"""504 Gateway Timeout - Timeout from upstream server."""

298

code = 504

299

description = "The connection to an upstream server timed out."

300

301

class HTTPVersionNotSupported(HTTPException):

302

"""505 HTTP Version Not Supported - HTTP version not supported."""

303

code = 505

304

description = "The server does not support the HTTP protocol version used in the request."

305

```

306

307

### Exception Utilities

308

309

Helper functions and classes for working with HTTP exceptions.

310

311

```python { .api }

312

def abort(status, *args, **kwargs):

313

"""

314

Raise an HTTPException for the given status code.

315

316

Parameters:

317

- status: HTTP status code (int) or Response object

318

- *args: Arguments passed to exception constructor

319

- **kwargs: Keyword arguments passed to exception constructor

320

321

Raises:

322

Appropriate HTTPException subclass for the status code

323

324

Examples:

325

- abort(404) → raises NotFound

326

- abort(500, description="Custom error") → raises InternalServerError with custom description

327

"""

328

329

class Aborter:

330

def __init__(self, mapping=None, extra=None):

331

"""

332

Exception raiser with customizable status code mapping.

333

334

Parameters:

335

- mapping: Dict mapping status codes to exception classes

336

- extra: Additional exception mappings

337

"""

338

339

def __call__(self, status, *args, **kwargs):

340

"""

341

Raise exception for status code.

342

343

Parameters:

344

- status: HTTP status code or Response object

345

- *args: Arguments for exception constructor

346

- **kwargs: Keyword arguments for exception constructor

347

"""

348

349

# Default aborter instance

350

default_exceptions = {

351

400: BadRequest,

352

401: Unauthorized,

353

403: Forbidden,

354

404: NotFound,

355

405: MethodNotAllowed,

356

406: NotAcceptable,

357

408: RequestTimeout,

358

409: Conflict,

359

410: Gone,

360

# ... complete mapping available

361

}

362

```

363

364

## Usage Examples

365

366

### Basic Exception Handling

367

368

```python

369

from werkzeug.exceptions import HTTPException, NotFound, BadRequest, InternalServerError

370

from werkzeug.wrappers import Request, Response

371

372

def view_function(request):

373

"""Example view that may raise exceptions."""

374

375

# Check for required parameter

376

if 'id' not in request.args:

377

raise BadRequest("Missing 'id' parameter")

378

379

user_id = request.args.get('id')

380

381

try:

382

user_id = int(user_id)

383

except ValueError:

384

raise BadRequest("Invalid user ID format")

385

386

# Simulate database lookup

387

if user_id == 0:

388

raise BadRequest("User ID cannot be zero")

389

elif user_id < 0:

390

raise NotFound("User not found")

391

elif user_id > 1000:

392

raise InternalServerError("Database connection failed")

393

394

return f"User {user_id} profile"

395

396

def application(environ, start_response):

397

"""WSGI application with exception handling."""

398

request = Request(environ)

399

400

try:

401

result = view_function(request)

402

response = Response(result)

403

404

except HTTPException as e:

405

# HTTP exceptions can be used directly as WSGI applications

406

return e(environ, start_response)

407

408

except Exception as e:

409

# Convert unexpected exceptions to 500 errors

410

error = InternalServerError("An unexpected error occurred")

411

return error(environ, start_response)

412

413

return response(environ, start_response)

414

```

415

416

### Custom Error Pages

417

418

```python

419

from werkzeug.exceptions import HTTPException, NotFound, InternalServerError

420

from werkzeug.wrappers import Response

421

422

def custom_error_handler(exception, environ=None):

423

"""Create custom error responses."""

424

425

if isinstance(exception, NotFound):

426

# Custom 404 page

427

html = f"""

428

<html>

429

<head><title>Page Not Found</title></head>

430

<body>

431

<h1>Oops! Page Not Found</h1>

432

<p>The page you're looking for doesn't exist.</p>

433

<p>Error code: {exception.code}</p>

434

<a href="/">Go back to homepage</a>

435

</body>

436

</html>

437

"""

438

return Response(html, status=404, mimetype='text/html')

439

440

elif isinstance(exception, InternalServerError):

441

# Custom 500 page (don't leak internal details in production)

442

html = """

443

<html>

444

<head><title>Server Error</title></head>

445

<body>

446

<h1>Something went wrong</h1>

447

<p>We're working to fix this issue. Please try again later.</p>

448

</body>

449

</html>

450

"""

451

return Response(html, status=500, mimetype='text/html')

452

453

else:

454

# Use default error page for other exceptions

455

return exception.get_response(environ)

456

457

def application_with_custom_errors(environ, start_response):

458

"""WSGI app with custom error handling."""

459

request = Request(environ)

460

461

try:

462

# Your application logic here

463

if request.path == '/':

464

response = Response('Hello World!')

465

elif request.path == '/error':

466

raise InternalServerError("Intentional error for testing")

467

else:

468

raise NotFound()

469

470

except HTTPException as e:

471

response = custom_error_handler(e, environ)

472

473

return response(environ, start_response)

474

```

475

476

### Using abort() Function

477

478

```python

479

from werkzeug.exceptions import abort

480

from werkzeug.wrappers import Request, Response

481

482

def protected_view(request):

483

"""Example of using abort() for concise error handling."""

484

485

# Check authentication

486

if 'Authorization' not in request.headers:

487

abort(401, description="Authentication required")

488

489

# Check permissions

490

if not user_has_permission(request):

491

abort(403, description="Insufficient permissions")

492

493

# Validate input

494

if not request.json:

495

abort(400, description="JSON data required")

496

497

data = request.json

498

499

if 'name' not in data:

500

abort(400, description="Missing 'name' field")

501

502

# Process valid request

503

return {"message": f"Hello {data['name']}!"}

504

505

def user_has_permission(request):

506

"""Mock permission check."""

507

# In real application, implement proper permission checking

508

return request.headers.get('Authorization') == 'Bearer valid-token'

509

510

def rest_api_app(environ, start_response):

511

"""REST API with abort-based error handling."""

512

request = Request(environ)

513

514

try:

515

if request.method == 'POST' and request.path == '/api/users':

516

result = protected_view(request)

517

response = Response(json.dumps(result), mimetype='application/json')

518

else:

519

abort(404, description="API endpoint not found")

520

521

except HTTPException as e:

522

# Return JSON error responses for API

523

error_data = {

524

'error': e.name,

525

'code': e.code,

526

'description': e.get_description()

527

}

528

response = Response(

529

json.dumps(error_data),

530

status=e.code,

531

mimetype='application/json'

532

)

533

534

return response(environ, start_response)

535

```

536

537

### Exception Middleware

538

539

```python

540

import traceback

541

import logging

542

from werkzeug.exceptions import HTTPException, InternalServerError

543

544

class ExceptionMiddleware:

545

"""Middleware for comprehensive exception handling."""

546

547

def __init__(self, app, debug=False, logger=None):

548

self.app = app

549

self.debug = debug

550

self.logger = logger or logging.getLogger(__name__)

551

552

def __call__(self, environ, start_response):

553

"""WSGI middleware interface."""

554

555

try:

556

return self.app(environ, start_response)

557

558

except HTTPException as e:

559

# Log HTTP exceptions for monitoring

560

self.logger.warning(f"HTTP {e.code}: {e.description}")

561

return e(environ, start_response)

562

563

except Exception as e:

564

# Log unexpected exceptions

565

self.logger.error(f"Unhandled exception: {e}")

566

self.logger.error(traceback.format_exc())

567

568

# Create appropriate error response

569

if self.debug:

570

# In debug mode, show full traceback

571

error_html = f"""

572

<html>

573

<head><title>Debug Error</title></head>

574

<body>

575

<h1>Unhandled Exception</h1>

576

<h2>{type(e).__name__}: {e}</h2>

577

<pre>{traceback.format_exc()}</pre>

578

</body>

579

</html>

580

"""

581

error = InternalServerError()

582

error.description = "Debug information available"

583

response = Response(error_html, status=500, mimetype='text/html')

584

else:

585

# In production, use generic error

586

error = InternalServerError("An unexpected error occurred")

587

response = error.get_response(environ)

588

589

return response(environ, start_response)

590

591

# Usage

592

def create_app(debug=False):

593

def app(environ, start_response):

594

request = Request(environ)

595

596

if request.path == '/crash':

597

raise ValueError("Intentional crash for testing")

598

599

response = Response(f'Hello from {request.path}')

600

return response(environ, start_response)

601

602

# Wrap with exception middleware

603

return ExceptionMiddleware(app, debug=debug)

604

```

605

606

### Custom Exception Classes

607

608

```python

609

from werkzeug.exceptions import HTTPException, BadRequest

610

611

class ValidationError(BadRequest):

612

"""Custom exception for validation errors."""

613

614

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

615

self.field = field

616

description = f"Validation failed"

617

if field:

618

description += f" for field '{field}'"

619

if message:

620

description += f": {message}"

621

super().__init__(description)

622

623

class QuotaExceeded(HTTPException):

624

"""Custom exception for quota/rate limiting."""

625

code = 429

626

description = "Request quota exceeded"

627

628

def __init__(self, quota_type=None, retry_after=None):

629

self.quota_type = quota_type

630

self.retry_after = retry_after

631

632

description = "Request quota exceeded"

633

if quota_type:

634

description += f" for {quota_type}"

635

636

super().__init__(description)

637

638

def get_headers(self, environ=None, scope=None):

639

"""Add Retry-After header."""

640

headers = super().get_headers(environ, scope)

641

642

if self.retry_after:

643

headers.append(('Retry-After', str(self.retry_after)))

644

645

return headers

646

647

class APIError(HTTPException):

648

"""Base class for API-specific errors."""

649

650

def get_response(self, environ=None, scope=None):

651

"""Return JSON error response."""

652

error_data = {

653

'error': {

654

'code': self.code,

655

'name': self.name,

656

'description': self.get_description(environ, scope)

657

}

658

}

659

660

return Response(

661

json.dumps(error_data, indent=2),

662

status=self.code,

663

mimetype='application/json',

664

headers=self.get_headers(environ, scope)

665

)

666

667

# Usage of custom exceptions

668

def validate_user_data(data):

669

"""Validate user data and raise custom exceptions."""

670

671

if 'email' not in data:

672

raise ValidationError('email', 'Email is required')

673

674

if '@' not in data['email']:

675

raise ValidationError('email', 'Invalid email format')

676

677

if len(data.get('password', '')) < 8:

678

raise ValidationError('password', 'Password must be at least 8 characters')

679

680

def api_endpoint(request):

681

"""API endpoint using custom exceptions."""

682

683

# Check rate limiting

684

if exceeded_rate_limit(request):

685

raise QuotaExceeded('requests', retry_after=60)

686

687

# Validate JSON data

688

if not request.is_json:

689

raise APIError(description="Content-Type must be application/json")

690

691

data = request.get_json()

692

validate_user_data(data)

693

694

# Process valid data

695

return {'status': 'success', 'user_id': 12345}

696

```

697

698

### Exception Testing

699

700

```python

701

from werkzeug.test import Client

702

from werkzeug.exceptions import NotFound, BadRequest

703

704

def test_exceptions():

705

"""Test exception handling in applications."""

706

707

def test_app(environ, start_response):

708

request = Request(environ)

709

710

if request.path == '/404':

711

raise NotFound("Test 404 error")

712

elif request.path == '/400':

713

raise BadRequest("Test 400 error")

714

else:

715

response = Response("OK")

716

return response(environ, start_response)

717

718

client = Client(test_app)

719

720

# Test normal response

721

response = client.get('/')

722

assert response.status_code == 200

723

assert response.text == "OK"

724

725

# Test 404 exception

726

response = client.get('/404')

727

assert response.status_code == 404

728

assert "Test 404 error" in response.text

729

730

# Test 400 exception

731

response = client.get('/400')

732

assert response.status_code == 400

733

assert "Test 400 error" in response.text

734

735

print("All exception tests passed!")

736

737

def test_custom_exceptions():

738

"""Test custom exception behavior."""

739

740

# Test custom exception properties

741

error = ValidationError('email', 'Invalid format')

742

assert error.code == 400

743

assert 'email' in error.description

744

assert 'Invalid format' in error.description

745

746

# Test quota exception with headers

747

quota_error = QuotaExceeded('API requests', retry_after=120)

748

headers = quota_error.get_headers()

749

header_dict = dict(headers)

750

assert header_dict.get('Retry-After') == '120'

751

752

print("Custom exception tests passed!")

753

754

if __name__ == '__main__':

755

test_exceptions()

756

test_custom_exceptions()

757

```

758

759

### Production Error Handling

760

761

```python

762

import os

763

import sys

764

import logging

765

from werkzeug.exceptions import HTTPException, InternalServerError

766

767

# Configure logging for production

768

logging.basicConfig(

769

level=logging.INFO,

770

format='%(asctime)s %(levelname)s %(name)s %(message)s',

771

handlers=[

772

logging.FileHandler('app.log'),

773

logging.StreamHandler(sys.stdout)

774

]

775

)

776

777

def production_error_handler(app):

778

"""Production-ready error handling wrapper."""

779

780

logger = logging.getLogger('error_handler')

781

is_debug = os.getenv('DEBUG', '').lower() in ('1', 'true', 'yes')

782

783

def error_app(environ, start_response):

784

try:

785

return app(environ, start_response)

786

787

except HTTPException as e:

788

# Log client errors (4xx) at info level

789

if 400 <= e.code < 500:

790

logger.info(f"Client error {e.code}: {e.description}")

791

# Log server errors (5xx) at error level

792

else:

793

logger.error(f"Server error {e.code}: {e.description}")

794

795

return e(environ, start_response)

796

797

except Exception as e:

798

# Log all unexpected exceptions as errors

799

logger.error(f"Unhandled exception: {e}", exc_info=True)

800

801

# Create safe error response

802

if is_debug:

803

# Show details in debug mode

804

error = InternalServerError(f"Debug: {type(e).__name__}: {e}")

805

else:

806

# Generic message in production

807

error = InternalServerError("Internal server error")

808

809

return error(environ, start_response)

810

811

return error_app

812

813

# Example production application

814

def create_production_app():

815

"""Create production-ready application with proper error handling."""

816

817

def base_app(environ, start_response):

818

request = Request(environ)

819

820

# Your application routes here

821

if request.path == '/':

822

response = Response('Production app running')

823

elif request.path == '/health':

824

response = Response('OK', mimetype='text/plain')

825

else:

826

raise NotFound("Endpoint not found")

827

828

return response(environ, start_response)

829

830

# Wrap with production error handler

831

return production_error_handler(base_app)

832

833

if __name__ == '__main__':

834

from werkzeug.serving import run_simple

835

836

app = create_production_app()

837

run_simple('localhost', 8000, app)

838

```