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
```