Bottle – микрофреймворк для API

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Bottle: Полное руководство по микрофреймворку Python

Введение в Bottle

Bottle — это минималистичный микрофреймворк на Python для создания веб-приложений и REST API. Весь фреймворк написан в одном файле размером менее 100 КБ, что делает его идеальным решением для небольших проектов, прототипов, встроенных систем и микросервисов. Bottle обеспечивает все необходимые компоненты для запуска полноценного веб-приложения без внешних зависимостей.

Особенности и преимущества Bottle

Легковесность и простота

Bottle не требует внешних зависимостей и может быть установлен одной командой. Фреймворк занимает всего один файл, что упрощает развертывание и понимание кода.

Встроенные возможности

  • Встроенный HTTP-сервер для разработки
  • Собственный шаблонизатор SimpleTemplate Engine
  • Поддержка WSGI для продакшн-развертывания
  • Автоматическое определение типов параметров маршрутов
  • Обработка JSON, форм, файлов и cookie

Гибкость архитектуры

Bottle совместим с любыми Python-библиотеками для работы с базами данных, шаблонизаторами и WSGI-серверами. Это позволяет легко интегрировать его в существующие системы.

Установка и базовая настройка

Установка Bottle

pip install bottle

Создание первого приложения

Создайте файл app.py:

from bottle import route, run

@route('/')
def index():
    return "Привет от Bottle!"

@route('/hello/<name>')
def greet(name):
    return f"Привет, {name}!"

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

Запуск приложения

python app.py

Приложение будет доступно по адресу http://localhost:8080

Маршрутизация и обработка запросов

Базовые маршруты

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

@route('/')
def home():
    return "Главная страница"

@get('/users')
def get_users():
    return "Список пользователей"

@post('/users')
def create_user():
    return "Создание пользователя"

@put('/users/<id:int>')
def update_user(id):
    return f"Обновление пользователя {id}"

@delete('/users/<id:int>')
def delete_user(id):
    return f"Удаление пользователя {id}"

Параметры маршрутов

Bottle поддерживает различные типы параметров:

@route('/user/<id:int>')
def user_by_id(id):
    return f"Пользователь ID: {id}"

@route('/article/<slug:path>')
def article_by_slug(slug):
    return f"Статья: {slug}"

@route('/price/<amount:float>')
def price(amount):
    return f"Цена: {amount}"

@route('/profile/<username:re:[a-zA-Z0-9_]+>')
def profile(username):
    return f"Профиль: {username}"

Обработка query-параметров

from bottle import request

@route('/search')
def search():
    query = request.query.get('q', '')
    category = request.query.get('category', 'all')
    return f"Поиск '{query}' в категории '{category}'"

Работа с данными запроса

Обработка форм

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"Получены данные: {name}, {email}, {age}"

Работа с JSON

from bottle import request, response

@post('/api/data')
def handle_json():
    data = request.json
    
    # Обработка данных
    result = {
        'status': 'success',
        'received': data,
        'message': 'Данные обработаны успешно'
    }
    
    response.content_type = 'application/json'
    return result

Загрузка файлов

import os
from bottle import request, post

@post('/upload')
def upload_file():
    upload = request.files.get('file')
    
    if upload:
        # Проверка расширения файла
        name, ext = os.path.splitext(upload.filename)
        if ext not in ['.jpg', '.png', '.gif']:
            return "Неподдерживаемый формат файла"
        
        # Сохранение файла
        upload.save('./uploads/')
        return f"Файл {upload.filename} успешно загружен"
    
    return "Файл не выбран"

Шаблонизация и отображение данных

Встроенный шаблонизатор

Создайте файл templates/user.tpl:

<!DOCTYPE html>
<html>
<head>
    <title>Профиль пользователя</title>
</head>
<body>
    <h1>Профиль {{name}}</h1>
    <p>Email: {{email}}</p>
    <p>Возраст: {{age}}</p>
    
    % if posts:
        <h2>Последние посты:</h2>
        <ul>
        % for post in posts:
            <li>{{post['title']}}</li>
        % end
        </ul>
    % else:
        <p>Постов нет</p>
    % end
</body>
</html>

Использование в коде:

from bottle import template

@route('/user/<id:int>')
def user_profile(id):
    user_data = {
        'name': 'Иван Иванов',
        'email': 'ivan@example.com',
        'age': 30,
        'posts': [
            {'title': 'Первый пост'},
            {'title': 'Второй пост'}
        ]
    }
    
    return template('user', **user_data)

Интеграция с Jinja2

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

# Подключение Jinja2
install(jinja2_plugin)

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

Работа с cookie и сессиями

Управление cookie

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 "Вход выполнен"

@route('/profile')
def profile():
    user_id = request.get_cookie('user_id')
    if user_id:
        return f"Добро пожаловать, пользователь {user_id}"
    else:
        return "Необходимо войти в систему"

@route('/logout')
def logout():
    response.delete_cookie('user_id')
    response.delete_cookie('session')
    return "Выход выполнен"

Простые сессии

import uuid
from bottle import request, response

# Простое хранилище сессий
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')
    
    # Проверка учетных данных
    if username == 'admin' and password == 'secret':
        create_session({'username': username, 'logged_in': True})
        return "Вход выполнен"
    else:
        return "Неверные учетные данные"

Обработка ошибок и исключений

Стандартные обработчики ошибок

from bottle import error, abort

@error(404)
def error404(error):
    return "Страница не найдена. Проверьте URL."

@error(500)
def error500(error):
    return "Внутренняя ошибка сервера. Попробуйте позже."

@route('/protected')
def protected():
    user = request.get_cookie('user')
    if not user:
        abort(401, "Доступ запрещен")
    return "Защищенная страница"

Пользовательские исключения

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 обязателен")
        
        # Валидация email
        if '@' not in data['email']:
            raise ValidationError("Некорректный email")
        
        return {"status": "success", "message": "Данные валидны"}
    
    except ValidationError as e:
        response.status = 400
        return {"status": "error", "message": str(e)}

Интеграция с базами данных

Подключение 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': 'Пользователь создан'}

Использование 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}

Статические файлы и медиа

Настройка статических файлов

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/
├── 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 с Bottle

Создание полноценного API

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

# Временное хранилище данных
users = [
    {'id': 1, 'name': 'Иван', 'email': 'ivan@example.com'},
    {'id': 2, 'name': 'Петр', 'email': 'petr@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': 'Пользователь не найден'})

@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': 'Пользователь не найден'})
    
    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 и плагины

Создание 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, "Токен доступа отсутствует")
        
        # Проверка токена
        if not is_valid_token(token[7:]):
            abort(401, "Недействительный токен")
        
        return f(*args, **kwargs)
    return decorated_function

def is_valid_token(token):
    # Простая проверка токена
    return token == "valid_token_123"

@route('/api/protected')
@auth_required
def protected_endpoint():
    cors_middleware()
    return {'message': 'Доступ разрешен'}

Логирование запросов

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}")

Развертывание приложений

Использование Gunicorn

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

Конфигурация для Docker

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

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;
    }
}

Таблица основных методов и функций Bottle

Метод/Функция Описание Пример использования
@route(path, method='GET') Декоратор для создания маршрутов @route('/users/<id:int>')
@get(path) Декоратор для GET-запросов @get('/users')
@post(path) Декоратор для POST-запросов @post('/users')
@put(path) Декоратор для PUT-запросов @put('/users/<id:int>')
@delete(path) Декоратор для DELETE-запросов @delete('/users/<id:int>')
run(host, port, debug) Запуск встроенного сервера run(host='localhost', port=8080)
request.query Доступ к query-параметрам request.query.get('q')
request.forms Доступ к данным форм request.forms.get('name')
request.json Доступ к JSON-данным request.json.get('key')
request.files Доступ к загруженным файлам request.files.get('upload')
request.headers Доступ к заголовкам запроса request.headers.get('Authorization')
request.get_cookie(name) Получение cookie request.get_cookie('session_id')
response.status Установка статуса ответа response.status = 404
response.content_type Установка типа контента response.content_type = 'application/json'
response.set_cookie(name, value) Установка cookie response.set_cookie('user', 'admin')
response.delete_cookie(name) Удаление cookie response.delete_cookie('session')
template(name, **kwargs) Рендеринг шаблона template('index', name='user')
static_file(filename, root) Отдача статических файлов static_file('style.css', root='./static')
redirect(url) Перенаправление redirect('/login')
abort(code, message) Прерывание с ошибкой abort(401, 'Доступ запрещен')
@error(code) Обработчик ошибок @error(404)
@hook('before_request') Хук перед запросом @hook('before_request')
@hook('after_request') Хук после запроса @hook('after_request')
install(plugin) Установка плагина install(cors_plugin)

Часто задаваемые вопросы

Что такое Bottle и для чего он используется?

Bottle — это минималистичный веб-фреймворк на Python для создания REST API, небольших веб-приложений и прототипов. Он идеально подходит для быстрой разработки и не требует внешних зависимостей.

Можно ли использовать Bottle для больших проектов?

Bottle лучше всего подходит для небольших и средних проектов. Для крупных приложений рекомендуется использовать более мощные фреймворки, такие как Django или FastAPI.

Как подключить внешние шаблонизаторы?

Bottle поддерживает интеграцию с популярными шаблонизаторами, такими как Jinja2, Mako и Cheetah. Необходимо установить соответствующий плагин.

Поддерживает ли Bottle аутентификацию?

Bottle не имеет встроенной системы аутентификации, но вы можете легко реализовать её самостоятельно, используя cookie, сессии или JWT-токены.

Можно ли использовать Bottle с базами данных?

Да, Bottle совместим с любыми Python-библиотеками для работы с базами данных, включая SQLite, PostgreSQL, MySQL и ORM, такие как SQLAlchemy.

Как развернуть Bottle-приложение на продакшн?

Для продакшн-развертывания рекомендуется использовать WSGI-серверы, такие как Gunicorn, uWSGI или Waitress, в сочетании с веб-сервером Nginx.

Поддерживает ли Bottle WebSocket?

Bottle не имеет встроенной поддержки WebSocket, но можно использовать специальные плагины или интегрировать с библиотеками, такими как eventlet или gevent.

Заключение

Bottle представляет собой отличный выбор для разработчиков, которым нужен простой, быстрый и легковесный веб-фреймворк. Его минималистичная архитектура и отсутствие внешних зависимостей делают его идеальным для прототипирования, создания микросервисов и небольших веб-приложений. Благодаря хорошей документации и активному сообществу, Bottle остается популярным инструментом для быстрой веб-разработки на Python.

Новости