Python Bottle Lightweight Web Framework

Written by

in

Looking for a fast, simple, and truly lightweight web framework for Python? Meet Bottle – the micro‑framework that lets you spin up web applications, RESTful APIs, and prototypes in a single file without sacrificing power or flexibility. In this guide we’ll explore what makes Bottle stand out, how to get started, and why it remains a favorite among developers who value minimalism and speed.

What Is Bottle?

Bottle is a single‑file Python web framework designed for building small to medium‑sized web applications. First released in 2009, it follows the microframework philosophy: core functionality is kept tiny, while optional features can be added via plugins. Because the entire framework fits into one .py file, installation and deployment are incredibly straightforward – perfect for scripts, serverless functions, or quick prototypes.

Key Features That Make Bottle a Top Choice

  • Zero dependencies – Bottle runs on the Python standard library, so you don’t need to install external packages unless you want extra features.
  • Single‑file distribution – the whole framework lives in bottle.py, making version control and deployment a breeze.
  • Built‑in routing with support for dynamic URL parameters and HTTP methods.
  • Template engine – a lightweight, Python‑based templating system that can also integrate Jinja2, Mako, or Cheetah.
  • WSGI compliance – works with any WSGI‑compatible server (Gunicorn, uWSGI, Waitress, etc.).
  • Extensible plugin system – add sessions, authentication, database connectors, and more without bloating the core.
  • Easy testing – built‑in support for unit testing with the standard unittest library.

Installing Bottle

Because Bottle has no external dependencies, you can install it with a single pip command, or even just download the bottle.py file directly.

pip install bottle
# or, download the file manually:
curl -O https://raw.githubusercontent.com/bottlepy/bottle/master/bottle.py

Creating Your First Bottle Application

Step‑by‑step example

Below is a minimal “Hello, World!” app that demonstrates Bottle’s clean syntax.

from bottle import route, run

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

# Run the built‑in development server
run(host='localhost', port=8080, debug=True)

Save this as app.py and run python app.py. Visit http://localhost:8080/hello in your browser to see the result.

Dynamic routes and URL parameters

One of Bottle’s strengths is its expressive routing system. Use placeholders inside the URL to capture values directly.

from bottle import route, run

@route('/user/<username>')
def greet_user(username):
    return f"Welcome, {username.title()}!"

run(host='0.0.0.0', port=5000)

Now http://localhost:5000/user/alice will display “Welcome, Alice!”.

Working with Templates

Bottle ships with a simple built‑in template engine that uses Python syntax. For more complex projects, you can plug in Jinja2 or Mako.

Using the built‑in engine

from bottle import route, template, run

@route('/hello/<name>')
def hello(name):
    # The template string is defined inline
    return template('Hello {{name}}!', name=name)

run()

Integrating Jinja2

If you prefer Jinja2’s syntax, install the plugin and use it as follows:

pip install bottle-jinja2
from bottle import route, run, Jinja2Template

# Set the template engine globally
bottle.TEMPLATE_PATH.append('./views')
bottle.default_template_engine = Jinja2Template

@route('/post/<id:int>')
def show_post(id):
    # Renders ./views/post.html
    return template('post.html', post_id=id)

run()

Handling Forms and JSON Requests

Whether you’re building a web form or a JSON API, Bottle provides convenient helpers.

Processing HTML forms

from bottle import post, request, run

@post('/login')
def login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    # Simple validation (never store plain passwords!)
    if username == 'admin' and password == 'secret':
        return "Login successful"
    return "Invalid credentials"

run()

Creating a JSON REST endpoint

import json
from bottle import route, response, run

@route('/api/items', method='GET')
def list_items():
    data = [{'id': 1, 'name': 'Apple'}, {'id': 2, 'name': 'Banana'}]
    response.content_type = 'application/json'
    return json.dumps(data)

run()

Extending Bottle with Plugins

Plugins let you add functionality without modifying the core. Here are three popular choices:

  • bottle‑sqlite – simple SQLite integration for quick data storage.
  • bottle‑cors – enables Cross‑Origin Resource Sharing for API projects.
  • bottle‑auth – provides basic authentication and session handling.

To install a plugin, use pip and then register it in your app:

pip install bottle-sqlite
from bottle import Bottle
from bottle_sqlite import SQLitePlugin

app = Bottle()
app.install(SQLitePlugin(dbfile='mydb.sqlite'))

@app.route('/users')
def users(db):
    return db.execute('SELECT * FROM users').fetchall()

Deploying Bottle in Production

While Bottle’s built‑in server is perfect for development, production environments typically use a more robust WSGI server. Below are common deployment patterns.

Using Gunicorn

# Install gunicorn
pip install gunicorn

# Run the app (assuming your file is app.py and the Bottle instance is called 'app')
gunicorn app:app -w 4 -b 0.0.0.0:8000

Running behind Nginx with uWSGI

# Install uWSGI
pip install uwsgi

# Create a uWSGI config (uwsgi.ini)
[uwsgi]
module = app:app
master = true
processes = 4
socket = /tmp/bottle.sock
chmod-socket = 660
vacuum = true
die-on-term = true

# Start uWSGI
uwsgi --ini uwsgi.ini

# Nginx snippet
server {
    listen 80;
    server_name example.com;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/bottle.sock;
    }
}

Both approaches give you process management, request buffering, and the ability to scale horizontally.

Testing Bottle Applications

Because Bottle is WSGI‑compliant, you can test routes with the standard unittest library or with pytest and webtest.

from bottle import Bottle, route
from webtest import TestApp
import unittest

app = Bottle()

@app.route('/ping')
def ping():
    return "pong"

class BottleTest(unittest.TestCase):
    def setUp(self):
        self.testapp = TestApp(app)

    def test_ping(self):
        resp = self.testapp.get('/ping')
        self.assertEqual(resp.status_int, 200)
        self.assertEqual(resp.body, b'pong')

if __name__ == '__main__':
    unittest.main()

When to Choose Bottle Over Other Frameworks

While Flask, Django, and FastAPI dominate the Python web ecosystem, Bottle shines in specific scenarios:

  • Micro‑services or serverless functions – the single‑file footprint keeps cold‑start times low.
  • Quick prototypes – you can go from idea to a runnable app in minutes.
  • Embedded devices or constrained environments – zero external dependencies reduce memory overhead.
  • Educational projects – the straightforward API makes it ideal for teaching HTTP basics.

If you need a full‑featured ORM, admin interface, or built‑in authentication system, a larger framework may be more appropriate. However, for lightweight APIs, internal tools, or proof‑of‑concepts, Bottle often provides the perfect balance of simplicity and power.

Best Practices for Maintaining Bottle Projects

  1. Separate concerns – keep routes, business logic, and data access in distinct modules even if the framework allows a single file.
  2. Use virtual environments – isolate dependencies, especially when adding plugins.
  3. Leverage plugins wisely – add only what you need to keep the core lightweight.
  4. Write unit tests early – Bottle’s simple request/response model makes testing straightforward.
  5. Log requests and errors – integrate Python’s logging module or a third‑party

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *