📖 Теория: конспект Flask-блока (Уроки 09–11)
⚡ Краткий конспект
- Проект Community Pulse — Flask-API для анонимных опросов. Стек: Flask + Flask-SQLAlchemy + Alembic + Pydantic.
- HTTP-методы — GET (read), POST (create), PUT (update), DELETE (delete). Идемпотентны: GET, PUT, DELETE.
- RESTful — ресурсы по URL, без состояния, единообразный интерфейс.
- Config-классы — Config / DevelopmentConfig / TestingConfig / ProductionConfig. Применение: app.config.from_object().
- Blueprint — модуль маршрутов. Регистрация: app.register_blueprint(bp, url_prefix='/...').
- Flask-SQLAlchemy — db = SQLAlchemy(); db.init_app(app). Модели наследуют db.Model.
- Pydantic-контракты — QuestionCreate, QuestionResponse(model_config=ConfigDict(from_attributes=True)).
- Миграции — flask db init → flask db migrate → flask db upgrade.
Проект Community Pulse
Community Pulse — интерактивная платформа для анонимных опросов. Пользователи создают вопросы и голосуют «согласен / не согласен».
| Технология | Роль |
|---|---|
| Flask | Веб-сервер, маршрутизация, обработка запросов |
| Flask-SQLAlchemy | ORM, управление сессиями, упрощённая интеграция с Flask |
| Alembic / Flask-Migrate | Версионирование и применение миграций БД |
| Pydantic v2 | Валидация входных данных, сериализация ответов (контракты) |
Структура файлов проекта
/community_pulse/
|-- app/
| |-- __init__.py # Application Factory: create_app()
| |-- routers/
| | |-- __init__.py
| | |-- questions.py # Blueprint: /questions
| | |-- response.py # Blueprint: /responses
| |-- models/
| | |-- __init__.py # db = SQLAlchemy()
| | |-- questions.py # Модели Question, Statistic
| | |-- response.py # Модель Response
| |-- schemas/
| |-- __init__.py
| |-- question.py # Pydantic: QuestionCreate, QuestionResponse
| |-- response.py # Pydantic: ResponseCreate, StatisticResponse
|-- config.py # Config-классы
|-- run.py # Точка входа
HTTP-методы и REST
HTTP-методы — стандартные команды для взаимодействия клиента с сервером.
| Метод | CRUD | Идемпотентен | Описание |
|---|---|---|---|
GET | Read | Да | Получить ресурс или список |
POST | Create | Нет | Создать новый ресурс |
PUT | Update | Да | Обновить ресурс целиком |
PATCH | Update | Да | Частично изменить ресурс |
DELETE | Delete | Да | Удалить ресурс |
HEAD | — | Да | Только заголовки, без тела |
OPTIONS | — | Да | Возможности сервера/ресурса |
Идемпотентность — повторное выполнение той же операции даёт тот же результат, что и однократное. GET /questions всегда вернёт тот же список; DELETE /questions/1 при повторном вызове всё равно означает «вопрос 1 должен быть удалён».
Принципы RESTful API
- Клиент-сервер — разделение ответственности.
- Без сохранения состояния — каждый запрос самодостаточен.
- Кэшируемость — ответы можно кэшировать.
- Единообразие интерфейса — ресурсы по URL, стандартные методы.
- Многоуровневость — клиент не знает, с чем общается (сервер, прокси).
Маршруты Community Pulse
| URL | Методы | Действие |
|---|---|---|
/questions | GET | Список всех вопросов |
/questions | POST | Создать вопрос |
/questions/<id> | GET | Детали вопроса |
/questions/<id> | PUT | Обновить вопрос |
/questions/<id> | DELETE | Удалить вопрос |
/responses | GET | Статистика ответов |
/responses | POST | Добавить ответ |
Конфигурация Flask
Для разных окружений (dev/test/prod) используют иерархию Config-классов:
# config.py
class Config:
DEBUG = False
TESTING = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///example.db'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pass@host/db'
Применение через переменную окружения:
# run.py
import os
from app import create_app
config_map = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
}
config_name = os.environ.get('FLASK_ENV', 'development')
app = create_app(config_map.get(config_name, DevelopmentConfig))
if __name__ == '__main__':
app.run()
Blueprints и Application Factory
Blueprint — способ организовать связанные маршруты в отдельный модуль. Зарегистрированный Blueprint получает свой url_prefix.
# app/routers/questions.py
from flask import Blueprint, request, jsonify
from app.models.questions import Question
from app.models import db
questions_bp = Blueprint('questions', __name__, url_prefix='/questions')
@questions_bp.route('/', methods=['GET'])
def get_questions():
questions = Question.query.all()
return jsonify([{'id': q.id, 'text': q.text} for q in questions]), 200
# app/__init__.py — Application Factory
from flask import Flask
from app.routers.questions import questions_bp
from app.routers.response import response_bp
from app.models import db
from flask_migrate import Migrate
def create_app(config=None):
app = Flask(__name__)
if config:
app.config.from_object(config)
db.init_app(app)
Migrate(app, db)
app.register_blueprint(questions_bp)
app.register_blueprint(response_bp)
return app
Преимущества Blueprints
- Модульность — каждый Blueprint в своём файле.
- Повторное использование — можно подключить к другому приложению.
- Масштабируемость — добавление новых разделов без изменения ядра.
Flask-SQLAlchemy
Flask-SQLAlchemy — расширение, упрощающее интеграцию SQLAlchemy с Flask: автоматическое управление сессиями, конфигурация через app.config.
Настройка (4 шага)
# Шаг 1: установка
# pip install Flask-SQLAlchemy Flask-Migrate
# Шаг 2: инициализация db (app/models/__init__.py)
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# Шаг 3: конфигурация (config.py)
SQLALCHEMY_DATABASE_URI = 'sqlite:///community_pulse.db'
# Шаг 4: подключение в create_app (app/__init__.py)
db.init_app(app)
Модели через db.Model
# app/models/questions.py
from app.models import db
class Question(db.Model):
__tablename__ = 'questions'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(255), nullable=False)
responses = db.relationship('Response', backref='question', lazy=True)
class Statistic(db.Model):
__tablename__ = 'statistics'
question_id = db.Column(db.Integer, db.ForeignKey('questions.id'), primary_key=True)
agree_count = db.Column(db.Integer, nullable=False, default=0)
disagree_count = db.Column(db.Integer, nullable=False, default=0)
Миграции: Flask-Migrate
Flask-Migrate — обёртка над Alembic для использования внутри Flask-приложения.
| Команда | Действие |
|---|---|
flask db init | Инициализировать папку migrations/ |
flask db migrate -m "msg" | Создать файл миграции по diff моделей |
flask db upgrade | Применить миграции к БД |
flask db downgrade | Откатить последнюю миграцию |
Эндпоинты: объект request и статус-коды
Атрибуты объекта request
| Атрибут / Метод | Описание |
|---|---|
request.method | Метод запроса: 'GET', 'POST', … |
request.args | Параметры строки запроса (?key=val) |
request.form | Данные HTML-формы (POST) |
request.get_json() | JSON из тела запроса |
request.data | Сырые байты тела запроса |
request.headers | HTTP-заголовки |
request.files | Загружаемые файлы |
HTTP статус-коды
| Код | Значение | Применение |
|---|---|---|
200 OK | Успех | GET, PUT, DELETE |
201 Created | Ресурс создан | POST |
204 No Content | Успех, без тела | DELETE |
400 Bad Request | Ошибка клиента | Нет обяз. полей, неверный JSON |
404 Not Found | Ресурс не найден | GET/PUT/DELETE несуществующего |
422 Unprocessable | Ошибка валидации | Pydantic ValidationError |
500 Internal Error | Ошибка сервера | Необработанное исключение |
Pydantic-контракты
Контракты — соглашения между бэкендом и фронтендом: какие данные принимает и возвращает API. Pydantic v2 обеспечивает валидацию на входе и сериализацию на выходе.
# app/schemas/question.py (Pydantic v2)
from pydantic import BaseModel, Field, ConfigDict
class QuestionCreate(BaseModel):
text: str = Field(..., min_length=12, description="Текст вопроса")
class QuestionResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
text: str
class MessageResponse(BaseModel):
message: str
# app/schemas/response.py
from pydantic import BaseModel, Field
class ResponseCreate(BaseModel):
question_id: int = Field(..., description="ID вопроса")
is_agree: bool = Field(..., description="Согласие с вопросом")
class StatisticResponse(BaseModel):
question_id: int
agree_count: int
disagree_count: int
Интеграция Pydantic в эндпоинт
# app/routers/questions.py
from pydantic import ValidationError
from app.schemas.question import QuestionCreate, QuestionResponse
@questions_bp.route('/', methods=['POST'])
def create_question():
data = request.get_json()
try:
question_data = QuestionCreate(**data)
except ValidationError as e:
return jsonify(e.errors()), 400
question = Question(text=question_data.text)
db.session.add(question)
db.session.commit()
return jsonify(QuestionResponse.model_validate(question).model_dump()), 201
Преимущества комбинации Pydantic + Flask-SQLAlchemy
- Разделение ответственности — SQLAlchemy управляет БД, Pydantic — данными API.
- Валидация на входе — ошибки выявляются до записи в БД.
- Сериализация на выходе — model_validate(orm_obj) переводит ORM-объект в dict.