I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 636 posts at DZone. You can read more from them at their website. View Full User Profile

Bottle: a lightweight Python framework

02.14.2012
| 9637 views |
  • submit to reddit
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.

Installation

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

@route('/hello/<name>')
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:

http://localhost:8080/hello/giorgio

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

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

@get('/hello')
@view('form_hello')
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" />
</form>

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.

@get('/hello')
@view('form_hello')
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:

@route('/forums/<id>')

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

@route('/<action>/<object>')

For type coercion, needed very often over HTTP,

@route('/topic/<id:int>')

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

@route('/public/<file:path>')

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:

@route('/public/<filename>')
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.

Reloading

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():

run(reloader=True)

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.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)