or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-resources.mddocumentation.mderror-handling.mdfields.mdindex.mdinput-validation.mdmodels-marshalling.mdrequest-parsing.md

error-handling.mddocs/

0

# Error Handling

1

2

Structured error handling with HTTP status codes, custom exception classes, and automatic error response formatting. Flask-RESTPlus provides comprehensive error handling capabilities that integrate with automatic documentation generation.

3

4

## Capabilities

5

6

### Abort Function

7

8

Function for terminating requests with HTTP errors and structured error responses.

9

10

```python { .api }

11

def abort(code=500, message=None, **kwargs):

12

"""

13

Abort the current request with an HTTP error.

14

15

Args:

16

code (int): HTTP status code (default: 500)

17

message (str, optional): Error message

18

**kwargs: Additional error data to include in response

19

20

Raises:

21

HTTPException: Flask HTTP exception with error details

22

"""

23

```

24

25

### Exception Classes

26

27

Custom exception classes for different types of API errors.

28

29

```python { .api }

30

class RestError(Exception):

31

def __init__(self, msg):

32

"""

33

Base class for all Flask-RESTPlus errors.

34

35

Args:

36

msg (str): Error message

37

"""

38

self.msg = msg

39

40

def __str__(self):

41

"""

42

String representation of the error.

43

44

Returns:

45

str: Error message

46

"""

47

48

class ValidationError(RestError):

49

def __init__(self, msg):

50

"""

51

Error for input validation failures.

52

53

Args:

54

msg (str): Validation error message

55

"""

56

57

class SpecsError(RestError):

58

def __init__(self, msg):

59

"""

60

Error for API specification issues.

61

62

Args:

63

msg (str): Specification error message

64

"""

65

```

66

67

### HTTP Status Constants

68

69

HTTP status code constants and utilities for consistent error responses.

70

71

```python { .api }

72

# Available through flask_restplus._http.HTTPStatus

73

class HTTPStatus:

74

# Informational responses

75

CONTINUE = 100

76

SWITCHING_PROTOCOLS = 101

77

PROCESSING = 102

78

79

# Successful responses

80

OK = 200

81

CREATED = 201

82

ACCEPTED = 202

83

NO_CONTENT = 204

84

85

# Redirection messages

86

MOVED_PERMANENTLY = 301

87

FOUND = 302

88

NOT_MODIFIED = 304

89

90

# Client error responses

91

BAD_REQUEST = 400

92

UNAUTHORIZED = 401

93

FORBIDDEN = 403

94

NOT_FOUND = 404

95

METHOD_NOT_ALLOWED = 405

96

NOT_ACCEPTABLE = 406

97

CONFLICT = 409

98

GONE = 410

99

UNPROCESSABLE_ENTITY = 422

100

101

# Server error responses

102

INTERNAL_SERVER_ERROR = 500

103

NOT_IMPLEMENTED = 501

104

BAD_GATEWAY = 502

105

SERVICE_UNAVAILABLE = 503

106

```

107

108

## Usage Examples

109

110

### Basic Error Handling

111

112

```python

113

from flask_restplus import Api, Resource, abort

114

115

api = Api()

116

117

@api.route('/users/<int:user_id>')

118

class User(Resource):

119

def get(self, user_id):

120

# Simulate user lookup

121

user = find_user_by_id(user_id)

122

123

if not user:

124

# Abort with 404 Not Found

125

abort(404, message=f"User {user_id} not found")

126

127

if not user.is_active:

128

# Abort with custom message and additional data

129

abort(403,

130

message="Access denied",

131

reason="User account is inactive",

132

user_id=user_id)

133

134

return {'id': user.id, 'name': user.name}

135

136

def delete(self, user_id):

137

user = find_user_by_id(user_id)

138

139

if not user:

140

abort(404, message="User not found")

141

142

if user.is_admin:

143

abort(400,

144

message="Cannot delete admin user",

145

user_type="admin")

146

147

# Delete user...

148

return {'message': f'User {user_id} deleted'}, 200

149

```

150

151

### Custom Error Responses

152

153

```python

154

from flask_restplus import Api, Resource, abort, fields

155

156

api = Api()

157

158

# Define error response model for documentation

159

error_model = api.model('Error', {

160

'message': fields.String(required=True, description='Error message'),

161

'code': fields.Integer(description='Error code'),

162

'details': fields.Raw(description='Additional error details')

163

})

164

165

@api.route('/orders/<int:order_id>')

166

class Order(Resource):

167

@api.response(404, 'Order not found', error_model)

168

@api.response(400, 'Invalid request', error_model)

169

def get(self, order_id):

170

order = find_order(order_id)

171

172

if not order:

173

abort(404,

174

message="Order not found",

175

code="ORDER_NOT_FOUND",

176

order_id=order_id)

177

178

if order.status == 'cancelled':

179

abort(400,

180

message="Cannot access cancelled order",

181

code="ORDER_CANCELLED",

182

details={

183

'order_id': order_id,

184

'cancelled_at': order.cancelled_at.isoformat(),

185

'reason': order.cancellation_reason

186

})

187

188

return order.to_dict()

189

```

190

191

### Global Error Handlers

192

193

```python

194

from flask_restplus import Api

195

from werkzeug.exceptions import HTTPException

196

import logging

197

198

api = Api()

199

200

@api.errorhandler(ValidationError)

201

def handle_validation_error(error):

202

"""Handle validation errors."""

203

return {

204

'message': 'Validation failed',

205

'error': str(error),

206

'type': 'validation_error'

207

}, 400

208

209

@api.errorhandler(ValueError)

210

def handle_value_error(error):

211

"""Handle value errors."""

212

return {

213

'message': 'Invalid value provided',

214

'error': str(error),

215

'type': 'value_error'

216

}, 400

217

218

@api.errorhandler(KeyError)

219

def handle_key_error(error):

220

"""Handle missing key errors."""

221

return {

222

'message': 'Required field missing',

223

'field': str(error).strip("'\""),

224

'type': 'missing_field'

225

}, 400

226

227

@api.errorhandler(Exception)

228

def handle_unexpected_error(error):

229

"""Handle unexpected errors."""

230

logging.exception("Unexpected error occurred")

231

return {

232

'message': 'An unexpected error occurred',

233

'type': 'internal_error'

234

}, 500

235

236

# Handle specific HTTP exceptions

237

@api.errorhandler(404)

238

def handle_not_found(error):

239

"""Handle 404 errors."""

240

return {

241

'message': 'Resource not found',

242

'type': 'not_found'

243

}, 404

244

```

245

246

### Namespace-Specific Error Handlers

247

248

```python

249

from flask_restplus import Api, Namespace, Resource

250

251

api = Api()

252

ns = api.namespace('users', description='User operations')

253

254

@ns.errorhandler(ValidationError)

255

def handle_user_validation_error(error):

256

"""Handle validation errors in user namespace."""

257

return {

258

'message': 'User validation failed',

259

'error': str(error),

260

'namespace': 'users'

261

}, 400

262

263

@ns.errorhandler(ValueError)

264

def handle_user_value_error(error):

265

"""Handle value errors in user namespace."""

266

return {

267

'message': 'Invalid user data',

268

'error': str(error),

269

'namespace': 'users'

270

}, 400

271

272

@ns.route('/')

273

class UserList(Resource):

274

def post(self):

275

# Validation errors in this namespace will be handled by

276

# the namespace-specific error handlers above

277

user_data = api.payload

278

279

if not user_data.get('email'):

280

raise ValidationError("Email is required")

281

282

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

283

raise ValueError("Invalid email format")

284

285

return {'message': 'User created'}, 201

286

```

287

288

### Validation Error Handling

289

290

```python

291

from flask_restplus import Api, Resource, reqparse, fields

292

from flask_restplus.errors import ValidationError

293

294

api = Api()

295

296

# Model with validation

297

user_model = api.model('User', {

298

'name': fields.String(required=True, min_length=2, max_length=50),

299

'email': fields.String(required=True),

300

'age': fields.Integer(min=0, max=150)

301

})

302

303

@api.route('/users')

304

class UserList(Resource):

305

@api.expect(user_model, validate=True)

306

def post(self):

307

# Validation is automatic when validate=True

308

# Invalid data will trigger ValidationError

309

data = api.payload

310

311

# Additional custom validation

312

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

313

abort(400,

314

message="Invalid email format",

315

field="email",

316

value=data['email'])

317

318

# Process valid data...

319

return {'message': 'User created'}, 201

320

321

# Custom validation handler

322

@api.errorhandler(ValidationError)

323

def handle_validation_error(error):

324

return {

325

'message': 'Input validation failed',

326

'errors': error.msg if hasattr(error, 'msg') else str(error)

327

}, 400

328

```

329

330

### Request Parser Error Handling

331

332

```python

333

from flask_restplus import Api, Resource, reqparse, inputs

334

335

api = Api()

336

337

parser = reqparse.RequestParser(bundle_errors=True)

338

parser.add_argument('name', type=str, required=True, help='Name is required')

339

parser.add_argument('email', type=inputs.email(), required=True, help='Valid email required')

340

parser.add_argument('age', type=inputs.int_range(0, 150), help='Age must be 0-150')

341

342

@api.route('/register')

343

class Register(Resource):

344

@api.expect(parser)

345

def post(self):

346

try:

347

args = parser.parse_args()

348

# Process registration...

349

return {'message': 'Registration successful'}, 201

350

351

except Exception as e:

352

# Parser errors are automatically formatted

353

# With bundle_errors=True, all errors are returned together:

354

# {

355

# "message": "Input payload validation failed",

356

# "errors": {

357

# "name": "Name is required",

358

# "email": "Valid email required",

359

# "age": "Age must be 0-150"

360

# }

361

# }

362

abort(400, message=str(e))

363

```

364

365

### Business Logic Error Handling

366

367

```python

368

from flask_restplus import Api, Resource, abort

369

370

api = Api()

371

372

class InsufficientFundsError(Exception):

373

def __init__(self, balance, amount):

374

self.balance = balance

375

self.amount = amount

376

super().__init__(f"Insufficient funds: balance {balance}, requested {amount}")

377

378

class AccountLockedError(Exception):

379

def __init__(self, account_id, locked_until):

380

self.account_id = account_id

381

self.locked_until = locked_until

382

super().__init__(f"Account {account_id} locked until {locked_until}")

383

384

@api.errorhandler(InsufficientFundsError)

385

def handle_insufficient_funds(error):

386

return {

387

'message': 'Insufficient funds',

388

'balance': error.balance,

389

'requested_amount': error.amount,

390

'shortfall': error.amount - error.balance,

391

'error_code': 'INSUFFICIENT_FUNDS'

392

}, 400

393

394

@api.errorhandler(AccountLockedError)

395

def handle_account_locked(error):

396

return {

397

'message': 'Account temporarily locked',

398

'account_id': error.account_id,

399

'locked_until': error.locked_until.isoformat(),

400

'error_code': 'ACCOUNT_LOCKED'

401

}, 423 # HTTP 423 Locked

402

403

@api.route('/accounts/<int:account_id>/withdraw')

404

class Withdraw(Resource):

405

def post(self, account_id):

406

account = find_account(account_id)

407

amount = api.payload.get('amount', 0)

408

409

if not account:

410

abort(404, message="Account not found")

411

412

if account.is_locked():

413

raise AccountLockedError(account_id, account.locked_until)

414

415

if account.balance < amount:

416

raise InsufficientFundsError(account.balance, amount)

417

418

# Process withdrawal...

419

return {'message': 'Withdrawal successful', 'new_balance': account.balance - amount}

420

```

421

422

### Error Response Documentation

423

424

```python

425

from flask_restplus import Api, Resource, fields

426

427

api = Api()

428

429

# Define standard error models for documentation

430

error_model = api.model('Error', {

431

'message': fields.String(required=True, description='Error message'),

432

'error_code': fields.String(description='Machine-readable error code'),

433

'details': fields.Raw(description='Additional error details')

434

})

435

436

validation_error_model = api.model('ValidationError', {

437

'message': fields.String(required=True, description='Validation error message'),

438

'errors': fields.Raw(required=True, description='Field-specific validation errors')

439

})

440

441

@api.route('/products/<int:product_id>')

442

class Product(Resource):

443

@api.response(200, 'Success')

444

@api.response(404, 'Product not found', error_model)

445

@api.response(400, 'Invalid request', error_model)

446

@api.response(500, 'Internal server error', error_model)

447

def get(self, product_id):

448

"""Get a product by ID"""

449

product = find_product(product_id)

450

451

if not product:

452

abort(404,

453

message="Product not found",

454

error_code="PRODUCT_NOT_FOUND",

455

product_id=product_id)

456

457

return product.to_dict()

458

459

@api.expect(product_model, validate=True)

460

@api.response(201, 'Product created')

461

@api.response(400, 'Validation error', validation_error_model)

462

def post(self, product_id):

463

"""Create or update a product"""

464

# Validation handled automatically

465

data = api.payload

466

# Process product...

467

return {'message': 'Product created'}, 201

468

```

469

470

### Custom Error Formatting

471

472

```python

473

from flask_restplus import Api

474

from flask import jsonify

475

import traceback

476

import uuid

477

478

api = Api()

479

480

def format_error_response(message, code=None, details=None, trace_id=None):

481

"""Format consistent error responses."""

482

response = {

483

'success': False,

484

'message': message,

485

'timestamp': datetime.utcnow().isoformat(),

486

'trace_id': trace_id or str(uuid.uuid4())

487

}

488

489

if code:

490

response['error_code'] = code

491

492

if details:

493

response['details'] = details

494

495

return response

496

497

@api.errorhandler(Exception)

498

def handle_error(error):

499

"""Global error handler with consistent formatting."""

500

trace_id = str(uuid.uuid4())

501

502

# Log error with trace ID for debugging

503

logging.error(f"Error {trace_id}: {str(error)}")

504

logging.error(f"Traceback {trace_id}: {traceback.format_exc()}")

505

506

if isinstance(error, HTTPException):

507

return format_error_response(

508

message=error.description or "HTTP error occurred",

509

code=f"HTTP_{error.code}",

510

trace_id=trace_id

511

), error.code

512

513

# Handle other exceptions

514

return format_error_response(

515

message="An unexpected error occurred",

516

code="INTERNAL_ERROR",

517

details={"type": type(error).__name__},

518

trace_id=trace_id

519

), 500

520

521

@api.route('/test-error')

522

class TestError(Resource):

523

def get(self):

524

# This will trigger the global error handler

525

raise ValueError("This is a test error")

526

```

527

528

### Error Context and Debugging

529

530

```python

531

from flask_restplus import Api, Resource, abort

532

from flask import g, request

533

import logging

534

535

api = Api()

536

537

@api.before_request

538

def before_request():

539

"""Set up request context for error handling."""

540

g.request_id = str(uuid.uuid4())

541

g.start_time = time.time()

542

543

def log_error_context(error, status_code):

544

"""Log error with request context."""

545

context = {

546

'request_id': getattr(g, 'request_id', 'unknown'),

547

'method': request.method,

548

'url': request.url,

549

'user_agent': request.headers.get('User-Agent'),

550

'remote_addr': request.remote_addr,

551

'status_code': status_code,

552

'error': str(error),

553

'duration_ms': (time.time() - getattr(g, 'start_time', 0)) * 1000

554

}

555

556

logging.error(f"Request failed: {context}")

557

558

@api.errorhandler(Exception)

559

def handle_error_with_context(error):

560

"""Error handler that logs full request context."""

561

status_code = 500

562

563

if isinstance(error, HTTPException):

564

status_code = error.code

565

566

log_error_context(error, status_code)

567

568

return {

569

'message': 'An error occurred',

570

'request_id': getattr(g, 'request_id', 'unknown'),

571

'status': status_code

572

}, status_code

573

```