Каковы ключевые шаги и best practices при создании проекта на FastAPI

Ответ

Создание проекта на FastAPI следует структурировать вокруг его ключевых возможностей: моделей данных Pydantic, инъекции зависимостей и асинхронности. Ниже приведены основные шаги и лучшие практики.

1. Валидация данных с помощью Pydantic

Всегда определяйте модели для входящих и исходящих данных. Это обеспечивает валидацию, преобразование типов и автоматическую генерацию документации.

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(..., min_length=3, description="Название товара")
    price: float = Field(..., gt=0, description="Цена должна быть положительной")

class ItemResponse(BaseModel):
    id: int
    name: str

2. Структурирование проекта с помощью APIRouter

Для проектов сложнее одного файла используйте APIRouter для разделения эндпоинтов на логические модули. Это улучшает читаемость и поддержку кода.

# /routers/items.py
from fastapi import APIRouter

router = APIRouter(
    prefix="/items",
    tags=["items"],
)

@router.get("/")
async def read_items():
    return [{"name": "Item 1"}]

# /main.py
from fastapi import FastAPI
from .routers import items

app = FastAPI()
app.include_router(items.router)

3. Использование инъекции зависимостей (Depends)

Выносите повторяющуюся логику (например, подключение к БД, аутентификацию пользователя) в функции-зависимости. Это соответствует принципу DRY (Don't Repeat Yourself) и упрощает тестирование.

from fastapi import Depends, FastAPI

async def get_db_session():
    # Логика получения сессии БД
    db = ... 
    try:
        yield db
    finally:
        db.close()

@app.get("/users/me")
async def read_current_user(db: Session = Depends(get_db_session)):
    # работа с db
    return {"user": "current"}

4. Асинхронная работа с базой данных и другими I/O

Используйте асинхронные драйверы для баз данных (asyncpg для PostgreSQL, motor для MongoDB) и HTTP-клиенты (httpx). Это позволяет вашему приложению эффективно обрабатывать большое количество одновременных запросов, не блокируя основной поток выполнения.

5. Управление конфигурацией

Не храните конфигурационные данные (ключи API, данные для подключения к БД) в коде. Используйте переменные окружения и библиотеки, такие как pydantic-settings, для их безопасной загрузки.

6. Централизованная обработка ошибок

Используйте декоратор @app.exception_handler() для создания кастомных обработчиков исключений. Это позволяет возвращать клиенту осмысленные и стандартизированные ответы об ошибках.

Ответ 18+ 🔞

А, слушай, смотри, вот тебе, блядь, как на FastAPI проект делать, чтобы не было потом мучительно больно, как говорится. В рот меня чих-пых, народ же любит всё в одну кучу свалить, а потом охуевает, почему ничего не работает.

Первое, и главное — Pydantic, твоя броня от долбоёбов.
Всё, что приходит и уходит, оборачивай в эти модели. Не просто request.json() и вперёд, а то потом какой-нибудь умник пришлёт цену в виде строки "десять рублей", и вся твоя логика накрывается медным тазом. Смотри, как надо:

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(..., min_length=3, description="Название товара")
    price: float = Field(..., gt=0, description="Цена должна быть положительной")

class ItemResponse(BaseModel):
    id: int
    name: str

Вот видишь? gt=0 — это чтобы какой-нибудь хитрожопый не прислал цену -100, а потом орал, что у него на балансе овердохуища денег. FastAPI сам проверит, отфутболит и в документации красиво покажет. Ёперный театр, красота!

Второе — не лепи всё в один файл main.py на тысячу строк.
Это ж пиздец какой-то получается, найти что-то — всё равно что иголку в стоге сена искать. Дроби на роутеры!

# /routers/items.py
from fastapi import APIRouter

router = APIRouter(
    prefix="/items",
    tags=["items"],  # И в документации всё аккуратно по полочкам
)

@router.get("/")
async def read_items():
    return [{"name": "Item 1"}]

# /main.py
from fastapi import FastAPI
from .routers import items

app = FastAPI()
app.include_router(items.router)

Вот так, блядь. Один файл — товары, другой — пользователи, третий — заказы. Всё логично, всё понятно. А то смотришь на монолит — и волнение ебать, терпения ноль ебать.

Третье — инъекция зависимостей, она же Depends.
Это не какая-то магия, а просто способ не повторять один и тот же код в каждом втором эндпоинте. Подключение к базе, проверка прав — выноси в отдельную функцию.

from fastapi import Depends, FastAPI

async def get_db_session():
    # Логика получения сессии БД
    db = ... 
    try:
        yield db  # Отдал сессию, работай
    finally:
        db.close()  # Всё сделал — прибрал за собой, не как свинья

@app.get("/users/me")
async def read_current_user(db: Session = Depends(get_db_session)):
    # работа с db
    return {"user": "current"}

Смотри, какая прелесть! Хочешь протестировать — подменил get_db_session на заглушку, и всё, ни одна реальная база не пострадала. DRY, блядь, в действии — Don't Repeat Yourself, а не «суши себя», как некоторые думают.

Четвёртое — асинхронность, но без фанатизма.
Если твоё приложение только считает числа в памяти — асинхронность тебе, как собаке пятая нога. Но если есть I/O — база, внешние API, файлы — тогда да, async/await в руки. Только смотри, драйвер базы должен быть асинхронный (asyncpg, motor), а то будет как в том анекдоте: «ждун». Блокирующая операция в асинхронном потоке — и всё, приехали, производительность хуй с горы.

Пятое — конфиги, ёбта, не в коде!
Никаких DATABASE_URL = "postgresql://admin:12345@localhost/mydb" прямо в скрипте! Это же пиздец, какой уровень распиздяйства. Выноси в переменные окружения, используй pydantic-settings. А то выложишь на гитхаб, а там уже тебе на сервер лезут, потому что пароль от базы как на ладони. Сам от себя охуеешь потом.

Шестое — ошибки.
Нельзя, чтобы приложение падало с пятисотой ошибкой, а клиент получал просто Internal Server Error и ни хуя не понял. Обрабатывай исключения централизованно!

from fastapi import Request
from fastapi.responses import JSONResponse

@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    return JSONResponse(
        status_code=400,
        content={"message": f"Некорректные данные, дружок: {exc}"},
    )

Вот так, по-человечески объяснил, в чём проблема. А не как обычно: «ошибка сервера, иди нахуй».

Короче, суть в чём: FastAPI — инструмент мощный, но если им как лопатой махать, можно и по лбу получить. Структурируй, валидируй, разделяй ответственность. И будет тебе счастье, а не техдолг, который потом разгребать — одни ебушки-воробушки.