Using Plugins¶
Added in version 0.9.
Bottle’s core features cover most common use-cases, but as a micro-framework it has its limits. This is where “Plugins” come into play. Plugins add missing functionality to the framework, integrate third party libraries, or just automate some repetitive work.
We have a growing list of3rd Party Plugins and most plugins are designed to be portable and re-usable across applications. Maybe your problem has already been solved and a ready-to-use plugin exists. If not, write your own. SeeWriting Plugins for details.
Plugin Basics¶
Bottles Plugin system builds on the concept ofdecorators. To put it briefly, a plugin is a decorator applied to all route callback in an application. Plugins can do more than just decorating route callbacks, but it is still a good starting point to understand the concept. Lets have a look at a practical example:
frombottleimportresponsedefstopwatch(callback):defwrapper(*args,**kwargs):start=time.time()result=callback(*args,**kwargs)end=time.time()response.headers['X-Exec-Time']=str(end-start)returnresultreturnwrapper
This “stopwatch” decorator measures the execution time of the wrapped function and then writes the result in the non-standardX-Exec-Time response header. You could just manually apply this decorator to all your route callbacks and call it a day:
frombottleimportroute@route("/timed")@stopwatch# <-- This works, but do not do thisdeftimed():time.sleep(1)return"DONE"
But we wouldn’t be talking about a plugin system if there wasn’t a better way to do this!
Managing Plugins¶
Explicitly applying decorators to every single route in a growing application quickly becomes tedious and error-prone. If you really want to apply something toall routes, there is a simpler way:
frombottleimportroute,installinstall(stopwatch)@route("/timed")deftimed():...
Theinstall() method registries a plugin to be automatically applied to all routes in an application. It does not matter if you callinstall() before or after binding routes, all plugins are always applied to all routes. The order ofinstall() calls is important though. If there are multiple plugins, they are applied in the same order the were installed.
Any callable object that works as a route callback decorator is a valid plugin. This includes normal decorators, classes, callables instances, but also plugins that implement the extendedPlugin API. SeeWriting Plugins for details.
You can alsouninstall() a previously installed plugin by name, class or instance:
sqlite_plugin=SQLitePlugin(dbfile='/tmp/test.db')install(sqlite_plugin)uninstall(sqlite_plugin)# uninstall a specific pluginuninstall(SQLitePlugin)# uninstall all plugins of that typeuninstall('sqlite')# uninstall all plugins with that nameuninstall(True)# uninstall all plugins at once
Plugins can be installed and also removed at any time, even at runtime while serving requests. They are applied on-demand, that is, as soon as the route is requested for the first time. This enables some neat tricks (e.g. installing slow debugging or profiling plugins only when needed) but should not be overused. Each time the list of plugins changes, the route cache is flushed and all plugins need to be re-applied.
Note
The module-levelinstall() anduninstall() functions affect thedefault application. To manage plugins for a specific application, use the corresponding methods on aBottle instance.
Selectively apply or skip Plugins¶
Most plugins are smart enough to ignore routes that do not need their functionality and do not add any overhead to those routes, but you can also apply or skip specific plugins per route if you need to.
To apply a decorator or plugin to just a single route, do notinstall() it, but use theapply keyword of theroute() decorator instead:
@route('/timed',apply=[stopwatch])deftimed():...
Route-level plugins are applied first (before application-wide plugins) but handled exactly like normal plugins otherwise.
You can also explicitly disable an installed plugin for a number of routes. Theroute() decorator has askip parameter for this purpose:
install(stopwatch)@route('/notime',skip=[stopwatch])defno_time():pass
Theskip parameter accepts a single value or a list of values. You can use a plugin name, class or instance to identify the plugin that should be skipped. Setskip=True to skip all plugins at once.
Plugins andBottle.mount()¶
Most plugins are specific to the application they were installed to and should not affect sub-applications mounted withBottle.mount(). Here is an example:
root=Bottle()root.install(plugins.WTForms())root.mount('/blog',apps.blog)@root.route('/contact')defcontact():returntemplate('contact',email='contact@example.com')
When mounting an application, Bottle creates a proxy-route on the mounting application that forwards all requests to the mounted application. Plugins are disabled for this kind of proxy-route by default. Our (fictional)WTForms plugin affects the/contact route, but does not affect the routes of the/blog sub-application.
This behavior is intended as a sane default, but can be overridden. The following example re-activates all plugins for a specific proxy-route:
root.mount('/blog',apps.blog,skip=None)
Note that a plugin sees the whole sub-application as a single route, namely the proxy-route mentioned above. To wrap each individual route of the sub-application, you have to install the plugin to the mounted application directly.
Configuring Plugins¶
Most plugins accept configuration as parameters passed to their constructor. This is the easiest and most obvious way to configure a plugin, e.g. to tell a database plugin which database to connect to:
install(SQLitePlugin(dbfile='/tmp/test.db',...))
Newer plugins may also read values fromBottle.config (seeConfiguration). This is useful for configuration that should be easy to change or override for a specific deployment. This pattern even supports runtime changes usingconfig hooks:
app.config["sqlite.db"]='/tmp/test.db'app.install(SQLitePlugin())
Plugins can also inspect the routes they are applied to and change their behavior for individual routes. Plugin authors have full access to the undecorated route callback as well as parameters passed to theroute() decorator, including custom parameters that are otherwise ignored by bottle. This allows for a great level of flexibility. Common patterns include:
A database plugin may automatically start a transaction if the route callback accepts a
dbkeyword.A forms plugin may ignore routes that do not listen to
POSTrequests.An access control plugin may check for a custom
roles_allowedparameter passed to theroute()decorator.
In any case, check the plugin documentation for details. If you want to write your own plugins, check outWriting Plugins.
