File: docs/plugins/dev.rst
.. currentmodule:: bottle
.. _plugindev:
=============== Writing Plugins
This guide explains the plugin API and how to write custom plugins. I suggest reading :ref:plugin-basics first if you have not done so already. You might also want to have a look at the :doc:/plugins/index for some practical examples.
Plugin API
Any callable that accepts a function and returns a function is a valid plugin. This simple approach has its limits, though. Plugins that need more context and control can implement the extended :class:Plugin interface and hook into advanced features. Note that this is not a real class you can import from :mod:bottle, just a contract that plugins must implement to be recognized as extended plugins.
.. class:: Plugin
Plugins must be callable or implement :meth:`apply`. If :meth:`apply` is defined, it is always preferred over calling the plugin directly. All other methods and attributes are optional.
.. attribute:: name
Both :meth:`Bottle.uninstall` and the `skip` parameter of :meth:`Bottle.route()` accept a name string to refer to a plugin or plugin type. This works only for plugins that have a name attribute.
.. attribute:: api
The Plugin API is still evolving. This integer attribute tells bottle which version to use. If it is missing, bottle defaults to the first version. The current version is ``2``. See :ref:`plugin-changelog` for details.
.. method:: setup(self, app: Bottle)
Called as soon as the plugin is installed to an application via :meth:`Bottle.install`. The only parameter is the application object the plugin is installed to. This method is *not* called for plugins that were applied to routes via `apply`, but only for plugins installed to an application.
.. method:: __call__(self, callback)
As long as :meth:`apply` is not defined, the plugin itself is used as a decorator and applied directly to each route callback. The only parameter is the callback to be decorated. Whatever is returned by this method replaces the original callback. If there is no need to wrap or replace a given callback, just return the unmodified callback parameter.
.. method:: apply(self, callback, route: Route)
If defined, this method is used in favor of :meth:`__call__` to decorate route callbacks. The additional `route` parameter is an instance of :class:`Route` and provides a lot of context and meta-information and about the route to be decorated. See :ref:`route-context` for details.
.. method:: close(self)
Called as soon as the plugin is uninstalled or the application is closed (see :meth:`Bottle.uninstall` or :meth:`Bottle.close`). This method is *not* called for plugins that were applied to routes via `apply`, but only for plugins installed to an application... _plugin-changelog:
Plugin API Versions
The Plugin API is still evolving and changed with Bottle 0.10 to address certain issues with the route context dictionary. To ensure backwards compatibility with 0.9 Plugins, we added an optional :attr:Plugin.api attribute to tell bottle which API to use. The API differences are summarized here.
Bottle 0.9 API 1 (:attr:
Plugin.apinot present)- Original Plugin API as described in the 0.9 docs.
Bottle 0.10 API 2 (:attr:
Plugin.apiequals 2)- The
contextparameter of the :meth:Plugin.applymethod is now an instance of :class:Routeinstead of a context dictionary.
- The
.. _route-context:
The Route Context
The :class:Route instance passed to :meth:Plugin.apply provides detailed information about the to-be-decorated route, the original route callback and route specific configuration.
Keep in mind that :attr:Route.config is local to the route, but shared between all plugins. It is always a good idea to add a unique prefix or, if your plugin needs a lot of configuration, store it in a separate namespace within the config dictionary. This helps to avoid naming collisions between plugins.
While some :class:Route attributes are mutable, changes may have unwanted effects on other plugins and also only affect plugins that were not applied yet. If you need to make changes to the route that are recognized by all plugins, call :meth:Route.reset afterwards. This will clear the route cache and apply all plugins again next time the route is called, giving all plugins a chance to adapt to the new config. The router is not updated, however. Changes to rule or method values have no effect on the router, only on plugins. This may change in the future.
Runtime optimizations
Once all plugins are applied to a route, the wrapped route callback is cached to speed up subsequent requests. If the behavior of your plugin depends on configuration, and you want to be able to change that configuration at runtime, you need to read the configuration on each request. Easy enough.
For performance reasons however, it might be worthwhile to return a different wrapper based on current needs, work with closures, or enable or disable a plugin at runtime. Let's take the built-in HooksPlugin as an example: If no hooks are installed, the plugin removes itself from all routes and has virtually no overhead. As soon as you install the first hook, the plugin activates itself and takes effect again.
To achieve this, you need control over the callback cache: :meth:Route.reset clears the cache for a single route and :meth:Bottle.reset clears all caches for all routes of an application at once. On the next request, all plugins are re-applied to the route as if it were requested for the first time.
Common patterns
.. rubric:: Dependency or resource injection
Plugins may checks if the callback accepts a specific keyboard parameter and only apply themselves if that parameter is present. For example, route callbacks that expect a db keyword argument need a database connection. Routes that do not expect such a parameter can be skipped and not decorated. The paramneter name should be configurable to avoid conflicts with other plugins or route parameters.
.. rubric:: Request context properties
Plugins may add new request-local properties to the current :data:request, for example request.session for a durable session or request.user for logged in users. See :class:Request.__setattr__ .
.. rubric:: Response type mapping
Plugins may check the return value of the wrapped callback and transform or serialize the output to a new type. The bundled :class:JsonPlugin does exactly that.
.. rubric:: Zero overhead plugins
Plugins that are not needed on a specific route should return the callback unchanged. If they want to remove themselves from a route at runtime, they can call :meth:Route.reset and skip the route the next time it is triggered.
.. rubric:: Before / after each request
Plugins can be a convenient alternative to before_request or after_request hooks (see :meth:Bottle.add_hook), especially if both are needed.
Plugin Example: SQLitePlugin
This plugin provides an sqlite3 database connection handle as an additional keyword argument to wrapped callbacks, but only if the callback expects it. If not, the route is ignored and no overhead is added. The wrapper does not affect the return value, but handles plugin-related exceptions properly. :meth:Plugin.setup is used to inspect the application and search for conflicting plugins.
::
import sqlite3
import inspect
class SQLitePlugin:
name = 'sqlite'
api = 2
def __init__(self,
dbfile=':memory:',
autocommit=True,
dictrows=True,
keyword='db'):
self.dbfile = dbfile
self.autocommit = autocommit
self.dictrows = dictrows
self.keyword = keyword
def setup(self, app):
''' Make sure that other installed plugins don't affect the same
keyword argument.'''
for other in app.plugins:
if not isinstance(other, SQLitePlugin): continue
if other.keyword == self.keyword:
raise PluginError("Found another sqlite plugin with "\
"conflicting settings (non-unique keyword).")
def apply(self, callback, route):
# Override global configuration with route-specific values.
conf = route.config.get('sqlite') or {}
dbfile = conf.get('dbfile', self.dbfile)
autocommit = conf.get('autocommit', self.autocommit)
dictrows = conf.get('dictrows', self.dictrows)
keyword = conf.get('keyword', self.keyword)
# Test if the original callback accepts a 'db' keyword.
# Ignore it if it does not need a database handle.
args = inspect.getargspec(route.callback)[0]
if keyword not in args:
return callback
def wrapper(*args, **kwargs):
# Connect to the database
db = sqlite3.connect(dbfile)
# This enables column access by name: row['column_name']
if dictrows: db.row_factory = sqlite3.Row
# Add the connection handle as a keyword argument.
kwargs[keyword] = db
try:
rv = callback(*args, **kwargs)
if autocommit: db.commit()
except sqlite3.IntegrityError, e:
db.rollback()
raise HTTPError(500, "Database Error", e)
finally:
db.close()
return rv
# Replace the route callback with the wrapped one.
return wrapperThis plugin is just an example, but actually usable::
sqlite = SQLitePlugin(dbfile='/tmp/test.db')
bottle.install(sqlite)
@route('/show/')
def show(page, db):
row = db.execute('SELECT * from pages where name=?', page).fetchone()
if row:
return template('showpage', page=row)
return HTTPError(404, "Page not found")
@route('/static/<fname:path>')
def static(fname):
return static_file(fname, root='/some/path')
@route('/admin/set/<db:re:[a-zA-Z]+>', skip=[sqlite])
def change_dbfile(db):
sqlite.dbfile = '/tmp/%s.db' % db
return "Switched DB to %s.db" % dbThe first route needs a database connection and tells the plugin to create a handle by accepting a db keyword argument. The second route does not need a database and is therefore ignored by the plugin. The third route does expect a 'db' keyword argument, but explicitly skips the sqlite plugin. This way the argument is not overruled by the plugin and still contains the value of the same-named url argument.
File: docs/plugins/index.rst
.. currentmodule:: bottle
.. _plugins:
============= Using Plugins
.. versionadded:: 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 of :doc:/plugins/list 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. See :doc:/plugins/dev for details.
.. _plugin-basics:
Plugin Basics
Bottles Plugin system builds on the concept of decorators <https://docs.python.org/glossary.html#term-decorator>_. 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::
from bottle import response
def stopwatch(callback):
def wrapper(*args, **kwargs):
start = time.time()
result = callback(*args, **kwargs)
end = time.time()
response.headers['X-Exec-Time'] = str(end - start)
return result
return wrapperThis "stopwatch" decorator measures the execution time of the wrapped function and then writes the result in the non-standard X-Exec-Time response header. You could just manually apply this decorator to all your route callbacks and call it a day::
from bottle import route
@route("/timed")
@stopwatch # <-- This works, but do not do this
def timed():
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 to all routes, there is a simpler way::
from bottle import route, install
install(stopwatch)
@route("/timed")
def timed():
...The :func:install method registries a plugin to be automatically applied to all routes in an application. It does not matter if you call :func:install before or after binding routes, all plugins are always applied to all routes. The order of :func:install 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 extended :class:Plugin API. See :ref:plugindev for details.
You can also :func:uninstall 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 plugin
uninstall(SQLitePlugin) # uninstall all plugins of that type
uninstall('sqlite') # uninstall all plugins with that name
uninstall(True) # uninstall all plugins at oncePlugins 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-level :func:install and :func:uninstall functions affect the :ref:default application <default-app>. To manage plugins for a specific application, use the corresponding methods on a :class:Bottle 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 not :func:install it, but use the apply keyword of the :func:route decorator instead::
@route('/timed', apply=[stopwatch])
def timed():
...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. The :func:route decorator has a skip parameter for this purpose::
install(stopwatch)
@route('/notime', skip=[stopwatch])
def no_time():
passThe skip 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. Set skip=True to skip all plugins at once.
Plugins and :meth:`Bottle.mount`
Most plugins are specific to the application they were installed to and should not affect sub-applications mounted with :meth:Bottle.mount. Here is an example::
root = Bottle()
root.install(plugins.WTForms())
root.mount('/blog', apps.blog)
@root.route('/contact')
def contact():
return template('contact', email='[email protected]')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 from :attr:Bottle.config (see :doc:../configuration). This is useful for configuration that should be easy to change or override for a specific deployment. This pattern even supports runtime changes using :ref:config hooks <conf-hook>::
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 the :meth:route 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 the :meth:routedecorator.
In any case, check the plugin documentation for details. If you want to write your own plugins, check out :doc:dev.
File: docs/plugins/list.rst
.. currentmodule:: bottle
========================= 3rd Party Plugins
This is a list of third-party plugins that add extend Bottles core functionality or integrate other libraries with the Bottle framework.
Have a look at :ref:plugins for general questions about plugins (installation, usage). If you plan to develop a new plugin, the :ref:plugindev may help you.
Bottle-Beaker <https://pypi.python.org/pypi/bottle-beaker/>_
Beaker to session and caching library with WSGI Middleware
Bottle-Cork <https://pypi.org/project/bottle-cork/>_
Cork provides a simple set of methods to implement Authentication and Authorization in web applications based on Bottle.
Bottle-Cors-plugin <https://pypi.org/project/bottle-cors-plugin/>_
Cors-plugin is the easiest way to implement cors on your bottle web application
Bottle-Extras <https://pypi.python.org/pypi/bottle-extras/>_
Meta package to install the bottle plugin collection.
Bottle-Flash <https://pypi.python.org/pypi/bottle-flash/>_
flash plugin for bottle
Bottle-Hotqueue <https://pypi.python.org/pypi/bottle-hotqueue/>_
FIFO Queue for Bottle built upon redis
Macaron <https://nobrin.github.com/macaron/webapp.html>_
Macaron is an object-relational mapper (ORM) for SQLite.
Bottle-Memcache <https://pypi.python.org/pypi/bottle-memcache/>_
Memcache integration for Bottle.
Bottle-Mongo <https://pypi.python.org/pypi/bottle-mongo/>_
MongoDB integration for Bottle
Bottle-OAuthlib <https://pypi.python.org/pypi/bottle-oauthlib/>_
Adapter for oauthlib - create your own OAuth2.0 implementation
Bottle-Redis <https://pypi.python.org/pypi/bottle-redis/>_
Redis integration for Bottle.
Bottle-Renderer <https://pypi.python.org/pypi/bottle-renderer/>_
Renderer plugin for bottle
Bottle-Servefiles <https://pypi.python.org/pypi/bottle-servefiles/>_
A reusable app that serves static files for bottle apps
Bottle-Sqlalchemy <https://pypi.python.org/pypi/bottle-sqlalchemy/>_
SQLAlchemy integration for Bottle.
Bottle-Sqlite <https://pypi.python.org/pypi/bottle-sqlite/>_
SQLite3 database integration for Bottle.
Bottle-Web2pydal <https://pypi.python.org/pypi/bottle-web2pydal/>_
Web2py Dal integration for Bottle.
Bottle-Werkzeug <https://pypi.python.org/pypi/bottle-werkzeug/>_
Integrates the werkzeug library (alternative request and response objects, advanced debugging middleware and more).
bottle-smart-filters <https://github.com/agile4you/bottle-smart-filters/>_
Bottle Querystring smart guessing.
bottle-jwt <https://github.com/agile4you/bottle-jwt/>_
JSON Web Token authentication plugin for bottle.py
bottlejwt <https://github.com/agalera/bottlejwt>_
JWT integration for bottle
canister <https://github.com/dagnelies/canister>_
a bottle wrapper to provide logging, sessions and authentication
bottle-cerberus <https://github.com/agalera/bottle-cerberus>_
Cerberus integration for bottle
Bottle-errorsrest <https://github.com/agalera/bottle-errorsrest>_
All errors generated from bottle are returned in json
Bottle-tools <https://github.com/theSage21/bottle-tools>_
Decorators that auto-supply function arguments using POST/query string data.
Plugins listed here are not part of Bottle or the Bottle project, but developed and maintained by third parties.
File: docs/api.rst
============================== API Reference
.. module:: bottle
:platform: Unix, Windows
:synopsis: WSGI micro framework
.. moduleauthor:: Marcel Hellkamp [email protected]
Bottle prides itself on having source code that is easy to read and follow, so most questions about APIs or inner workings can be answered quickly by inspecting sources. Your IDE should give you the same information you'll find on this page, as most of it is auto-generates from docstrings and method signatures anyway. If you are new to bottle, you may find the :doc:tutorial more helpful as a starting point.
Global functions
The module defines several functions, constants, and an exception.
.. function:: app()
default_app()
Return the current :ref:`default-app`. This is actually a callable instances of :class:`AppStack`... autofunction:: debug
.. autofunction:: install
.. autofunction:: uninstall
.. autofunction:: run
Global decorators
Bottle maintains a stack of :class:Bottle instances (see :func:app and :class:AppStack)
and uses the top of the stack as a :ref:default-app for some of the module-level functions
and decorators. All of those have a corresponding method on the :class:Bottle class.
.. function:: route(path, method='GET', callback=None, **options)
get(...)
post(...)
put(...)
delete(...)
patch(...)
Decorator to install a route to the current default application. See :meth:Bottle.route for details.
.. function:: error(...)
Decorator to install an error handler to the current default application. See :meth:Bottle.error for details.
.. autofunction:: hook
Request Context
The global :data:request and :data:response instances are only valid from within an request handler function and represent the current HTTP request or response.
.. autodata:: request
.. autodata:: response
Helper Functions
.. autofunction:: abort
.. autofunction:: redirect
.. autofunction:: static_file
Exceptions
.. autoexception:: BottleException
:members:
.. autoexception:: HTTPResponse
:members:
.. autoexception:: HTTPError
:members:
The :class:`Bottle` Class
.. autoclass:: Bottle
:members:
The :class:`Request` Object
The :class:Request class wraps a WSGI environment and provides helpful methods to parse and access form data, cookies, file uploads and other metadata. Most of the attributes are read-only.
.. autoclass:: Request
:members:
.. autoclass:: BaseRequest
:members:
:special-members: setattr
.. autoclass:: LocalRequest
:members:
The :class:`Response` Object
The :class:Response class stores the HTTP status code as well as headers and cookies that are to be sent to the client. Similar to :data:bottle.request there is a thread-local :data:bottle.response instance that can be used to adjust the current response. Moreover, you can instantiate :class:Response and return it from your request handler. In this case, the custom instance overrules the headers and cookies defined in the global one.
.. autoclass:: Response
:members:
.. autoclass:: BaseResponse
:members:
.. autoclass:: LocalResponse
:members:
Data Structures
.. autoclass:: AppStack
:members:
.. method:: pop()
Return the current default application and remove it from the stack... autoclass:: ConfigDict
:members:
.. autoclass:: MultiDict
:members:
.. autoclass:: WSGIHeaderDict
:members:
.. autoclass:: HeaderDict
:members:
.. autoclass:: FormsDict
:members:
.. autoclass:: FileUpload
:members:
Request routing
.. autoclass:: Router
:members:
.. autoclass:: Route
:members:
Templating
All template engines supported by :mod:bottle implement the :class:BaseTemplate API. This way it is possible to switch and mix template engines without changing the application code at all.
.. autoclass:: BaseTemplate
:members:
.. autofunction:: view
.. autofunction:: template
.. autodata:: TEMPLATE_PATH
Global search path for templates.
You can write your own adapter for your favourite template engine or use one of the predefined adapters. Currently there are four fully supported template engines:
======================== ================================== ==================== ========================
Class URL Decorator Render function
======================== ================================== ==================== ========================
:class:SimpleTemplate :doc:stpl :func:view :func:template
:class:MakoTemplate https://www.makotemplates.org :func:mako_view :func:mako_template
:class:CheetahTemplate https://www.cheetahtemplate.org/ :func:cheetah_view :func:cheetah_template
:class:Jinja2Template https://jinja.palletsprojects.com/ :func:jinja2_view :func:jinja2_template
======================== ================================== ==================== ========================
To use :class:MakoTemplate as your default template engine, just import its specialised decorator and render function::
from bottle import mako_view as view, mako_template as template
HTTP utilities
.. autofunction:: parse_date
.. autofunction:: parse_auth
.. autofunction:: cookie_encode
.. autofunction:: cookie_decode
.. autofunction:: cookie_is_encoded
.. autofunction:: path_shift
.. autodata:: HTTP_CODES
:no-value:
Misc utilities
.. autoclass:: DictProperty
.. autoclass:: cached_property
.. autoclass:: lazy_attribute
.. autofunction:: yieldroutes
.. autofunction:: load
.. autofunction:: load_app
File: docs/configuration.rst
============= Configuration
.. currentmodule:: bottle
Bottle applications can store their configuration in :attr:Bottle.config, a dict-like object and central place for application specific settings. This dictionary controls many aspects of the framework, tells (newer) plugins what to do, and can be used to store your own configuration as well.
Configuration Basics
The :attr:Bottle.config object behaves a lot like an ordinary dictionary. All the common dict methods work as expected. Let us start with some examples::
import bottle
app = bottle.default_app() # or bottle.Bottle() if you prefer
app.config['autojson'] = False # Turns off the "autojson" feature
app.config['sqlite.db'] = ':memory:' # Tells the sqlite plugin which db to use
app.config['myapp.param'] = 'value' # Example for a custom config value.
# Change many values at once
app.config.update({
'autojson': False,
'sqlite.db': ':memory:',
'myapp.param': 'value'
})
# Add default values
app.config.setdefault('myapp.param2', 'some default')
# Receive values
param = app.config['myapp.param']
param2 = app.config.get('myapp.param2', 'fallback value')
# An example route using configuration values
@app.route('/about', view='about.rst')
def about():
email = app.config.get('my.email', '[email protected]')
return {'email': email}Helper functions can rely on :attr:Request.app to get the application
and configuration associated with the current request::
from bottle import request
def is_admin(user):
return user == request.app.config['myapp.admin_user']Naming Convention
To make life easier, plugins and applications should follow some simple rules when it comes to config parameter names:
- All keys should be lowercase strings and follow the rules for python identifiers (no special characters but the underscore).
- Namespaces are separated by dots (e.g.
namespace.fieldornamespace.subnamespace.field). - Bottle uses the root namespace for its own configuration. Plugins should store all their variables in their own namespace (e.g.
sqlite.dborwerkzeug.use_debugger). - Your own application should use a separate namespace (e.g.
myapp.*).
Load configuration from a File
.. versionadded 0.12
Configuration files are useful if you want to enable non-programmers to configure your application,
or just don't want to hack python module files just to change the database port. A very common syntax for configuration files is shown here:
.. code-block:: ini
[sqlite]
db = /tmp/test.db
commit = auto
[myapp]
admin_user = defnullWith :meth:ConfigDict.load_config you can load these *.ini style configuration
files from disk and import their values into your existing configuration::
app.config.load_config('/etc/myapp.conf')Load configuration from a python module
.. versionadded 0.13
Loading configuration from a Python module is a common pattern for Python
programs and frameworks. :meth:ConfigDict.load_module imports a python
module by name and adds all upper-case variables to configuration.
.. code-block:: python
DEBUG = True
SQLITE = {"db": ":memory:"}.. code-block::
c = ConfigDict()
c.load_module('config')
{'debug': True, 'sqlite.db': 'memory'}
c.load_module("config", squash=False)
{'debug': True, 'sqlite': {'db': 'memory'}}
Configuration is flattened by default, similar to the behavior of :meth:ConfigDict.load_dict. You can prevent that by passing squash=False as a parameter.
Loading configuration from a :class:`dict`
.. versionadded 0.12
Another useful method is :meth:ConfigDict.load_dict. This method takes
an entire structure of nested dictionaries and turns it into a flat list
of keys and values with namespaced keys::
# Load an entire dict structure
app.config.load_dict({
'autojson': False,
'sqlite': { 'db': ':memory:' },
'myapp': {
'param': 'value',
'param2': 'value2'
}
})
assert app.config['myapp.param'] == 'value'
# Load configuration from a json file
with open('/etc/myapp.json') as fp:
app.config.load_dict(json.load(fp)).. _conf-hook:
Listening to configuration changes
.. versionadded 0.12
Bottle triggers the config application hook each time a value in :attr:Bottle.config is about to be changed.
This hook can be used to dynamically re-configure plugins or parts of your application once configuration changes at runtime.
For example:
- Enable or disable maintenance or debug mode.
- Reconnect to a new database.
- Change the settings on a plugin or background service.
- Resize worker thread pools.
The hook callback receives two arguments (key, new_value) and is called before the value is actually changed in the dictionary.
Raising an exception from this callback will prevent the change and preserve the old value.
::
@app.hook('config')
def on_config_change(key, value):
if key == 'debug':
switch_own_debug_mode_to(value)
The hook callbacks cannot change the value that is to be stored to the dictionary. That is what filters are for.
.. _conf-meta:
Filters and other Meta Data
.. versionadded 0.12
:class:ConfigDict allows you to store meta data along with configuration keys. Two meta fields are currently defined:
help
A help or description string. May be used by debugging, introspection or
admin tools to help the site maintainer configuring their application.filter
A callable that accepts and returns a single value. If a filter is defined for a key, any new value stored to that key is first passed through the filter callback. The filter can be used to cast the value to a different type, check for invalid values (throw a ValueError) or trigger side effects.
This feature is most useful for plugins. They can validate their config parameters or trigger side effects using filters and document their configuration via help fields::
class SomePlugin(object):
def setup(app):
app.config.meta_set('some.int', 'filter', int)
app.config.meta_set('some.list', 'filter',
lambda val: str(val).split(';'))
app.config.meta_set('some.list', 'help',
'A semicolon separated list.')
def apply(self, callback, route):
...
import bottle
app = bottle.default_app()
app.install(SomePlugin())
app.config['some.list'] = 'a;b;c' # Actually stores ['a', 'b', 'c']
app.config['some.int'] = 'not an int' # raises ValueErrorAPI Documentation
.. versionadded 0.12
.. autoclass:: ConfigDict
:members:
:no-index:
File: docs/deployment.rst
.. highlight:: python
.. currentmodule:: bottle
.. _flup: https://pypi.org/project/flup/
.. _gae: https://code.google.com/appengine/docs/python/overview.html
.. _wsgiref: https://docs.python.org/library/wsgiref.html
.. _cherrypy: https://cherrypy.dev/
.. _paste: https://pythonpaste.readthedocs.io/
.. _gunicorn: https://pypi.python.org/pypi/gunicorn
.. _tornado: https://www.tornadoweb.org/
.. _twisted: https://twistedmatrix.com/
.. _diesel: https://dieselweb.org/
.. _meinheld: https://pypi.python.org/pypi/meinheld
.. _bjoern: https://pypi.python.org/pypi/bjoern
.. _gevent: https://www.gevent.org/
.. _eventlet: https://eventlet.net/
.. _waitress: https://readthedocs.org/docs/waitress/en/latest/
.. _apache: https://httpd.apache.org/
.. _mod_wsgi: https://code.google.com/p/modwsgi/
.. _pound: https://www.apsis.ch/pound
.. _nginx: https://nginx.org/
.. _lighttpd: https://www.lighttpd.net/
.. _cherokee: https://cherokee-project.com/
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
.. _cheroot: https://cheroot.cherrypy.dev/
.. _tutorial-deployment:
================================================================================ Deployment
The bottle :func:run function, when called without any parameters, starts a local development server on port 8080. You can access and test your application via http://localhost:8080/ if you are on the same host.
To get your application available to the outside world, specify the IP the server should listen to (e.g. run(host='192.168.0.1')) or let the server listen to all interfaces at once (e.g. run(host='0.0.0.0')). The listening port can be changed in a similar way, but you need root or admin rights to choose a port below 1024. Port 80 is the standard for HTTP servers::
Listen to HTTP on all interfaces
if name == 'main':
run(host='0.0.0.0', port=80)
Scaling for Production
The built-in development server is base on wsgiref WSGIServer <https://docs.python.org/library/wsgiref.html#module-wsgiref.simple_server>_, which is a very simple non-threading HTTP server implementation. This is perfectly fine for development, but may become a performance bottleneck when server load increases.
The easiest way to increase performance is to install a multi-threaded server library like cheroot_ or gunicorn_ and tell Bottle to use that instead of the single-threaded wsgiref server::
run(server='cheroot', ...) # Pure Python, runs everywhere
run(server='gunicorn', ...) # High performanceOr using the bottle command line interface:
.. code-block:: sh
python3 -m bottle --server gunicorn [...] mymodule:appFor production deployments gunicorn_ is a really good choice. It comes with its own command line utility that supports a lot more options than bottle. Since :class:Bottle instances are WSGI applications, you can tell gunicorn_ (or any other WSGI server) to load your app instead of calling :func:run yourself:
.. code-block:: sh
gunicorn -w 4 mymodule:appThis will start your application with 4 gunicorn workers and sane default settings. For more details and more complete examples, check out Gunicorn Documentation <https://docs.gunicorn.org/>_.
Server adapters
Bottle ships with a bunch of ready-to-use adapters for the most common WSGI servers so you can try out different server backends easily. You can select a server backend via run(server='NAME') or python3 -m bottle --server NAME. Here is an incomplete list:
======== ============ ======================================================
Name Homepage Description
======== ============ ======================================================
cgi Run as CGI script
flup flup_ Run as FastCGI process
gae gae_ Helper for Google App Engine deployments
wsgiref wsgiref_ Single-threaded default server
cherrypy cherrypy_ Multi-threaded (deprectated))
cheroot cheroot_ Successor of cheerypy
paste paste_ Multi-threaded, stable, tried and tested
waitress waitress_ Multi-threaded, poweres Pyramid
gunicorn gunicorn_ Pre-forked, partly written in C
eventlet eventlet_ Asynchronous framework with WSGI support.
gevent gevent_ Asynchronous (greenlets)
diesel diesel_ Asynchronous (greenlets)
tornado tornado_ Asynchronous, powers some parts of Facebook
twisted twisted_ Asynchronous, well tested but... twisted
meinheld meinheld_ Asynchronous, partly written in C
bjoern bjoern_ Asynchronous, very fast and written in C
auto Automatically selects the first available server adapter
======== ============ ======================================================
Those adapters are very basic and just for convenience, though. If you need more control over your deployment, refer to the server backend documentation and mount your Bottle application just like any other WSGI application.
WSGI Deployment
If there is no adapter for your favorite server or if you need more control over the server setup, you may want to start the server manually. Refer to the server documentation on how to run WSGI applications. Here is an example for cheroot_::
import cheroot.wsgi
import mymodule.app
wsgi_server = cheroot.wsgi.Server(
bind_addr=('0.0.0.0', 80),
wsgi_app=mymodule.app
)
try:
wsgi_server.start()
finally:
wsgi_server.stop()File: docs/development.rst
Developer Notes
This document is intended for developers and package maintainers interested in the bottle development and release workflow. If you want to contribute, you are just right!
Get involved
There are several ways to join the community and stay up to date. Here are some of them:
- Mailing list: Join our mailing list by sending an email to
[email protected] <mailto:[email protected]>_ (no google account required). - IRC: Join
#bottlepy on irc.libera.chat_ or use theweb chat interface <https://web.libera.chat/#bottlepy>_.
Get the Sources
The bottle development repository <https://github.com/bottlepy/bottle>_ and the issue tracker <https://github.com/bottlepy/bottle/issues>_ are both hosted at github <https://github.com/bottlepy/bottle>_. If you plan to contribute, it is a good idea to create an account there and fork the main repository. This way your changes and ideas are visible to other developers and can be discussed openly. Even without an account, you can clone the repository or just download the latest development version as a source archive.
- git:
git clone git://github.com/bottlepy/bottle.git - git/https:
git clone https://github.com/bottlepy/bottle.git - Download: Development branch as
tar archive <https://github.com/bottlepy/bottle/tarball/master>_ orzip file <https://github.com/bottlepy/bottle/zipball/master>_.
Releases and Updates
Bottle is released at irregular intervals and distributed through PyPI <https://pypi.python.org/pypi/bottle>_. Release candidates are only available from the git repository mentioned above. Debian and many other Linux distributions offer packages.
The Bottle version number splits into three parts (major.minor.patch) but does not follow the rules of SemVer <https://semver.org/>_. Instead, you can usually rely on the following rules:
Major Release (x.0)
The major release number is increased on important milestones that change the design of core parts of the framework and break backward compatibility in some fundamental way. You probably have to change parts of your application to use a new major release. These releases are very rare, through.
Minor Release (x.y)
The minor release number is increased whenever APIs or behavior changes in some backwards incompatible way or major features or new APIs are added. You might get some depreciation warnings any may have to tweak some configuration settings to restore the old behavior, but in most cases these changes are designed to be backward compatible for at least one minor release. You should update to stay up do date, but don't have to.
Patches (x.y.z)
The patch number is increased on bug-fixes and other patches that do not change APIs or behaviour. You can safely update without editing your application code. In fact, you really should as soon as possible, because important security fixes are released this way.
Pre-Release Versions
Release candidates are marked by an rc in their revision number. These are API stable most of the time and open for testing, but not officially released yet. You should not use these for production.
Repository Structure
The source repository is structured as follows:
master branch
This is the integration, testing and development branch. All changes that are planned to be part of the next release are merged and tested here.
release-x.y branches
As soon as the master branch is (almost) ready for a new release, it is branched into a new release branch. This "release candidate" is feature-frozen but may receive bug-fixes and last-minute changes until it is considered production ready and officially released. From that point on it is called a "maintenance branch" and still receives bug-fixes, but only important ones. The patch number is increased on each push to these branches, so you can keep up with important changes.
Feature branches
All other branches are feature branches. These are based on the master branch and only live as long as they are still active and not merged back into master.
.. rubric:: What does this mean for a developer?
If you want to add a feature, create a new feature branch from master. If you want to fix a bug, branch off of release-x.y for each affected release. Please use a separate branch for each feature or bug to make integration as easy as possible.
.. rubric:: What does this mean for a maintainer ?
Watch the tags (and the mailing list) for bug-fixes and new releases. If you want to fetch a specific release from the git repository, trust the tags, not the branches. A branch may contain changes that are not released yet, but a tag marks the exact commit which changed the version number.
Submitting Patches
The best way to get your changes integrated into the main development branch is to fork the main repository at github, create a new feature-branch, apply your changes and send a pull-request. Further down this page is a small collection of git workflow examples that may guide you. Submitting git-compatible patches to the mailing list is fine too. In any case, please follow some basic rules:
- Documentation: Tell us what your patch does. Comment your code. If you introduced a new feature, add to the documentation so others can learn about it.
- Test: Write tests to prove that your code works as expected and does not break anything. If you fixed a bug, write at least one test-case that triggers the bug. Make sure that all tests pass before you submit a patch.
- One patch at a time: Only fix one bug or add one feature at a time. Design your patches so that they can be applied as a whole. Keep your patches clean, small and focused.
- Sync with upstream: If the
upstream/masterbranch changed while you were working on your patch, rebase or pull to make sure that your patch still applies without conflicts.