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.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов