or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdindex.mdplugin-system.mdrequest-handling.mdresponse-management.mdserver-management.mdstatic-utilities.mdtemplate-rendering.md

plugin-system.mddocs/

0

# Plugin System

1

2

Plugin architecture for extending Bottle functionality with custom middleware, request/response processing, hooks, and built-in plugins for JSON handling and template rendering.

3

4

## Capabilities

5

6

### Plugin Management

7

8

Install, uninstall, and manage plugins for applications.

9

10

```python { .api }

11

def install(plugin):

12

"""

13

Install a plugin on the default application.

14

15

Parameters:

16

- plugin: plugin instance, class, or callable

17

"""

18

19

def uninstall(plugin):

20

"""

21

Uninstall plugins from the default application.

22

23

Parameters:

24

- plugin: plugin instance, class, type, or string name

25

"""

26

27

class Bottle:

28

def install(self, plugin):

29

"""

30

Install a plugin on this application.

31

32

Parameters:

33

- plugin: plugin instance, class, or callable

34

35

Returns:

36

plugin: installed plugin instance

37

"""

38

39

def uninstall(self, plugin):

40

"""

41

Uninstall plugins from this application.

42

43

Parameters:

44

- plugin: plugin instance, class, type, or string name

45

46

Returns:

47

list: list of uninstalled plugins

48

"""

49

```

50

51

Usage:

52

53

```python

54

from bottle import install, uninstall, Bottle

55

56

# Install plugin on default app

57

install(MyPlugin())

58

59

# Install by class (will instantiate)

60

install(MyPlugin)

61

62

# Uninstall by instance

63

plugin = MyPlugin()

64

install(plugin)

65

uninstall(plugin)

66

67

# Uninstall by type

68

uninstall(MyPlugin)

69

70

# Uninstall by name

71

uninstall('myplugin')

72

73

# Application-specific plugins

74

app = Bottle()

75

app.install(MyPlugin())

76

app.uninstall(MyPlugin)

77

```

78

79

### Hook System

80

81

Register callbacks for application lifecycle events.

82

83

```python { .api }

84

def hook(name):

85

"""

86

Hook decorator for the default application.

87

88

Parameters:

89

- name: str, hook name ('before_request', 'after_request', 'app_reset', 'config')

90

91

Returns:

92

function: decorator function

93

"""

94

95

class Bottle:

96

def hook(self, name):

97

"""Hook decorator for this application."""

98

99

def add_hook(self, name, func):

100

"""

101

Add hook callback directly.

102

103

Parameters:

104

- name: str, hook name

105

- func: callable, hook function

106

"""

107

108

def remove_hook(self, name, func):

109

"""

110

Remove hook callback.

111

112

Parameters:

113

- name: str, hook name

114

- func: callable, hook function to remove

115

116

Returns:

117

bool: True if hook was removed

118

"""

119

120

def trigger_hook(self, name, *args, **kwargs):

121

"""

122

Trigger hook callbacks.

123

124

Parameters:

125

- name: str, hook name

126

- *args: hook arguments

127

- **kwargs: hook keyword arguments

128

129

Returns:

130

list: list of hook return values

131

"""

132

```

133

134

Available hooks:

135

- `before_request`: Called before each request is processed

136

- `after_request`: Called after each request is processed

137

- `app_reset`: Called when application is reset

138

- `config`: Called when configuration changes

139

140

Usage:

141

142

```python

143

@hook('before_request')

144

def before_request():

145

print(f"Processing request: {request.method} {request.path}")

146

147

@hook('after_request')

148

def after_request():

149

print(f"Request completed: {response.status}")

150

151

@hook('config')

152

def config_changed(key, old_value, new_value):

153

print(f"Config {key} changed from {old_value} to {new_value}")

154

155

# Direct hook management

156

app = Bottle()

157

158

def log_request():

159

print(f"Request: {request.path}")

160

161

app.add_hook('before_request', log_request)

162

app.remove_hook('before_request', log_request)

163

```

164

165

### Creating Plugins

166

167

Create custom plugins by implementing the plugin interface.

168

169

```python { .api }

170

class Plugin:

171

"""Base plugin interface."""

172

173

name = 'plugin' # Plugin name for identification

174

api = 2 # Plugin API version

175

176

def apply(self, callback, route):

177

"""

178

Apply plugin to route callback.

179

180

Parameters:

181

- callback: function, route callback function

182

- route: Route, route instance

183

184

Returns:

185

function: wrapped callback function

186

"""

187

188

def setup(self, app):

189

"""

190

Setup plugin when installed.

191

192

Parameters:

193

- app: Bottle, application instance

194

"""

195

196

def close(self):

197

"""Cleanup when plugin is uninstalled."""

198

```

199

200

Simple plugin example:

201

202

```python

203

class TimingPlugin:

204

name = 'timing'

205

api = 2

206

207

def apply(self, callback, route):

208

import time

209

210

def wrapper(*args, **kwargs):

211

start = time.time()

212

try:

213

return callback(*args, **kwargs)

214

finally:

215

duration = time.time() - start

216

print(f"Route {route.rule} took {duration:.3f}s")

217

218

return wrapper

219

220

# Install timing plugin

221

install(TimingPlugin())

222

```

223

224

### Built-in Plugins

225

226

Bottle includes several built-in plugins for common functionality.

227

228

#### JSON Plugin

229

230

Automatic JSON serialization for dictionary return values.

231

232

```python { .api }

233

class JSONPlugin:

234

name = 'json'

235

api = 2

236

237

def __init__(self, json_dumps=json.dumps):

238

"""

239

JSON serialization plugin.

240

241

Parameters:

242

- json_dumps: function, JSON serialization function

243

"""

244

245

def apply(self, callback, route):

246

"""Apply JSON serialization to route."""

247

```

248

249

Configuration:

250

251

```python

252

import json

253

from bottle import JSONPlugin, install

254

255

# Custom JSON encoder

256

def custom_dumps(obj):

257

return json.dumps(obj, indent=2, sort_keys=True)

258

259

# Install with custom encoder

260

install(JSONPlugin(json_dumps=custom_dumps))

261

262

# Configure via app config

263

app.config['json.enable'] = True

264

app.config['json.ascii_only'] = False

265

app.config['json.dumps'] = custom_dumps

266

```

267

268

Usage:

269

270

```python

271

@route('/api/data')

272

def api_data():

273

# Automatically serialized to JSON

274

return {

275

'status': 'success',

276

'data': ['item1', 'item2', 'item3'],

277

'count': 3

278

}

279

```

280

281

#### Template Plugin

282

283

Automatic template rendering for dictionary return values.

284

285

```python { .api }

286

class TemplatePlugin:

287

name = 'template'

288

api = 2

289

290

def apply(self, callback, route):

291

"""Apply template rendering to route."""

292

```

293

294

Usage with template decorator:

295

296

```python

297

@route('/user/<id:int>')

298

@view('user.html')

299

def show_user(id):

300

# Return dict is passed to template

301

return {

302

'user': get_user(id),

303

'title': f'User {id}'

304

}

305

```

306

307

### Plugin Configuration

308

309

Configure plugin behavior and route-specific settings.

310

311

#### Route-specific Plugin Control

312

313

```python

314

@route('/api/raw', apply=['json']) # Only apply json plugin

315

def raw_api():

316

return {'raw': True}

317

318

@route('/api/skip', skip=['json']) # Skip json plugin

319

def skip_json():

320

return "Raw string response"

321

322

@route('/template', template='page.html') # Template plugin config

323

def templated():

324

return {'content': 'Hello World'}

325

```

326

327

#### Plugin Priorities

328

329

Control plugin execution order:

330

331

```python

332

class HighPriorityPlugin:

333

name = 'high'

334

api = 2

335

336

def apply(self, callback, route):

337

def wrapper(*args, **kwargs):

338

print("Before high priority")

339

result = callback(*args, **kwargs)

340

print("After high priority")

341

return result

342

return wrapper

343

344

class LowPriorityPlugin:

345

name = 'low'

346

api = 2

347

348

def apply(self, callback, route):

349

def wrapper(*args, **kwargs):

350

print("Before low priority")

351

result = callback(*args, **kwargs)

352

print("After low priority")

353

return result

354

return wrapper

355

356

# Install in order (first installed wraps innermost)

357

install(LowPriorityPlugin())

358

install(HighPriorityPlugin())

359

```

360

361

### Advanced Plugin Patterns

362

363

#### Context Plugins

364

365

Plugins that provide request context:

366

367

```python

368

class DatabasePlugin:

369

name = 'db'

370

api = 2

371

372

def __init__(self, database_url):

373

self.database_url = database_url

374

self.pool = None

375

376

def setup(self, app):

377

self.pool = create_connection_pool(self.database_url)

378

379

def apply(self, callback, route):

380

def wrapper(*args, **kwargs):

381

with self.pool.get_connection() as db:

382

# Add db to request context

383

bottle.request.db = db

384

try:

385

return callback(*args, **kwargs)

386

finally:

387

# Clean up

388

del bottle.request.db

389

return wrapper

390

391

def close(self):

392

if self.pool:

393

self.pool.close()

394

395

# Usage in routes

396

@route('/users')

397

def list_users():

398

users = request.db.query('SELECT * FROM users')

399

return {'users': users}

400

```

401

402

#### Authentication Plugins

403

404

```python

405

class AuthPlugin:

406

name = 'auth'

407

api = 2

408

409

def __init__(self, auth_func, skip_paths=None):

410

self.auth_func = auth_func

411

self.skip_paths = skip_paths or []

412

413

def apply(self, callback, route):

414

# Skip authentication for certain paths

415

if route.rule in self.skip_paths:

416

return callback

417

418

def wrapper(*args, **kwargs):

419

if not self.auth_func(request):

420

abort(401, 'Authentication required')

421

return callback(*args, **kwargs)

422

return wrapper

423

424

def check_auth(request):

425

token = request.get_header('Authorization')

426

return token and validate_token(token)

427

428

# Install auth plugin

429

install(AuthPlugin(check_auth, skip_paths=['/login', '/public']))

430

```

431

432

#### Caching Plugins

433

434

```python

435

class CachePlugin:

436

name = 'cache'

437

api = 2

438

439

def __init__(self, cache_store):

440

self.cache = cache_store

441

442

def apply(self, callback, route):

443

# Only cache GET requests

444

if getattr(route, 'method', 'GET') != 'GET':

445

return callback

446

447

def wrapper(*args, **kwargs):

448

cache_key = f"{route.rule}:{request.query_string}"

449

450

# Try cache first

451

cached = self.cache.get(cache_key)

452

if cached:

453

return cached

454

455

# Generate response

456

result = callback(*args, **kwargs)

457

458

# Cache result

459

self.cache.set(cache_key, result, timeout=300)

460

return result

461

462

return wrapper

463

```

464

465

### Plugin Error Handling

466

467

Handle errors in plugin processing:

468

469

```python { .api }

470

class PluginError(BottleException):

471

def __init__(self, message):

472

"""Plugin-specific error exception."""

473

```

474

475

Error handling in plugins:

476

477

```python

478

class SafePlugin:

479

name = 'safe'

480

api = 2

481

482

def apply(self, callback, route):

483

def wrapper(*args, **kwargs):

484

try:

485

return callback(*args, **kwargs)

486

except Exception as e:

487

print(f"Plugin error in {route.rule}: {e}")

488

# Could log, send to monitoring, etc.

489

raise # Re-raise or return error response

490

return wrapper

491

```