or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-hooks.mdcharts.mdcli-tools.mdconstants-exceptions.mdcore-framework.mddatabase-models.mdforms-fields.mdindex.mdrest-api.mdsecurity.mdviews-crud.md

rest-api.mddocs/

0

# REST API System

1

2

Complete REST API framework with automatic CRUD operations, OpenAPI documentation, schema generation, and JSON serialization. The API system provides a comprehensive solution for building REST APIs with minimal code while maintaining full customization capabilities.

3

4

## Capabilities

5

6

### ModelRestApi Class

7

8

Full-featured REST API class providing complete CRUD operations with automatic schema generation, validation, filtering, pagination, and OpenAPI documentation.

9

10

```python { .api }

11

from flask_appbuilder.api import ModelRestApi

12

from flask_appbuilder.models.sqla.interface import SQLAInterface

13

14

class ModelRestApi(BaseModelApi):

15

def __init__(self):

16

"""Initialize REST API with automatic endpoint generation."""

17

18

def info(self, **kwargs):

19

"""

20

Get API metadata and schema information.

21

22

GET /_info

23

24

Returns:

25

Dict with model schema, relationships, permissions, filters

26

"""

27

28

def get_list(self, **kwargs):

29

"""

30

Get paginated list of model instances.

31

32

GET /

33

34

Query parameters:

35

- q: Rison encoded query filters

36

- page_size: Number of items per page (max: max_page_size)

37

- page: Page number (0-based)

38

- order_column: Column to order by

39

- order_direction: asc or desc

40

41

Returns:

42

Dict with count, ids, result array

43

"""

44

45

def get(self, pk, **kwargs):

46

"""

47

Get single model instance by primary key.

48

49

GET /<pk>

50

51

Parameters:

52

- pk: Primary key value

53

54

Returns:

55

Dict with model data or 404 error

56

"""

57

58

def post(self):

59

"""

60

Create new model instance.

61

62

POST /

63

64

Request body: JSON with model data

65

66

Returns:

67

Dict with created model data and 201 status

68

"""

69

70

def put(self, pk):

71

"""

72

Update existing model instance.

73

74

PUT /<pk>

75

76

Parameters:

77

- pk: Primary key value

78

Request body: JSON with updated model data

79

80

Returns:

81

Dict with updated model data or 404/422 error

82

"""

83

84

def delete(self, pk):

85

"""

86

Delete model instance by primary key.

87

88

DELETE /<pk>

89

90

Parameters:

91

- pk: Primary key value

92

93

Returns:

94

Dict with success message or 404 error

95

"""

96

97

# Lifecycle hooks

98

def pre_add(self, item):

99

"""

100

Hook called before adding new item.

101

102

Parameters:

103

- item: Model instance to be added

104

"""

105

106

def post_add(self, item):

107

"""

108

Hook called after successfully adding item.

109

110

Parameters:

111

- item: Added model instance

112

"""

113

114

def pre_update(self, item):

115

"""

116

Hook called before updating item.

117

118

Parameters:

119

- item: Model instance to be updated

120

"""

121

122

def post_update(self, item):

123

"""

124

Hook called after successfully updating item.

125

126

Parameters:

127

- item: Updated model instance

128

"""

129

130

def pre_delete(self, item):

131

"""

132

Hook called before deleting item.

133

134

Parameters:

135

- item: Model instance to be deleted

136

"""

137

138

def post_delete(self, item):

139

"""

140

Hook called after successfully deleting item.

141

142

Parameters:

143

- item: Deleted model instance

144

"""

145

146

def pre_get(self, data):

147

"""

148

Hook called before returning single item data.

149

150

Parameters:

151

- data: Serialized item data dict

152

153

Returns:

154

Modified data dict

155

"""

156

157

def pre_get_list(self, data):

158

"""

159

Hook called before returning list data.

160

161

Parameters:

162

- data: Dict with count, ids, result

163

164

Returns:

165

Modified data dict

166

"""

167

168

# API configuration properties

169

datamodel = None # SQLAlchemy interface (required)

170

list_title = "List" # API list title

171

show_title = "Show" # API show title

172

add_title = "Add" # API add title

173

edit_title = "Edit" # API edit title

174

175

# Column configuration

176

list_columns = [] # Columns returned in list view

177

show_columns = [] # Columns returned in show view

178

add_columns = [] # Columns accepted for creation

179

edit_columns = [] # Columns accepted for updates

180

list_exclude_columns = [] # Columns excluded from list

181

show_exclude_columns = [] # Columns excluded from show

182

add_exclude_columns = [] # Columns excluded from add

183

edit_exclude_columns = [] # Columns excluded from edit

184

185

# SQL optimization

186

list_select_columns = [] # Specific SELECT columns for list

187

show_select_columns = [] # Specific SELECT columns for show

188

list_outer_default_load = [] # Outer join loading for list

189

show_outer_default_load = [] # Outer join loading for show

190

191

# Pagination and ordering

192

page_size = 20 # Default page size

193

max_page_size = 100 # Maximum allowed page size

194

order_columns = [] # Columns available for ordering

195

196

# Validation and schema

197

description_columns = {} # Column descriptions

198

validators_columns = {} # Marshmallow validators dict

199

add_query_rel_fields = {} # Related field queries for add

200

edit_query_rel_fields = {} # Related field queries for edit

201

order_rel_fields = {} # Related field ordering

202

203

# Schema customization

204

list_model_schema = None # Custom list schema class

205

add_model_schema = None # Custom add schema class

206

edit_model_schema = None # Custom edit schema class

207

show_model_schema = None # Custom show schema class

208

model2schemaconverter = None # Custom schema converter class

209

210

# Usage example

211

class PersonApi(ModelRestApi):

212

datamodel = SQLAInterface(Person)

213

list_columns = ['id', 'name', 'email', 'created_on']

214

show_columns = ['id', 'name', 'email', 'phone', 'created_on', 'updated_on']

215

add_columns = ['name', 'email', 'phone']

216

edit_columns = ['name', 'email', 'phone']

217

order_columns = ['name', 'email', 'created_on']

218

description_columns = {

219

'name': 'Person full name',

220

'email': 'Contact email address'

221

}

222

223

# Register API

224

appbuilder.add_api(PersonApi)

225

```

226

227

### BaseApi Class

228

229

Foundation class for building custom API endpoints with security, routing, and response handling capabilities.

230

231

```python { .api }

232

from flask_appbuilder.api import BaseApi, expose, safe

233

234

class BaseApi:

235

def __init__(self):

236

"""Initialize API with base configuration."""

237

238

def create_blueprint(self, appbuilder, endpoint=None, static_folder=None):

239

"""

240

Create Flask blueprint for API.

241

242

Parameters:

243

- appbuilder: AppBuilder instance

244

- endpoint: Custom endpoint name

245

- static_folder: Static files folder

246

247

Returns:

248

Flask Blueprint instance

249

"""

250

251

def add_api_spec(self, api_spec):

252

"""

253

Add OpenAPI specification to API documentation.

254

255

Parameters:

256

- api_spec: APISpec instance

257

"""

258

259

def add_apispec_components(self, api_spec):

260

"""

261

Add custom OpenAPI components.

262

263

Parameters:

264

- api_spec: APISpec instance

265

"""

266

267

def get_method_permission(self, method_name):

268

"""

269

Get permission name for API method.

270

271

Parameters:

272

- method_name: API method name

273

274

Returns:

275

Permission name string

276

"""

277

278

# Standard HTTP responses

279

def response(self, code, **kwargs):

280

"""

281

Generic JSON response with status code.

282

283

Parameters:

284

- code: HTTP status code

285

- **kwargs: Response data

286

287

Returns:

288

Flask Response with JSON content

289

"""

290

291

def response_400(self, message="Bad request"):

292

"""Bad Request (400) response."""

293

294

def response_401(self):

295

"""Unauthorized (401) response."""

296

297

def response_403(self):

298

"""Forbidden (403) response."""

299

300

def response_404(self):

301

"""Not Found (404) response."""

302

303

def response_422(self, message="Unprocessable Entity"):

304

"""Unprocessable Entity (422) response."""

305

306

def response_500(self, message="Internal server error"):

307

"""Internal Server Error (500) response."""

308

309

# API configuration properties

310

endpoint = "" # API endpoint name

311

version = "v1" # API version

312

route_base = "" # API route base (auto-generated)

313

resource_name = "" # Resource name for OpenAPI

314

base_permissions = [] # Base permissions list

315

class_permission_name = "" # Class permission name override

316

method_permission_name = {} # Method permission name overrides

317

allow_browser_login = False # Allow Flask-Login session auth

318

csrf_exempt = [] # CSRF exempt methods list

319

apispec_parameter_schemas = {} # Custom parameter schemas

320

openapi_spec_component_schemas = {} # OpenAPI component schemas

321

responses = {} # Standard HTTP responses dict

322

exclude_route_methods = set() # Methods to exclude from routes

323

include_route_methods = set() # Methods to include in routes

324

openapi_spec_methods = {} # OpenAPI spec method overrides

325

openapi_spec_tag = {} # OpenAPI tag configuration

326

limits = [] # Rate limiting configuration

327

328

# Usage example

329

class CustomApi(BaseApi):

330

route_base = "/custom"

331

332

@expose('/hello/')

333

@safe

334

def hello(self):

335

return self.response(200, message="Hello World")

336

337

@expose('/protected/')

338

@protect()

339

def protected_endpoint(self):

340

return self.response(200, user=g.user.username)

341

342

# Register custom API

343

appbuilder.add_api(CustomApi)

344

```

345

346

### API Decorators

347

348

Decorators for API endpoint exposure, parameter parsing, error handling, and response processing.

349

350

```python { .api }

351

from flask_appbuilder.api import expose, rison, safe, merge_response_func

352

353

@expose("/", methods=["GET", "POST"])

354

def expose(url="/", methods=("GET",)):

355

"""

356

Expose API method as endpoint.

357

358

Parameters:

359

- url: URL pattern for endpoint

360

- methods: HTTP methods list

361

362

Usage:

363

@expose('/users/', methods=['GET', 'POST'])

364

@protect()

365

def users_endpoint(self):

366

if request.method == 'GET':

367

return self.get_users()

368

return self.create_user()

369

"""

370

371

@rison(schema=UserSchema())

372

def rison(schema=None):

373

"""

374

Parse URI Rison arguments using Marshmallow schema.

375

376

Parameters:

377

- schema: Marshmallow schema for validation

378

379

Usage:

380

from marshmallow import Schema, fields

381

382

class FilterSchema(Schema):

383

name = fields.String()

384

active = fields.Boolean()

385

386

@expose('/filtered/')

387

@rison(FilterSchema())

388

@protect()

389

def filtered_data(self, **kwargs):

390

filters = kwargs.get('rison', {})

391

return self.response(200, filters=filters)

392

"""

393

394

@safe

395

def safe(f):

396

"""

397

Catch uncaught exceptions and return JSON error response.

398

399

Usage:

400

@expose('/may-fail/')

401

@safe

402

def risky_endpoint(self):

403

# This will catch any unhandled exceptions

404

# and return proper JSON error response

405

result = risky_operation()

406

return self.response(200, result=result)

407

"""

408

409

@merge_response_func(custom_processor, "data")

410

def merge_response_func(func, key):

411

"""

412

Merge response function results into endpoint response.

413

414

Parameters:

415

- func: Function to call with response data

416

- key: Key name for merged data

417

418

Usage:

419

def add_metadata(data):

420

return {"timestamp": datetime.utcnow().isoformat()}

421

422

@expose('/with-meta/')

423

@merge_response_func(add_metadata, "meta")

424

@protect()

425

def endpoint_with_meta(self):

426

return self.response(200, result="success")

427

# Response includes merged metadata

428

"""

429

```

430

431

### BaseModelApi Class

432

433

Foundation for model-based APIs providing data model integration, filtering, and search capabilities without full CRUD operations.

434

435

```python { .api }

436

from flask_appbuilder.api import BaseModelApi

437

438

class BaseModelApi(BaseApi):

439

"""Base class for model APIs with search and filter capabilities."""

440

441

# Model integration properties

442

datamodel = None # SQLAlchemy interface (required)

443

search_columns = [] # Searchable columns

444

search_filters = {} # Custom search filters

445

search_exclude_columns = [] # Columns excluded from search

446

label_columns = {} # Column label overrides

447

base_filters = [] # Base query filters

448

base_order = [] # Default query ordering

449

450

# Usage example - Read-only API

451

class ReadOnlyPersonApi(BaseModelApi):

452

datamodel = SQLAInterface(Person)

453

search_columns = ['name', 'email']

454

base_filters = [['active', FilterEqual, True]]

455

456

@expose('/search/')

457

@protect()

458

def search_persons(self):

459

# Custom search implementation using datamodel

460

query = self.datamodel.get_query()

461

# Apply search logic

462

return self.response(200, results=query.all())

463

```

464

465

### OpenAPI Integration

466

467

Automatic OpenAPI (Swagger) documentation generation for all API endpoints with customizable schemas and documentation.

468

469

```python { .api }

470

# OpenAPI is automatically enabled for all ModelRestApi endpoints

471

# Access documentation at: /api/v1/openapi.json

472

# Swagger UI available at: /swaggerview/

473

474

# Custom OpenAPI schemas

475

from marshmallow import Schema, fields

476

477

class PersonResponseSchema(Schema):

478

id = fields.Integer()

479

name = fields.String(description="Person's full name")

480

email = fields.Email(description="Contact email address")

481

created_on = fields.DateTime()

482

483

class PersonApi(ModelRestApi):

484

datamodel = SQLAInterface(Person)

485

486

# Custom response schema

487

show_model_schema = PersonResponseSchema

488

489

# Custom OpenAPI tags

490

openapi_spec_tag = {

491

"name": "Persons",

492

"description": "Person management endpoints"

493

}

494

495

# Custom parameter schemas

496

apispec_parameter_schemas = {

497

"person_id": {

498

"in": "path",

499

"schema": {"type": "integer", "minimum": 1},

500

"description": "Person ID"

501

}

502

}

503

504

# Configuration options

505

FAB_API_SWAGGER_UI = True # Enable Swagger UI

506

FAB_API_SWAGGER_TEMPLATE = "appbuilder/swagger/swagger.html"

507

FAB_API_MAX_PAGE_SIZE = 100 # Maximum API page size

508

```

509

510

### API Error Handling

511

512

Comprehensive error handling with structured JSON responses and proper HTTP status codes.

513

514

```python { .api }

515

# Standard error response format

516

{

517

"message": "Error description",

518

"severity": "error", # or "warning", "info"

519

"details": {} # Additional error details

520

}

521

522

# Common API errors and responses:

523

524

# 400 Bad Request - Invalid input

525

{

526

"message": "Invalid request data",

527

"severity": "error",

528

"details": {"field": "validation error message"}

529

}

530

531

# 401 Unauthorized - Authentication required

532

{

533

"message": "Authentication required",

534

"severity": "error"

535

}

536

537

# 403 Forbidden - Insufficient permissions

538

{

539

"message": "Access denied",

540

"severity": "error"

541

}

542

543

# 404 Not Found - Resource not found

544

{

545

"message": "Record not found",

546

"severity": "error"

547

}

548

549

# 422 Unprocessable Entity - Validation errors

550

{

551

"message": "Validation failed",

552

"severity": "error",

553

"details": {

554

"email": ["Invalid email address"],

555

"name": ["Name is required"]

556

}

557

}

558

559

# Custom error handling in APIs

560

class PersonApi(ModelRestApi):

561

def pre_add(self, item):

562

if not item.email:

563

raise ValidationError("Email is required")

564

565

def post_add(self, item):

566

try:

567

send_welcome_email(item.email)

568

except Exception as e:

569

# Log error but don't fail the request

570

log.error(f"Failed to send welcome email: {e}")

571

```