Bottle Documentation

This document is a work in progress and intended to be a tutorial, howto and an api documentation at the same time. If you have questions not answered here, please check the F.A.Q. or file a ticket at bottles issue tracker.

This documentation describes the features of the 0.6.4 Release and is not updated anymore. The 0.7 docs can be found here

"Hello World" in a Bottle

Lets start with a very basic example: Hello World

1
2
3
4
5
from bottle import route, run
@route('/hello')
def hello():
    return "Hello World!"
run() # This starts the HTTP server

Run this script, visit http://localhost:8080/hello and you will see "Hello World!" in your Browser. So, what happened here?

  1. First we imported some bottle components. The route() decorator and the run() function.
  2. The route() decorator is used do bind a piece of code to an URL. In this example we want to answer requests to the /hello URL.
  3. This function will be called every time someone hits the /hello URL on the web server. It is called a handler function or callback.
  4. The return value of a handler function will be sent back to the Browser.
  5. Now it is time to start the actual HTTP server. The default is a development server running on localhost port 8080 and serving requests until you hit Ctrl-C

Routing

Routes are used to map URLs to callbacks that generate the content for the specific URL. Bottle has a route() decorator to do that. You can add any number of routes to a callback.

1
2
3
4
5
6
7
8
9
from bottle import route
@route('/')
@route('/index.html')
def index():
    return "<a href='/hello'>Go to Hello World page</a>"

@route('/hello')
def hello():
    return "Hello World!"

As you can see, URLs and routes have nothing to do with actual files on the web server. Routes are unique names for your callbacks, nothing more and nothing less. Requests to URLs not matching any routes are answered with a 404 HTTP error. Exceptions within your handler callbacks will cause a 500 error.

Request Methods

The route() decorator has an optional keyword argument method which defaults to method='GET', so only GET requests get answered. Possible values are POST, PUT, DELETE, HEAD or any other HTTP request method you want to listen to.

1
2
3
4
5
6
from bottle import route, request
@route('/form/submit', method='POST')
def form_submit():
    form_data = request.POST
    do_something_with(form_data)
    return "Done"

In this example we used request.POST to access POST form data. This is described here

Dynamic Routes

Static routes are fine, but URLs may carry information as well. Let's add a :name placeholder to our route.

1
2
3
4
from bottle import route
@route('/hello/:name')
def hello(name):
    return "Hello %s!" % name

This dynamic route matches /hello/alice as well as /hello/bob. In fact, the :name part of the route matches everything but a slash (/), so any name is possible. /hello/bob/and/alice or /hellobob won't match.

Each part of the URL covered by a placeholder is provided as a keyword parameter to your handler callback.

Regular Expressions

The default placeholder matches everything up to the next slash. To change that, you can add some regular expression:

1
2
3
4
from bottle import route
@route('/get_object/:id#[0-9]+#')
def get(id):
    return "Object ID: %d" % int(id)

or even use full featured regular expressions with named groups:

1
2
3
4
from bottle import route
@route('/get_object/(?P<id>[0-9]+)')
def get(id):
    return "Object ID: %d" % int(id)

As you can see, URL parameters remain strings, even if they are configured to only match digits. You have to explicitly cast them into the type you need.

The @validate() decorator

Bottle offers a handy decorator called validate() to check and manipulate URL parameters. It takes callables (function or class objects) as keyword arguments and filters every URL parameter through the corresponding callable before they are passed to your request handler.

1
2
3
4
5
6
from bottle import route, validate
# /test/validate/1/2.3/4,5,6,7
@route('/test/validate/:i/:f/:csv')
@validate(i=int, f=float, csv=lambda x: map(int, x.split(',')))
def validate_test(i, f, csv):
    return "Int: %d, Float:%f, List:%s" % (i, f, repr(csv))

You may raise ValueError in your custom callable if a parameter does not validate.

Generating content

TODO

Output Casting

The WSGI specification expects an iterable list of byte strings to be returned from your application and can't handle file objects, unicode, dictionaries or exceptions.

1
2
3
4
from bottle import route
@route('/wsgi')
def wsgi():
    return ['WSGI','wants a','list of','strings']

Bottle automatically tries to convert anything to a WSGI supported type, so you don't have to. The following examples will work with Bottle, but won't work with pure WSGI.

Strings and Unicode

Returning strings (bytes) is not a problem. Unicode however needs to be encoded into a byte stream before the webserver can send it to the client. Ths default encoding is utf-8, so if that fits your needs, you can simply return unicode or unicode iterables.

1
2
3
4
5
6
7
8
from bottle import route, response
@route('/string')
def get_string():
    return 'Bottle converts strings to iterables'

@route('/unicode')
def get_unicode():
    return u'Unicode is encoded with UTF-8 by default'

You can change Bottles default encoding by setting response.content_type to a value containing a charset=... parameter or by changing response.charset directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from bottle import route, response
@route('/iso')
def get_iso():
    response.charset = 'ISO-8859-15'
    return u'This will be sent with ISO-8859-15 encoding.'

@route('/latin9')
def get_latin():
    response.content_type = 'text/html; charset=latin9'
    return u'ISO-8859-15 is also known as latin9.'

In some rare cases the Python encoding names differ from the names supported by the HTTP specification. Then, you have to do both: First set the response.content_type header (which is sent to the client unchanged) and then set the response.charset option (which is used to decode unicode).

File Objects and Streams

Bottle wrapps everything that has a read() method (file objects) with the wsgi.file_wrapper provided by your WSGI server implementation. This wrapper should use highly optimised system calls for your operating system (sendfile on UNIX) to transfer the file.

1
2
3
@route('/file')
def get_file():
    return open('some/file.txt','r')

JSON

Even dictionaries are allowed. They are converted to json and returned with Content-Type header set to application/json. To disable this feature (and pass dicts to your middleware) you can set bottle.default_app().autojson to False.

1
2
3
@route('/api/status')
def api_status():
    return {'status':'online', 'servertime':time.time()}

Static Files

You can directly return file objects, but bottle.send_file() is the recommended way to serve static files. It automatically guesses a mime-type, adds a Last-Modified header, restricts paths to a root directory for security reasons and generates appropriate error pages (401 on permission errors, 404 on missing files). It even supports the If-Modified-Since header and eventually generates a 304 Not modified response. You can pass a custom mimetype to disable mimetype guessing.

1
2
3
4
5
6
7
8
9
from bottle import send_file

@route('/static/:filename')
def static_file(filename):
    send_file(filename, root='/path/to/static/files')

@route('/images/:filename#.*\.png#')
def static_image(filename):
    send_file(filename, root='/path/to/image/files', mimetype='image/png')

HTTP Errors and Redirects

The bottle.abort(code[, message]) function is used to generate HTTP error pages.

1
2
3
4
from bottle import route, redirect, abort
@route('/restricted')
def restricted():
    abort(401, "Sorry, access denied.")

To redirect a client to a different URL, you can send a 307 Temporary Redirect response with the Location header set to the new URL. bottle.redirect(url[, code]) does that for you. You may provide a different HTTP status code as a second parameter.

1
2
3
4
from bottle import route, redirect, abort
@route('/wrong/url')
def wrong():
    redirect("/right/url")

Both functions interrupt your handler code (by throwing a bottle.HTTPError exception) so you don't have to return anything.

All unhandled exceptions other than bottle.HTTPError will result in a 500 Internal Server Error response, so they won't crash your WSGI server.

HTTP Stuff

TODO

Cookies

Bottle stores cookies sent by the client in a dictionary called request.COOKIES. To create new cookies, the method response.set_cookie(name, value[, **params]) is used. It accepts additional parameters as long as they are valid cookie attributes supported by SimpleCookie.

1
2
from bottle import response
response.set_cookie('key','value', path='/', domain='example.com', secure=True, expires=+500, ...)

To set the max-age attribute use the max_age name.

GET and POST values

Query strings and/or POST form submissions are parsed into dictionaries and made available as bottle.request.GET and bottle.request.POST. Multiple values per key are possible, so each each value of these dictionaries may contain a string or a list of strings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<form action="/search" method="post">
  <input type="text" name="query" />
  <input type="submit" />
</form>

#!Python
from bottle import route, request
@route('/search', method='POST')
def do_search():
    query = request.POST.get('query', '').strip()
    if not query:
        return "You didn't supply a search query."
    else:
        return 'You searched for %s.' % query

File Uploads

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Bottle handles file uploads similar to normal POST form data.
Instead of strings or list of strings, you will get file-like objects.

#!html
<form action="/upload" method="post" enctype="multipart/form-data">
  <input name="datafile" type="file" />
</form>

#!Python
from bottle import route, request
@route('/upload', method='POST')
def do_upload():
    datafile = request.POST.get('datafile')
    return datafile.read()

Templates

Bottle uses its own little template engine by default. You can use a template by calling template(template_name, **template_arguments) and returning the result.

1
2
3
@route('/hello/:name')
def hello(name):
    return template('hello_template', username=name)

This will load the template hello_template.tpl with the username variable set to the URL :name part and return the result as a string.

The hello_template.tpl file could look like this:

1
2
<h1>Hello {{username}}</h1>
<p>How are you?</p>

Template search path

The list bottle.TEMPLATE_PATH is used to map template names to actual file names. By default, this list contains ['./%s.tpl', './views/%s.tpl'].

Template caching

Templates are cached in memory after compilation. Modifications made to the template file will have no affect until you clear the template cache. Call bottle.TEMPLATES.clear() to do so.

Template Syntax

The template syntax is a very thin layer around the Python language. It's main purpose is to ensure correct indention of blocks, so you can format your template without worrying about indentions. Here is the complete syntax description:

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
%header = 'Test Template'
%items = [1,2,3,'fly']
%include http_header title=header, use_js=['jquery.js', 'default.js']
<h1>{{header.title()}}</h1>
<ul>
%for item in items:
  <li>
    %if isinstance(item, int):
      Zahl: {{item}}
    %else:
      %try:
        Other type: ({{type(item).__name__}}) {{repr(item)}}
      %except:
        Error: Item has no string representation.
      %end try-block (yes, you may add comments here)
    %end
    </li>
  %end
</ul>
%include http_footer

Key/Value Databases

Warning: The included key/value database is depreciated.

Please switch to a real key value database.

Bottle (>0.4.6) offers a persistent key/value database accessible through the bottle.db module variable. You can use key or attribute syntax to store or fetch any pickle-able object to the database. Both bottle.db.bucket_name.key_name and bottle.db[bucket_name][key_name] will work.

Missing buckets are created on demand. You don't have to check for their existence before using them. Just be sure to use alphanumeric bucket-names.

The bucket objects behave like mappings (dictionaries), except that only strings are allowed for keys and values must be pickle-able. Printing a bucket object doesn't print the keys and values, and the items() and values() methods are not supported. Missing keys will raise KeyError as expected.

Persistence

During a request live-cycle, all changes are cached in thread-local memory. At the end of the request, the changes are saved automatically so the next request will have access to the updated values. Each bucket is stored in a separate file in bottle.DB_PATH. Be sure to allow write-access to this path and use bucket names that are allowed in filenames.

Race conditions

You don't have do worry about file corruption but race conditions are still a problem in multi-threaded or forked environments. You can call bottle.db.save() or botle.db.bucket_name.save() to flush the thread-local memory cache to disk, but there is no way to detect database changes made in other threads until these threads call bottle.db.save() or leave the current request cycle.

Example

1
2
3
4
5
6
7
from bottle import route, db
@route('/db/counter')
def db_counter():
    if 'hits' not in db.counter:
        db.counter.hits = 0
    db['counter']['hits'] += 1
    return "Total hits: %d!" % db.counter.hits

Using WSGI and Middleware

A call to bottle.default_app() returns your WSGI application. After applying as many WSGI middleware modules as you like, you can tell bottle.run() to use your wrapped application, instead of the default one.

1
2
3
4
from bottle import default_app, run
app = default_app()
newapp = YourMiddleware(app)
run(app=newapp)

How default_app() works

Bottle creates a single instance of bottle.Bottle() and uses it as a default for most of the modul-level decorators and the bottle.run() routine. bottle.default_app() returns (or changes) this default. You may, however, create your own instances of bottle.Bottle().

1
2
3
4
5
6
from bottle import Bottle, run
mybottle = Bottle()
@mybottle.route('/')
def index():
  return 'default_app'
run(app=mybottle)

Development

Bottle has two features that may be helpfull during development.

Debug Mode

In debug mode, bottle is much more verbose and tries to help you finding bugs. You should never use debug mode in production environments.

1
2
import bottle
bottle.debug(True)

This does the following:

Auto Reloading

During development, you have to restart the server a lot to test your recent changes. The auto reloader can do this for you. Every time you edit a module file, the reloader restarts the server process and loads the newest version of your code.

1
2
from bottle import run
run(reloader=True)

How it works: The main process will not start a server, but spawn a new child process using the same command line agruments used to start the main process. All module level code is executed at least twice! Be carefull.

The child process will have os.environ['BOTTLE_CHILD'] set to true and start as a normal non-reloading app server. As soon as any of the loaded modules changes, the child process is terminated and respawned by the main process. Changes in template files will not trigger a reload. Please use debug mode to deactivate template caching.

The reloading depends on the ability to stop the child process. If you are running on Windows or any other operating system not supporting signal.SIGINT (which raises KeyboardInterrupt in Python), signal.SIGTERM is used to kill the child. Note that exit handlers and finally clauses, etc., are not executed after a SIGTERM.

Deployment

Bottle uses the build-in wsgiref.SimpleServer by default. This non-threading HTTP server is perfectly fine for development and early production, but may become a performance bottleneck when server load increases.

There are three ways to eliminate this bottleneck:

Multi-Threaded Server

The easiest way to increase performance is to install a multi-threaded and WSGI-capable HTTP server like Paste, flup, cherrypy or fapws3 and use the corresponding bottle server-adapter.

1
2
from bottle import PasteServer, FlupServer, FapwsServer, CherryPyServer
bottle.run(server=PasteServer) # Example

If bottle is missing an adapter for your favorite server or you want to tweak the server settings, you may want to manually set up your HTTP server and use bottle.default_app() to access your WSGI application.

1
2
3
4
def run_custom_paste_server(self, host, port):
    myapp = bottle.default_app()
    from paste import httpserver
    httpserver.serve(myapp, host=host, port=port)

Multiple Server Processes

A single Python process can only utilise one CPU at a time, even if there are more CPU cores available. The trick is to balance the load between multiple independent Python processes to utilise all of your CPU cores.

Instead of a single Bottle application server, you start one instances of your server for each CPU core available using different local port (localhost:8080, 8081, 8082, ...). Then a high performance load balancer acts as a reverse proxy and forwards each new requests to a random Bottle processes, spreading the load between all available backed server instances. This way you can use all of your CPU cores and even spread out the load between different physical servers.

But there are a few drawbacks:

One of the fastest load balancer available is pound but most common web servers have a proxy-module that can do the work just fine.

I'll add examples for lighttpd and Apache web servers soon.

Apache mod_wsgi

Instead of running your own HTTP server from within Bottle, you can attach Bottle applications to an Apache server using mod_wsgi and Bottles WSGI interface.

All you need is an app.wsgi file that provides an application object. This object is used by mod_wsgi to start your application and should be a WSGI conform Python callable.

1
2
3
4
5
6
7
8
9
# File: /var/www/yourapp/app.wsgi

# Change working directory so relative paths (and template lookup) work again
os.chdir(os.path.dirname(__file__))

import bottle
# ... add or import your bottle app code here ...
# Do NOT use bottle.run() with mod_wsgi
application = bottle.default_app()

The Apache configuration may look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess yourapp user=www-data group=www-data processes=1 threads=5
    WSGIScriptAlias / /var/www/yourapp/app.wsgi

    <Directory /var/www/yourapp>
        WSGIProcessGroup yourapp
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

Google AppEngine

I didn't test this myself but several Bottle users reported that this works just fine.

1
2
3
4
5
import bottle
from google.appengine.ext.webapp import util 
# ... add or import your bottle app code here ...
# Do NOT use bottle.run() with AppEngine
util.run_wsgi_app(bottle.default_app())

Good old CGI

CGI is slow as hell, but it works.

1
2
3
import bottle
# ... add or import your bottle app code here ...
bottle.run(server=bottle.CGIServer)
Edit this page at GitHub