Over a million developers have joined DZone.

Bottle: a lightweight Python framework

DZone 's Guide to

Bottle: a lightweight Python framework

· Web Dev Zone ·
Free Resource
Bottle is a fast, lightweight microframework for Python web application. It conforms to WSGI and targets small applications, where set up speed is favored with respect to the possibility of long-term expansions. You wouldn't use Bottle in place of Django, but you would in place of plain old .py files.


Bottle is distributed as a single file and can be installed through PyPI, the global index of Python packages. It comes with no hard dependencies, so installation will take only a minute.

From the command line, the result will be something like this:

[12:44:24][giorgio@Desmond:~]$ sudo easy_install bottle
Searching for bottle
Reading http://pypi.python.org/simple/bottle/
Downloading http://pypi.python.org/packages/source/b/bottle/bottle-0.10.9.tar.gz#md5=e6ec6b37d2e0231d34c9625e106b965f
Installed /usr/local/lib/python2.7/dist-packages/bottle-0.10.9-py2.7.egg

First steps

Bottle runs with a reference implementation of a WSGI server, at least during development. This solution has the advantage of showing all errors in the terminal where you started the application, instead of swallowing them in some server log; however, it if oriented only to development: the bundled server is single threaded and won't handle even a moderate load.

from bottle import route, run

def index(name):
    return '<b>Hello %s!</b>' % name

if __name__ == '__main__':
    run(host='localhost', port=8080)

After executing this script, which will block until you exit with CTRL+C, go to:


which will print, of course:

<b>Hello giorgio!</b>

Let's see something actually useful: a template rendering a form and a corresponding action accessing POST variables.

from bottle import post, get, view, run, request

def sayHello():
    return '<b>Hello %s!</b>' % request.forms.username

def form():
    return dict()

if __name__ == '__main__':
    run(host='localhost', port=8080)

We use @get and @post annotations instead of the generic @route, while @view tells Bottle to load the form_hello.tpl file, allowing the action to return a dictionary containing variables for the template.

The template file should be located at views/form_hello.tpl, and for now it can be just static HTML:

<form action="/say_hello" method="post">
<input type="text" name="username" />
<input type="submit" />

You can add folders to search templates in to the bottle.TEMPLATE_PATH list.

Note that request and response seem global objects, but will resolve to the current request scope in a thread-safe way; it would make no sense to make them global to the application. It's not really as clean an API as passing them as parameters, but at least it's very simple to use if you know what to import.

Here's a bit more dynamic example of templating: it accesses variables listed in the returned dictionary.

def form():
    return dict(username='default')

The actual template:

<input type="text" name="username" value="{{username}}" />

For a bit more dynamic example of routing instead, look at these definitions:


lets you create nice-looking URLs. Wildcards can be used at any level:


For type coercion, needed very often over HTTP,


will convert the id named parameter to an integer. :float does the same.


will valorize file with the full path following /public/, including any slashes; the other wildcards only work with segments. Thus static files are served by importing the bottle.static_file function to define the following idiom:

def server_static_files(filename):
    return static_file(filename, root='/var/www/public')

If you see it in any code sample, the old syntax where wildcards started with : is deprecated.


You will probably grow tired of pressing CTRl+C and restart the script while trying out features in a browser. Add this parameter to run():


You'll see the bundled server shutdown (without returning control) and restart each time you save a file. It's not an heavyweight operation, so by the time you get to refresh a browser window the application will have already been updated.

Deploying in the real world

bottle.default_app() returns a WSGI callable, which you can use with any WSGI server wrapper like mod_wsgi. You substitute this call (along with an assignment to the application variable) to bottle.run().

Use absolute paths or os.chdir() to make sure paths like template folders resolve correctly.


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}