Bottle - microphramwork for API

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Bottle: Complete Guide to the Python Microframework

Introduction to Bottle

Bottle is a minimalist Python microframework for building web applications and REST APIs. The entire framework is written in a single file under 100 KB, making it an ideal solution for small projects, prototypes, embedded systems, and micro‑services. Bottle provides all essential components to run a full‑featured web application without external dependencies.

Features and Advantages of Bottle

Lightweight and Simple

Bottle requires no external dependencies and can be installed with a single command. The framework consists of just one file, which simplifies deployment and code comprehension.

Built‑in Capabilities

  • Built‑in development HTTP server
  • SimpleTemplate Engine templating system
  • WSGI support for production deployment
  • Automatic route parameter type detection
  • Handling of JSON, forms, file uploads, and cookies

Architectural Flexibility

Bottle works with any Python libraries for databases, templating engines, and WSGI servers, allowing easy integration into existing systems.

Installation and Basic Setup

Installing Bottle

pip install bottle

Creating Your First Application

Create a file app.py:

from bottle import route, run

@route('/')
def index():
    return "Hello from Bottle!"

@route('/hello/<name>')
def greet(name):
    return f"Hello, {name}!"

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

Running the Application

python app.py

The application will be accessible at http://localhost:8080

Routing and Request Handling

Basic Routes

from bottle import route, get, post, put, delete

@route('/')
def home():
    return "Home page"

@get('/users')
def get_users():
    return "User list"

@post('/users')
def create_user():
    return "Create user"

@put('/users/<id:int>')
def update_user(id):
    return f"Update user {id}"

@delete('/users/<id:int>')
def delete_user(id):
    return f"Delete user {id}"

Route Parameters

Bottle supports various parameter types:

@route('/user/<id:int>')
def user_by_id(id):
    return f"User ID: {id}"

@route('/article/<slug:path>')
def article_by_slug(slug):
    return f"Article: {slug}"

@route('/price/<amount:float>')
def price(amount):
    return f"Price: {amount}"

@route('/profile/<username:re:[a-zA-Z0-9_]+>')
def profile(username):
    return f"Profile: {username}"

Query Parameter Handling

from bottle import request

@route('/search')
def search():
    query = request.query.get('q', '')
    category = request.query.get('category', 'all')
    return f"Search '{query}' in category '{category}'"

Working with Request Data

Form Processing

from bottle import request, post

@post('/submit')
def submit_form():
    name = request.forms.get('name')
    email = request.forms.get('email')
    age = request.forms.get('age', type=int)
    
    return f"Received data: {name}, {email}, {age}"

JSON Handling

from bottle import request, response

@post('/api/data')
def handle_json():
    data = request.json
    
    # Process data
    result = {
        'status': 'success',
        'received': data,
        'message': 'Data processed successfully'
    }
    
    response.content_type = 'application/json'
    return result

File Uploads

import os
from bottle import request, post

@post('/upload')
def upload_file():
    upload = request.files.get('file')
    
    if upload:
        # Validate file extension
        name, ext = os.path.splitext(upload.filename)
        if ext not in ['.jpg', '.png', '.gif']:
            return "Unsupported file format"
        
        # Save file
        upload.save('./uploads/')
        return f"File {upload.filename} uploaded successfully"
    
    return "No file selected"

Templating and Data Rendering

Built‑in Templating Engine

Create a file templates/user.tpl:

<!DOCTYPE html>
<html>
<head>
    <title>User Profile</title>
</head>
<body>
    <h1>Profile {{name}}</h1>
    <p>Email: {{email}}</p>
    <p>Age: {{age}}</p>
    
    % if posts:
        <h2>Recent Posts:</h2>
        <ul>
        % for post in posts:
            <li>{{post['title']}}</li>
        % end
        </ul>
    % else:
        <p>No posts</p>
    % end
</body>
</html>

Usage in code:

from bottle import template

@route('/user/<id:int>')
def user_profile(id):
    user_data = {
        'name': 'John Doe',
        'email': 'john@example.com',
        'age': 30,
        'posts': [
            {'title': 'First post'},
            {'title': 'Second post'}
        ]
    }
    
    return template('user', **user_data)

Integration with Jinja2

from bottle import route, run, install
from bottle import jinja2_plugin

# Install Jinja2
install(jinja2_plugin)

@route('/hello/<name>')
def hello(name):
    return {'name': name}

Cookies and Sessions

Cookie Management

from bottle import response, request

@route('/login')
def login():
    response.set_cookie('user_id', '123', max_age=3600)
    response.set_cookie('session', 'abc123', httponly=True, secure=True)
    return "Logged in"

@route('/profile')
def profile():
    user_id = request.get_cookie('user_id')
    if user_id:
        return f"Welcome, user {user_id}"
    else:
        return "Please log in"

@route('/logout')
def logout():
    response.delete_cookie('user_id')
    response.delete_cookie('session')
    return "Logged out"

Simple Sessions

import uuid
from bottle import request, response

# Simple in‑memory session store
sessions = {}

def get_session():
    session_id = request.get_cookie('session_id')
    if session_id and session_id in sessions:
        return sessions[session_id]
    return None

def create_session(data):
    session_id = str(uuid.uuid4())
    sessions[session_id] = data
    response.set_cookie('session_id', session_id)
    return session_id

@route('/login', method='POST')
def login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    
    # Credential check
    if username == 'admin' and password == 'secret':
        create_session({'username': username, 'logged_in': True})
        return "Logged in"
    else:
        return "Invalid credentials"

Error and Exception Handling

Standard Error Handlers

from bottle import error, abort

@error(404)
def error404(error):
    return "Page not found. Check the URL."

@error(500)
def error500(error):
    return "Internal server error. Please try again later."

@route('/protected')
def protected():
    user = request.get_cookie('user')
    if not user:
        abort(401, "Access denied")
    return "Protected page"

Custom Exceptions

class ValidationError(Exception):
    pass

@route('/api/validate', method='POST')
def validate_data():
    try:
        data = request.json
        if not data or 'email' not in data:
            raise ValidationError("Email is required")
        
        # Validate email format
        if '@' not in data['email']:
            raise ValidationError("Invalid email address")
        
        return {"status": "success", "message": "Data is valid"}
    
    except ValidationError as e:
        response.status = 400
        return {"status": "error", "message": str(e)}

Database Integration

Connecting SQLite

import sqlite3
from bottle import g

def get_db():
    if not hasattr(g, 'db'):
        g.db = sqlite3.connect('app.db')
        g.db.row_factory = sqlite3.Row
    return g.db

@route('/users')
def get_users():
    db = get_db()
    cursor = db.cursor()
    cursor.execute('SELECT * FROM users')
    users = [dict(row) for row in cursor.fetchall()]
    return {'users': users}

@post('/users')
def create_user():
    db = get_db()
    cursor = db.cursor()
    
    name = request.forms.get('name')
    email = request.forms.get('email')
    
    cursor.execute(
        'INSERT INTO users (name, email) VALUES (?, ?)',
        (name, email)
    )
    db.commit()
    
    return {'status': 'success', 'message': 'User created'}

Using an ORM (SQLAlchemy)

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine('sqlite:///app.db')
Session = sessionmaker(bind=engine)

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    email = Column(String(100))

Base.metadata.create_all(engine)

@route('/users')
def get_users():
    session = Session()
    users = session.query(User).all()
    result = [{'id': u.id, 'name': u.name, 'email': u.email} for u in users]
    session.close()
    return {'users': result}

Static Files and Media

Serving Static Files

from bottle import static_file

@route('/static/<filename:path>')
def static_files(filename):
    return static_file(filename, root='./static')

@route('/download/<filename>')
def download_file(filename):
    return static_file(filename, root='./downloads', download=filename)

@route('/image/<filename:re:.*\.(jpg|png|gif)>')
def serve_image(filename):
    return static_file(filename, root='./images')

Project Structure

project/
├── app.py
├── templates/
│   ├── base.tpl
│   ├── index.tpl
│   └── user.tpl
├── static/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── app.js
│   └── images/
│       └── logo.png
├── uploads/
├── downloads/
└── database.db

REST API with Bottle

Building a Full‑Featured API

from bottle import route, get, post, put, delete, request, response
import json

# In‑memory data store
users = [
    {'id': 1, 'name': 'Ivan', 'email': 'ivan@example.com'},
    {'id': 2, 'name': 'Peter', 'email': 'peter@example.com'}
]

def json_response(data):
    response.content_type = 'application/json'
    return json.dumps(data, ensure_ascii=False)

@get('/api/users')
def get_all_users():
    return json_response({'users': users})

@get('/api/users/<id:int>')
def get_user(id):
    user = next((u for u in users if u['id'] == id), None)
    if user:
        return json_response(user)
    else:
        response.status = 404
        return json_response({'error': 'User not found'})

@post('/api/users')
def create_user():
    data = request.json
    new_id = max(u['id'] for u in users) + 1 if users else 1
    
    new_user = {
        'id': new_id,
        'name': data.get('name'),
        'email': data.get('email')
    }
    
    users.append(new_user)
    response.status = 201
    return json_response(new_user)

@put('/api/users/<id:int>')
def update_user(id):
    user = next((u for u in users if u['id'] == id), None)
    if not user:
        response.status = 404
        return json_response({'error': 'User not found'})
    
    data = request.json
    user.update({
        'name': data.get('name', user['name']),
        'email': data.get('email', user['email'])
    })
    
    return json_response(user)

@delete('/api/users/<id:int>')
def delete_user(id):
    global users
    users = [u for u in users if u['id'] != id]
    response.status = 204
    return ""

Middleware and Plugins

Creating Middleware

from functools import wraps
from bottle import request, response, abort

def cors_middleware():
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'

def auth_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        token = request.headers.get('Authorization')
        if not token or not token.startswith('Bearer '):
            abort(401, "Access token missing")
        
        # Validate token
        if not is_valid_token(token[7:]):
            abort(401, "Invalid token")
        
        return f(*args, **kwargs)
    return decorated_function

def is_valid_token(token):
    # Simple token check
    return token == "valid_token_123"

@route('/api/protected')
@auth_required
def protected_endpoint():
    cors_middleware()
    return {'message': 'Access granted'}

Request Logging

import logging
from bottle import hook

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@hook('before_request')
def log_request():
    logger.info(f"{request.method} {request.path} - {request.environ.get('REMOTE_ADDR')}")

@hook('after_request')
def log_response():
    logger.info(f"Response: {response.status}")

Deploying Applications

Using Gunicorn

pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app

Docker Configuration

Dockerfile:

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]

requirements.txt:

bottle
gunicorn

Nginx Configuration

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Key Bottle Methods and Functions

Method/Function Description Example Usage
@route(path, method='GET') Decorator for defining routes @route('/users/<id:int>')
@get(path) Decorator for GET requests @get('/users')
@post(path) Decorator for POST requests @post('/users')
@put(path) Decorator for PUT requests @put('/users/<id:int>')
@delete(path) Decorator for DELETE requests @delete('/users/<id:int>')
run(host, port, debug) Starts the built‑in server run(host='localhost', port=8080)
request.query Access query parameters request.query.get('q')
request.forms Access form data request.forms.get('name')
request.json Access JSON payload request.json.get('key')
request.files Access uploaded files request.files.get('upload')
request.headers Access request headers request.headers.get('Authorization')
request.get_cookie(name) Retrieve a cookie request.get_cookie('session_id')
response.status Set response status code response.status = 404
response.content_type Set response content type response.content_type = 'application/json'
response.set_cookie(name, value) Set a cookie response.set_cookie('user', 'admin')
response.delete_cookie(name) Delete a cookie response.delete_cookie('session')
template(name, **kwargs) Render a template template('index', name='user')
static_file(filename, root) Serve static files static_file('style.css', root='./static')
redirect(url) Redirect to another URL redirect('/login')
abort(code, message) Abort with an error abort(401, 'Access denied')
@error(code) Error handler decorator @error(404)
@hook('before_request') Hook executed before each request @hook('before_request')
@hook('after_request') Hook executed after each request @hook('after_request')
install(plugin) Install a plugin install(cors_plugin)

Frequently Asked Questions

What is Bottle and what is it used for?

Bottle is a minimalist Python web framework for building REST APIs, small web applications, and prototypes. It is perfect for rapid development and requires no external dependencies.

Can Bottle be used for large projects?

Bottle is best suited for small to medium‑sized projects. For large‑scale applications, more feature‑rich frameworks like Django or FastAPI are recommended.

How can I integrate external templating engines?

Bottle supports integration with popular templating engines such as Jinja2, Mako, and Cheetah via appropriate plugins.

Does Bottle provide authentication out of the box?

Bottle does not include a built‑in authentication system, but you can easily implement one yourself using cookies, sessions, or JWT tokens.

Can Bottle work with databases?

Yes, Bottle is compatible with any Python database library, including SQLite, PostgreSQL, MySQL, and ORMs like SQLAlchemy.

How do I deploy a Bottle application to production?

For production, it is recommended to use WSGI servers such as Gunicorn, uWSGI, or Waitress together with a reverse proxy like Nginx.

Does Bottle support WebSocket?

Bottle does not have native WebSocket support, but you can use specialized plugins or integrate with libraries like eventlet or gevent.

Conclusion

Bottle is an excellent choice for developers who need a simple, fast, and lightweight web framework. Its minimalist architecture and lack of external dependencies make it ideal for prototyping, micro‑service creation, and small‑scale web applications. With solid documentation and an active community, Bottle remains a popular tool for rapid Python web development.

$

News