📖 Теория: конспект 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-SQLAlchemyORM, управление сессиями, упрощённая интеграция с 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ИдемпотентенОписание
GETReadДаПолучить ресурс или список
POSTCreateНетСоздать новый ресурс
PUTUpdateДаОбновить ресурс целиком
PATCHUpdateДаЧастично изменить ресурс
DELETEDeleteДаУдалить ресурс
HEADДаТолько заголовки, без тела
OPTIONSДаВозможности сервера/ресурса

Идемпотентность — повторное выполнение той же операции даёт тот же результат, что и однократное. GET /questions всегда вернёт тот же список; DELETE /questions/1 при повторном вызове всё равно означает «вопрос 1 должен быть удалён».

Принципы RESTful API

  • Клиент-сервер — разделение ответственности.
  • Без сохранения состояния — каждый запрос самодостаточен.
  • Кэшируемость — ответы можно кэшировать.
  • Единообразие интерфейса — ресурсы по URL, стандартные методы.
  • Многоуровневость — клиент не знает, с чем общается (сервер, прокси).

Маршруты Community Pulse

URLМетодыДействие
/questionsGETСписок всех вопросов
/questionsPOSTСоздать вопрос
/questions/<id>GETДетали вопроса
/questions/<id>PUTОбновить вопрос
/questions/<id>DELETEУдалить вопрос
/responsesGETСтатистика ответов
/responsesPOSTДобавить ответ

Конфигурация 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.headersHTTP-заголовки
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.