Форум программистов, компьютерный форум, киберфорум
IndentationError
Войти
Регистрация
Восстановить пароль

API на базе FastAPI с Python за пару минут

Запись от IndentationError размещена 07.07.2025 в 10:16
Показов 6097 Комментарии 0

Нажмите на изображение для увеличения
Название: API на базе FastAPI с Python за пару минут.jpg
Просмотров: 403
Размер:	227.7 Кб
ID:	10957
FastAPI - это относительно молодой фреймворк для создания веб-API, который за короткое время заработал бешеную популярность в Python-сообществе. И не зря. Я помню, как впервые запустил приложение на FastAPI и мои коллеги не поверили, что весь бэкенд был написан всего за пару часов.

FastAPI выделяется сверхвысокой производительностью - он работает на базе Starlette и Pydantic, что делает его одним из самых быстрых Python-фреймворков. На тестах он обходит даже Node.js и Go по некоторым метрикам! Но скорость - лишь часть истории. Когда я сравнивал FastAPI с Flask, с которым работал раньше, меня поразила встроенная поддержка асинхронного программирования. В Flask для этого нужны дополнительные библиотеки и нетривиальные настройки, а тут все работает из коробки. А по сравнению с Django, FastAPI не таскает за собой тонны лишнего функционала, когда вам нужен просто легкий API. Самой восхитительной особенностью, по моему опыту, стала автоматическая генерация документации. Вы пишете код, а FastAPI сам создает интерактивный Swagger UI и ReDoc. Больше никаких отдельных документов, которые устаревают на следущий день после написания!

Встроенная валидация данных через Pydantic - еще один козырь в рукаве. Я перестал писать десятки строк кода для проверки входных данных. Просто определяете модель с типами полей, и фреймворк сам валидирует запросы и выдает понятные ошибки.

Недостатки? Ну, если вам нужен монолитный сайт с админкой, шаблонами и всем прочим - Django по-прежнему король. Если вы предпочитаете максимальную гибкость и простоту - Flask останется вашим выбором. Но для большинства современных API, особенно если вы работаете с микросервисами, FastAPI - это именно то, что доктор прописал.

Практическая часть: Создаем минимальный API



Хватит теории! Давайте напишем реальный код и посмотрим, как FastAPI работает на практике. Для начала нам нужно настроить окружение. Я всегда рекомендую использовать виртуальное окружение, чтобы избежать конфликтов между пакетами разных проектов. Начнем с создания папки для проекта и активации виртуального окружения:

Bash
1
2
3
4
mkdir fastapi_app
cd fastapi_app
python -m venv venv
source venv/bin/activate  # На Windows: venv\Scripts\activate
Теперь установим необходимые пакеты - нам понадобятся сам FastAPI и сервер Uvicorn для запуска приложения:

Bash
1
pip install fastapi uvicorn
Uvicorn - это ASGI-сервер, который необходим для запуска асинхронных приложений. Он намного быстрее чем традиционные WSGI-серверы, которые использует Flask.

Что касается IDE, я обычно использую VS Code или PyCharm. Для VS Code рекомендую установить расширения Python, Pylance и даже есть специальное расширение FastAPI Snippets, которое добавляет полезные шаблоны кода. Если вы, как и я, забываете синтаксис - такие сниппеты неоценимы.

Создадим базовую структуру проекта:

Python
1
2
3
fastapi_app/
├── venv/                # Виртуальное окружение
├── main.py              # Главный файл приложения
Теперь откроем main.py и напишем наш первый FastAPI-эндпоинт:

Python
1
2
3
4
5
6
7
from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/")
def read_root():
    return {"message": "Привет, FastAPI!"}
Этот код создает экземпляр приложения FastAPI и определяет маршрут для корневого URL (/), который возвращает JSON с приветствием. Обратите внимание на декоратор @app.get("/") - это говорит FastAPI, что функция read_root() должна обрабатывать GET-запросы к корневому пути.

Запустим наше приложение:

Bash
1
uvicorn main:app --reload
Здесь main - это имя файла (main.py), app - экземпляр FastAPI, а флаг --reload позволяет серверу автоматически перезагружаться при изменении кода, что очень удобно во время разработки. После запуска, перейдите по адресу http://127.0.0.1:8000 в браузере, и вы увидите JSON-ответ: {"message": "Привет, FastAPI!"}. Не впечатляет? Подождите, это только начало!

Давайте создадим более полезный API для управления списком продуктов. Допустим, мы хотим иметь возможность добавлять продукты и получать их по ID.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi import FastAPI, HTTPException
 
app = FastAPI()
 
# Имитация базы данных
products = []
 
@app.post("/products/")
def create_product(name: str, price: float):
    product = {"id": len(products), "name": name, "price": price}
    products.append(product)
    return product
 
@app.get("/products/{product_id}")
def read_product(product_id: int):
    if product_id < 0 or product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return products[product_id]
В этом примере я добавил два эндпоинта:
1. POST /products/ - принимает имя и цену продукта через параметры запроса и добавляет его в список.
2. GET /products/{product_id} - возвращает продукт по его ID.

Обратите внимание, как просто я определил типы для параметров функций. FastAPI автоматически валидирует их и преобразует в нужный тип. Если кто-то отправит product_id="не_число", FastAPI сам вернет ошибку с понятным сообщением.

Для проверки API вы можете использовать curl или любой другой HTTP-клиент:

Bash
1
2
3
4
5
# Добавляем продукт
curl -X POST "http://127.0.0.1:8000/products/?name=Яблоко&price=1.5"
 
# Получаем продукт по ID
curl "http://127.0.0.1:8000/products/0"
Но есть способ проще. Одна из фишек FastAPI - автоматическая генерация интерактивной документации. Перейдите по адресу http://127.0.0.1:8000/docs, и вы увидите Swagger UI с описанием всех эндпоинтов. Вы можете тестировать их прямо из браузера! Также доступна альтернативная документация ReDoc по адресу http://127.0.0.1:8000/redoc - она менее интерактивна, но более наглядна и удобна для чтения.

Теперь давайте использовать Pydantic для валидации данных. В предыдущем примере мы просто принимали параметры запроса. Но часто нужно обрабатывать более сложные данные, отправленные в теле запроса.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
 
class Product(BaseModel):
    name: str
    price: float
    description: str = None  # Опциональное поле
 
app = FastAPI()
 
products = []
 
@app.post("/products/")
def create_product(product: Product):
    new_product = product.dict()
    new_product["id"] = len(products)
    products.append(new_product)
    return new_product
 
@app.get("/products/{product_id}")
def read_product(product_id: int):
    if product_id < 0 or product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return products[product_id]
Я создал класс Product, унаследованный от BaseModel. Это Pydantic-модель, которая определяет структуру данных продукта и их типы. Поле description опциональное, поэтому я установил для него значение по умолчанию None. Теперь FastAPI будет ожидать, что запрос к /products/ будет содержать JSON в теле с полями name, price и, возможно, description. Если какие-то поля отсутствуют или имеют неверный тип, пользователь получит ошибку с детальным описанием проблемы. Чтобы протестировать это, отправьте POST-запрос с JSON-телом:

Bash
1
2
3
curl -X POST "http://127.0.0.1:8000/products/" \
     -H "Content-Type: application/json" \
     -d '{"name": "Груша", "price": 2.0, "description": "Сочная груша"}'
А теперь проверте автоматически сгенерированную документацию - вы увидите, что Swagger UI теперь показывает структуру ожидаемого JSON и даже позволяет заполнить форму для тестирования. Мне потребовалось всего несколько строк кода, чтобы создать API с валидацией данных и автоматической документацией. Я не могу передать, насколько это упрощает разработку по сравнению с написанием всей этой логики вручную.
Теперь попробуем улучшить наш API, добавив больше функциональности и гибкости. В реальных проектах часто требуется получать список элементов с фильтрацией и сортировкой. Давайте реализуем это:

Python
1
2
3
4
5
6
7
8
9
10
@app.get("/products/")
def list_products(skip: int = 0, limit: int = 10, min_price: float = None):
    result = products
    
    # Фильтрация по минимальной цене
    if min_price is not None:
        result = [p for p in result if p["price"] >= min_price]
    
    # Пагинация
    return result[skip : skip + limit]
В этом примере я добавил эндпоинт для получения списка продуктов с возможностью пагинации (skip и limit) и фильтрации по минимальной цене. Параметры запроса с значениями по умолчанию становятся опциональными – пользователь может их не указывать.

Я обожаю этот подход FastAPI к параметрам! Когда я использовал Flask, приходилось писать кучу дополнительного кода для извлечения и валидации параметров запроса. А здесь всё происходит автоматически. Кстати, можно также указывать ограничения на параметры с помощью специальных валидаторов. Например, давайте убедимся, что количество запрашиваемых продуктов не превышает 100, а skip не отрицательный:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from fastapi import FastAPI, HTTPException, Query, Path
 
@app.get("/products/")
def list_products(
    skip: int = Query(0, ge=0, description="Сколько продуктов пропустить"),
    limit: int = Query(10, ge=1, le=100, description="Максимальное количество продуктов"),
    min_price: float = Query(None, description="Минимальная цена для фильтрации")
):
    result = products
    
    if min_price is not None:
        result = [p for p in result if p["price"] >= min_price]
    
    return result[skip : skip + limit]
 
@app.get("/products/{product_id}")
def read_product(product_id: int = Path(..., ge=0, description="ID продукта")):
    if product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return products[product_id]
Обратите внимание на использование Query и Path. Эти функции позволяют задавать дополнительные ограничения и метаданные для параметров. ge=0 означает "greater than or equal to 0" (больше или равно 0), а le=100 - "less than or equal to 100" (меньше или равно 100). Многоточие ... в Path(..., ge=0) означает, что параметр обязательный (без значения по умолчанию). Я наконец-то нашел фреймворк, где валидация данных не превращается в кошмар из десятков условий if-else! Эти метаданные также отображаются в автоматической документации, делая её еще полезнее.

Теперь давайте улучшим наш API для создания продуктов, добавив возможность обновления и удаления:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@app.put("/products/{product_id}")
def update_product(product_id: int, product: Product):
    if product_id < 0 or product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    
    updated_product = product.dict()
    updated_product["id"] = product_id
    products[product_id] = updated_product
    return updated_product
 
@app.delete("/products/{product_id}")
def delete_product(product_id: int):
    if product_id < 0 or product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    
    deleted_product = products.pop(product_id)
    
    # Обновляем ID оставшихся продуктов
    for i, p in enumerate(products):
        p["id"] = i
    
    return {"message": f"Продукт '{deleted_product['name']}' удален"}
Теперь у нас полноценный CRUD API для работы с продуктами. Хотя в этом примере я использую простой список вместо базы данных, в реальном проекте вы подключите ORM вроде SQLAlchemy или асинхронный драйвер для MongoDB.

Не могу не отметить, насколько элегантно FastAPI интегрируется с Pydantic. Модели Pydantic не только валидируют входящие данные, но и помогают определить структуру ответа API. Давайте улучшим наш код, определив модель ответа:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from typing import List, Optional
from pydantic import BaseModel, Field
 
class ProductBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=100, description="Название продукта")
    price: float = Field(..., gt=0, description="Цена продукта в рублях")
    description: Optional[str] = Field(None, max_length=1000, description="Описание продукта")
 
class ProductCreate(ProductBase):
    pass
 
class ProductResponse(ProductBase):
    id: int = Field(..., description="Уникальный идентификатор продукта")
 
@app.post("/products/", response_model=ProductResponse)
def create_product(product: ProductCreate):
    new_product = product.dict()
    new_product["id"] = len(products)
    products.append(new_product)
    return new_product
 
@app.get("/products/{product_id}", response_model=ProductResponse)
def read_product(product_id: int = Path(..., ge=0)):
    if product_id >= len(products):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return products[product_id]
 
@app.get("/products/", response_model=List[ProductResponse])
def list_products(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=100),
    min_price: float = Query(None, ge=0)
):
    result = products
    
    if min_price is not None:
        result = [p for p in result if p["price"] >= min_price]
    
    return result[skip : skip + limit]
Здесь я создал базовую модель ProductBase с общими полями, от которой наследуются модели для создания продукта и для ответа API. Это хороший паттерн для разделения входных и выходных данных. Параметр response_model в декораторах указывает FastAPI, какую структуру должен иметь ответ.
Обратите внимание на использование Field – это аналог Query и Path, но для полей Pydantic-моделей. С его помощью можно задавать валидацию, значения по умолчанию и документацию для полей модели.

Теперь давайте глубже изучим возможности автоматической документации. FastAPI генерирует OpenAPI-схему, которая включает все пути, параметры, типы данных и даже примеры запросов и ответов. Вы можете настроить глобальные метаданные для своего API:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app = FastAPI(
    title="API для управления продуктами",
    description="Этот API позволяет управлять каталогом продуктов",
    version="0.1.0",
    terms_of_service="http://example.com/terms/",
    contact={
        "name": "Иван Иванов",
        "url": "http://example.com/contact/",
        "email": "ivan@example.com",
    },
    license_info={
        "name": "MIT",
        "url": "https://opensource.org/licenses/MIT",
    },
)
Эти метаданные отображаются в документации и помогают пользователям понять, для чего предназначен ваш API и как с вами связаться в случае проблем. Я также часто добавляю примеры для запросов и ответов, чтобы сделать документацию еще понятнее:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from fastapi import Body
 
@app.post("/products/", response_model=ProductResponse)
def create_product(
    product: ProductCreate = Body(
        ...,
        example={
            "name": "Яблоко",
            "price": 1.5,
            "description": "Свежее красное яблоко"
        }
    )
):
    new_product = product.dict()
    new_product["id"] = len(products)
    products.append(new_product)
    return new_product
Параметр example определяет пример данных, который будет отображаться в Swagger UI. Это особено полезно для сложных моделей с множеством полей.

Python FastAPI не отображается html страница
Добрый день! Подскажите, пожалуйста, почему у меня не отображается html файл, хотя всё работает без...

Разработка на fastapi с jinja + uvicorn + starlette -- это какая архитектра?)
Добрый вечер, сразу прошу прощение за возможно глупый и банальный вопрос. Если я разрабатываю...

Сколько оперативной памяти сервера нужно питону и среде для запуска fastapi?
Доброй ночи всем, кто с радостью набирает код на Питоне! Я пока не определился в пользу выбора...

FastAPI и сериалайзеры
В моём проекте три таблицы. Нужно сформировать ответ который дергает инфу сразу из всех трёх...


Углубляемся в детали



В своем первом проекте на FastAPI я поначалу использовал всего 10% его возможностей, и уже это показалось мне революционным прорывом. Но когда я узнал про остальные 90% - вот тогда наступило настоящее просветление.

Pydantic: больше чем просто валидация



Мы уже затронули Pydantic-модели, но они способны на гораздо большее, чем простая валидация полей. Pydantic - это настоящий фундамент FastAPI, который решает множество болезненных проблем разработки.

Во-первых, давайте посмотрим на более сложные примеры валидации:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import date
 
class Category(BaseModel):
    name: str
    description: Optional[str] = None
 
class ProductDetailed(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    price: float = Field(..., gt=0)
    is_in_stock: bool = True
    tags: List[str] = []
    manufactured: date
    category: Category
    
    @validator('manufactured')
    def check_date(cls, v):
        if v > date.today():
            raise ValueError("Дата производства не может быть в будущем")
        return v
    
    @validator('tags')
    def check_tags(cls, v):
        if len(v) > 5:
            raise ValueError("Слишком много тегов (максимум 5)")
        return v
Здесь я использую валидаторы для проверки пользовательской логики. Например, дата производства не может быть в будущем, а количество тегов ограничено пятью. Метод validator - это декоратор, который позволяет задать функцию для валидации конкретного поля. Но самое интересное - это вложенные модели. Обратите внимание на поле category типа Category. Pydantic рекурсивно проверит все поля вложенной модели, и если что-то не так, выдаст детальную ошибку с указанием точного пути к проблемному полю. Я перестал писать однообразные проверки и сосредоточился на бизнес-логике, как только освоил эту возможность. Это как иметь встроенный статический анализатор типов прямо в рантайме.

А еще Pydantic умеет преобразовывать данные между различными форматами. Метод .dict() мы уже видели, но есть еще .json() для сериализации в JSON-строку и .parse_obj() для создания модели из словаря.

Python
1
2
3
4
5
6
7
# Преобразование модели в JSON-строку
product_json = product.json()
 
# Преобразование из словаря в модель
product_data = {"name": "Молоко", "price": 60.5, "manufactured": "2023-05-01", 
                "category": {"name": "Молочные продукты"}}
product = ProductDetailed.parse_obj(product_data)

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



Работа с файлами - еще одна область, где FastAPI блистает. В прошлом я писал десятки строк кода для обработки загрузки файлов в Flask, а с FastAPI это делается буквально в пару строк:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from fastapi import FastAPI, File, UploadFile
import shutil
from pathlib import Path
 
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
    upload_dir = Path("uploads")
    upload_dir.mkdir(exist_ok=True)
    
    # Сохраняем загруженный файл
    file_path = upload_dir / file.filename
    with file_path.open("wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    
    return {"filename": file.filename, "size": file_path.stat().st_size}
Здесь я использую класс UploadFile, который предоставляет методы для работы с загруженным файлом, включая асинхронное чтение. Это намного удобнее, чем работать с сырыми байтами.
А если нужно обработать несколько файлов одновременно:

Python
1
2
3
4
5
6
7
@app.post("/upload-multiple/")
async def upload_multiple_files(files: List[UploadFile] = File(...)):
    results = []
    for file in files:
        # Здесь может быть любая обработка файла
        results.append({"filename": file.filename, "content_type": file.content_type})
    return results
Кстати, FastAPI также поддерживает форматы данных, отличные от JSON. Например, вы можете получать и отправлять файлы как часть ответа:

Python
1
2
3
4
5
6
7
8
9
from fastapi.responses import FileResponse
 
@app.get("/download/{filename}")
async def download_file(filename: str):
    file_path = Path("uploads") / filename
    if not file_path.exists():
        raise HTTPException(status_code=404, detail="Файл не найден")
        
    return FileResponse(file_path)

Работа с заголовками HTTP и cookies



Часто API-приложениям нужен доступ к HTTP-заголовкам и cookies. FastAPI делает это предельно просто:

Python
1
2
3
4
5
6
7
8
9
from fastapi import FastAPI, Header, Cookie
 
@app.get("/headers/")
async def read_headers(user_agent: str = Header(None)):
    return {"User-Agent": user_agent}
 
@app.get("/cookies/")
async def read_cookies(session: str = Cookie(None)):
    return {"session": session}
А если нужно установить cookie в ответе:

Python
1
2
3
4
5
6
7
8
from fastapi.responses import JSONResponse
 
@app.post("/login/")
async def login(username: str):
    content = {"message": f"Привет, {username}"}
    response = JSONResponse(content=content)
    response.set_cookie(key="session", value="secret-token")
    return response
Я часто использую этот паттерн в микросервисных архитектурах, где нужно передавать токены между сервисами. FastAPI делает это настолько прозрачно, что код остается чистым и понятным.

Продвинутая обработка ошибок



Мы уже видели базовое использование HTTPException, но FastAPI позволяет настроить обработку ошибок глобально, что особенно полезно для больших проектов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
 
app = FastAPI()
 
class CustomException(Exception):
    def __init__(self, name: str):
        self.name = name
 
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Упс! {exc.name} сломался. Мы уже чиним!"},
    )
 
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 42:
        raise CustomException(name="Продукт")
    return {"item_id": item_id}
В этом примере я создал пользовательское исключение CustomException и глобальный обработчик для него. Теперь, когда это исключение возникает где-либо в коде, FastAPI автоматически преобразует его в ответ с кодом 418 (I'm a teapot - да, такой статус-код действительно существует!).

Это чрезвычайно полезно для централизованной обработки ошибок. В одном из моих проектов я создал иерархию исключений для разных типов бизнес-ошибок, и вся обработка была вынесена в отдельный модуль. Код основных эндпоинтов остался чистым, без загромождения блоками try-except.

Dependency Injection и безопасность



Одна из самых недооцененных, но мощных фич FastAPI - это система внедрения зависимостей (dependency injection). Она позволяет:

1. Повторно использовать общую логику между эндпоинтами,
2. Разделять код на маленькие, тестируемые функции,
3. Реализовать аутентификацию и авторизацию

Давайте посмотрим на простой пример: допустим, нам нужно проверять API-ключ для некоторых эндпоинтов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from fastapi import FastAPI, Depends, HTTPException, Header
 
app = FastAPI()
 
async def verify_api_key(x_api_key: str = Header(None)):
    if x_api_key != "секретный_ключ":
        raise HTTPException(status_code=403, detail="Неверный API-ключ")
    return x_api_key
 
@app.get("/secure-endpoint/", dependencies=[Depends(verify_api_key)])
async def secure_endpoint():
    return {"message": "Это защищенный эндпоинт"}
 
@app.get("/another-secure/")
async def another_secure(api_key: str = Depends(verify_api_key)):
    return {"message": f"Ваш API-ключ: {api_key}"}
Я использую функцию verify_api_key как зависимость двумя способами:
1. Как просто зависимость для эндпоинта, без доступа к её результату.
2. Как параметр функции, чтобы получить результат зависимости.

Возможности Depends идут гораздо дальше. Вы можете создавать классы зависимостей с параметрами, комбинировать зависимости и даже использовать их для всего приложения с помощью app.dependency_overrides.

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

Зависимости в FastAPI также могут быть условными. Например, в одном из моих проектов мне нужно было реализовать разные уровни доступа для API: некоторые эндпоинты должны быть доступны анонимно, другие - только зарегестрированным пользователям, а третьи - только админам. Вместо написания отдельной логики для каждого эндпоинта, я создал многоуровневую систему:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def get_current_user(token: str = Depends(oauth2_scheme)):
    # Проверка токена и возврат пользователя
    user = decode_token(token)
    if user is None:
        raise HTTPException(
            status_code=401, 
            detail="Недействительные учетные данные"
        )
    return user
 
def get_admin_user(current_user: User = Depends(get_current_user)):
    if current_user.role != "admin":
        raise HTTPException(
            status_code=403, 
            detail="Недостаточно прав"
        )
    return current_user
 
@app.get("/users/me")
def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user
 
@app.get("/admin/stats")
def admin_stats(admin: User = Depends(get_admin_user)):
    return {"total_users": get_users_count()}
Красота этого подхода в том, что зависимости могут сами иметь зависимости! get_admin_user зависит от get_current_user, образуя цепочку проверок. Код становится очень чистым и легко тестируемым.

WebSocket поддержка



FastAPI также имеет встроенную поддержку WebSocket для создания приложений реального времени. Я использовал эту возможность для создания системы уведомлений в одном из проектов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
 
app = FastAPI()
 
# Хранилище для активных соединений
class ConnectionManager:
    def __init__(self):
        self.active_connections = []
 
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
 
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
 
    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)
 
manager = ConnectionManager()
 
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            # Эхо индивидуальному клиенту
            await websocket.send_text(f"Вы сказали: {data}")
            # Рассылка всем клиентам
            await manager.broadcast(f"Клиент #{client_id} говорит: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Клиент #{client_id} покинул чат")
Этот простой код реализует чат-сервер с рассылкой сообщений всем подключеным клиентам. Можно легко расширить его для реализации приватных сообщений, каналов или других паттернов обмена данными в реальном времени.

В моем проекте я добавил к этому шифрование сообщений и систему авторизации на WebSocket, что позволило создать безопасный канал обмена конфиденциальными данными между клиентом и сервером.

Middleware и обработка запросов



Middleware (промежуточное ПО) в FastAPI позволяет выполнять код до и после обработки запроса. Это отлично подходит для добавления логирования, измерения времени выполнения, проверки заголовков и т.д.

Python
1
2
3
4
5
6
7
8
9
10
11
12
import time
from fastapi import FastAPI, Request
 
app = FastAPI()
 
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response
Этот middleware добавляет заголовок с временем обработки запроса. Функция call_next вызывает следующий middleware или сам обработчик запроса, а затем возвращает ответ. Очень полезный паттерн - логирование запросов и ответов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
 
logger = logging.getLogger("api")
 
@app.middleware("http")
async def log_requests(request: Request, call_next):
    logger.info(f"Request: {request.method} {request.url}")
    try:
        response = await call_next(request)
        logger.info(f"Response: {response.status_code}")
        return response
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return JSONResponse(status_code=500, content={"detail": "Internal Server Error"})
Я часто использую middleware для добавления кастомных заголовков безопасности, как CORS, CSP и других:

Python
1
2
3
4
5
6
7
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Content-Security-Policy"] = "default-src 'self'"
    return response
Кстати, FastAPI уже имеет встроенную поддержку CORS, которую можно настроить так:

Python
1
2
3
4
5
6
7
8
9
from fastapi.middleware.cors import CORSMiddleware
 
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost", "https://example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Асинхронность в FastAPI



Одна из главных особенностей FastAPI - поддержка асинхронного программирования с помощью async/await. В отличие от Flask и Django, где асинхронность - это скорее дополнение, в FastAPI это встроенная возможность.

Когда стоит использовать асинхронные обработчики?
  1. При операциях ввода-вывода: запросы к базам данных, API или файловой системе,
  2. При обработке множества параллельных запросов,
  3. Для долгих операций, которые могут блокировать основной поток.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import asyncio
from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/sync")
def sync_route():
    # Блокирующая операция - блокирует весь сервер
    import time
    time.sleep(1)
    return {"message": "Синхронный путь"}
 
@app.get("/async")
async def async_route():
    # Неблокирующая операция - другие запросы обрабатываются параллельно
    await asyncio.sleep(1)
    return {"message": "Асинхронный путь"}
В этом примере /sync блокирует весь сервер на 1 секунду, в то время как /async позволяет обрабатывать другие запросы, пока эта функция "спит".

Асинхронность особенно полезна при работе с базами данных. Например, с асинхронным драйвером для MongoDB:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
 
app = FastAPI()
 
@app.on_event("startup")
async def startup_db_client():
    app.mongodb_client = AsyncIOMotorClient("mongodb://localhost:27017")
    app.mongodb = app.mongodb_client.db
 
@app.on_event("shutdown")
async def shutdown_db_client():
    app.mongodb_client.close()
 
@app.get("/users/{user_id}")
async def get_user(user_id: str):
    user = await app.mongodb.users.find_one({"_id": user_id})
    if user:
        return user
    return {"error": "User not found"}
В моем опыте, переход от синхронных к асинхронным операциям с базой данных увеличил пропускную способность API примерно в 10 раз при тех же затратах ресурсов. Но есть и подводные камни: асинхронный код сложнее отлаживать, а если вы используете блокирующие библиотеки внутри асинхронных функций, производительность может даже упасть.

Мой совет: используйте асинхронность для операций ввода-вывода, но не переписывайте все подряд - иногда синхронный код проще и понятнее.

Есть и другие крутые возможности FastAPI для асинхронного программирования, например, фоновые задачи:

Python
1
2
3
4
5
6
7
8
9
10
11
12
from fastapi import FastAPI, BackgroundTasks
 
app = FastAPI()
 
def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(message + "\n")
 
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, f"Отправка уведомления на {email}")
    return {"message": "Уведомление будет отправлено в фоновом режиме"}
Метод add_task добавляет функцию в очередь задач, которые будут выполнены после отправки ответа клиенту. Это отличный способ обрабатывать долгие операции, не заставляя пользователя ждать.

Типизация запросов и ответов



Я всегда был фанатом строгой типизации, и FastAPI - это настоящий подарок для таких, как я. Аннотации типов здесь не просто документация, а рабочий механизм, который помогает избежать множества ошибок еще на этапе разработки. Допустим, вы хотите создать эндпоинт, который принимает список продуктов и возвращает общую стоимость:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from typing import List
from pydantic import BaseModel
from fastapi import FastAPI
 
app = FastAPI()
 
class Product(BaseModel):
    name: str
    price: float
    quantity: int = 1
 
@app.post("/calculate-total/")
async def calculate_total(products: List[Product]):
    total = sum(product.price * product.quantity for product in products)
    return {"total": total}
Здесь типизация работает на нескольких уровнях:
1. FastAPI проверяет, что тело запроса - это список объектов.
2. Pydantic проверяет, что каждый объект соответствует модели Product.
3. IDE показывает подсказки с типами при работе с полями продукта.

Но это только начало. Вы можете определять типы для всего: параметров пути, запроса, заголовков и даже для возвращаемых данных.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import Dict, Any, Union, Optional
from fastapi import FastAPI, Query, Path, Body
 
app = FastAPI()
 
@app.get("/items/{item_id}", response_model=Dict[str, Any])
async def read_item(
    item_id: int = Path(..., title="ID товара"),
    q: Optional[str] = Query(None, max_length=50),
    skip: int = Query(0, ge=0),
    limit: Union[int, None] = Query(10, le=100)
):
    # ...
    return {"item_id": item_id, "q": q, "skip": skip, "limit": limit}
Параметр response_model особенно полезен - он не только валидирует данные перед отправкой, но и фильтрует лишние поля. Это критично для безопасности, когда вы не хотите случайно раскрыть конфиденциальную информацию. Я часто использую это для разделения внутренних и публичных представлений данных:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class UserInDB(BaseModel):
    id: int
    username: str
    password_hash: str
    email: str
    is_admin: bool = False
 
class UserPublic(BaseModel):
    id: int
    username: str
    email: str
 
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
    # Предположим, это реальный объект с хешем пароля и другими секретами
    user = UserInDB(
        id=user_id,
        username="johndoe",
        password_hash="supersecret",
        email="john@example.com",
        is_admin=True
    )
    return user  # FastAPI автоматически отфильтрует поля password_hash и is_admin
В одном из моих проектов это спасло от серьезной уязвимости - джуниор случайно вернул полный объект пользователя, включая хеш пароля, но благодаря response_model клиент получил только безопасные поля.

Декораторы для маршрутизации и организации кода



FastAPI предлагает набор декораторов для определения маршрутов, которые соответствуют HTTP-методам:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@app.get("/items/")
async def read_items():
    return [{"name": "Item 1"}, {"name": "Item 2"}]
 
@app.post("/items/")
async def create_item(item: dict):
    return item
 
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: dict):
    return {"item_id": item_id, **item}
 
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"deleted": item_id}
 
@app.patch("/items/{item_id}")
async def partial_update(item_id: int, item: dict):
    return {"item_id": item_id, **item}
Но помимо основных методов, есть еще @app.head(), @app.options() и даже @app.trace(). Я редко использую их напрямую, но они бывают полезны для совместимости с некоторыми клиентами.

Для организации кода в больших проектах, FastAPI предлагает концепцию "APIRouter". Это как мини-приложения, которые можно подключать к основному. Я обычно организую код по доменным областям:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from fastapi import APIRouter, FastAPI
 
app = FastAPI()
 
# Роутер для пользователей
user_router = APIRouter(prefix="/users", tags=["users"])
 
@user_router.get("/")
async def list_users():
    return [{"username": "rick"}, {"username": "morty"}]
 
@user_router.get("/{username}")
async def get_user(username: str):
    return {"username": username}
 
# Роутер для товаров
product_router = APIRouter(prefix="/products", tags=["products"])
 
@product_router.get("/")
async def list_products():
    return [{"name": "Hammer"}, {"name": "Wrench"}]
 
# Подключаем роутеры к основному приложению
app.include_router(user_router)
app.include_router(product_router)
Параметр tags особенно полезен - он группирует эндпоинты в Swagger UI, что делает документацию более структурированной. А prefix избавляет от необходимости повторять базовый путь для каждого эндпоинта.
В одном из наших проектов мы пошли дальше и создали фабрику роутеров для стандартных CRUD-операций:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def create_crud_router(model, db_dependency):
    router = APIRouter()
    
    @router.get("/")
    async def list_items(db = Depends(db_dependency)):
        return await db.find_all(model)
    
    @router.get("/{item_id}")
    async def get_item(item_id: str, db = Depends(db_dependency)):
        return await db.find_one(model, item_id)
    
    # ... другие CRUD-методы
    
    return router
 
# Использование
user_router = create_crud_router(User, get_db)
app.include_router(user_router, prefix="/users", tags=["users"])
Это сэкономило нам кучу повторяющегося кода и сделало API более консистентным.

Кеширование и управление состоянием



FastAPI не имеет встроенного механизма кеширования, но его легко добавить с помощью зависимостей и декораторов. Я часто использую такой паттерн:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from fastapi import FastAPI, Depends
from functools import lru_cache
 
app = FastAPI()
 
class Settings:
    def __init__(self):
        # В реальном проекте здесь могла бы быть загрузка из .env файла
        self.app_name = "Awesome API"
        self.admin_email = "admin@example.com"
        self.items_per_page = 20
 
@lru_cache()
def get_settings():
    return Settings()
 
@app.get("/info")
async def info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "admin_email": settings.admin_email
    }
Декоратор @lru_cache() из стандартной библиотеки Python кеширует результат функции, так что Settings создается только один раз, а не при каждом запросе.
Для более сложных сценариев кеширования я использую Redis:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import aioredis
from fastapi import FastAPI, Depends
import json
 
app = FastAPI()
 
async def get_redis():
    redis = await aioredis.create_redis_pool("redis://localhost")
    try:
        yield redis
    finally:
        redis.close()
        await redis.wait_closed()
 
@app.get("/products/{product_id}")
async def get_product(product_id: int, redis = Depends(get_redis)):
    # Пробуем получить из кеша
    cached = await redis.get(f"product:{product_id}")
    if cached:
        return json.loads(cached)
    
    # Если нет в кеше, получаем из БД
    product = await db.get_product(product_id)
    
    # Сохраняем в кеш на 1 час
    await redis.set(
        f"product:{product_id}", 
        json.dumps(product), 
        expire=3600
    )
    
    return product
Такой подход значительно снижает нагрузку на базу данных и ускоряет ответы API. В одном из проектов это снизило среднее время ответа с 200мс до 15мс - впечатляющее улучшение!

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



Большинство реальных проектов так или иначе используют базы данных. FastAPI прекрасно работает с SQLAlchemy — самым популярным ORM для Python. Вот базовая настройка асинхронного SQLAlchemy:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import Column, Integer, String, Float
 
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"
 
Base = declarative_base()
engine = create_async_engine(DATABASE_URL)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
 
class Product(Base):
__tablename__ = "products"
 
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)
price = Column(Float)
 
app = FastAPI()
 
async def get_db():
db = AsyncSessionLocal()
try:
    yield db
finally:
    await db.close()
 
@app.get("/products/{product_id}")
async def get_product(product_id: int, db: AsyncSession = Depends(get_db)):
from sqlalchemy import select
query = select(Product).where(Product.id == product_id)
result = await db.execute(query)
product = result.scalars().first()
 
if not product:
    raise HTTPException(status_code=404, detail="Продукт не найден")
return product
Обратите внимание на использование asyncpg — это асинхронный драйвер для PostgreSQL, который делает запросы неблокирующими. В боевом приложении я обычно выношу схемы моделей и функции доступа к БД в отдельные модули для лучшей организации кода.

Частая ошибка новичков — создание сессии базы данных для каждого запроса вручную. Зависимость get_db решает эту проблему элегантно и обеспечивает автоматическое закрытие соединения даже при возникновении исключения.

Безопасность и аутентификация



Безопасность критична для любого API. FastAPI имеет встроенную поддержку OAuth2 с JWT-токенами:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from pydantic import BaseModel
 
# Настройки безопасности
SECRET_KEY = "ваш_секретный_ключ"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
 
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
 
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = False
 
class Token(BaseModel):
access_token: str
token_type: str
 
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
 
def get_user(db, username: str):
# В реальности - запрос к БД
if username == "testuser":
    return {"username": "testuser", "hashed_password": pwd_context.hash("password")}
 
def authenticate_user(db, username: str, password: str):
user = get_user(db, username)
if not user:
    return False
if not verify_password(password, user["hashed_password"]):
    return False
return user
 
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
 
app = FastAPI()
 
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(None, form_data.username, form_data.password)
if not user:
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Неверное имя пользователя или пароль",
        headers={"WWW-Authenticate": "Bearer"},
    )
access_token = create_access_token(
    data={"sub": user["username"]},
    expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
return {"access_token": access_token, "token_type": "bearer"}
 
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
    status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Недействительные учетные данные",
    headers={"WWW-Authenticate": "Bearer"},
)
try:
    payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    username: str = payload.get("sub")
    if username is None:
        raise credentials_exception
except JWTError:
    raise credentials_exception
user = get_user(None, username=username)
if user is None:
    raise credentials_exception
return user
 
@app.get("/users/me", response_model=User)
async def read_users_me(current_user = Depends(get_current_user)):
return current_user
В реальных проектах я также часто реализую:
  • Систему ролей и разрешений,
  • Ограничение частоты запросов (rate limiting),
  • Защиту от CSRF и XSS атак,
  • Двухфакторную аутентификацию,

Не секрет, что большинство уязвимостей в API происходят из-за некорректной проверки аутентификации и авторизации. FastAPI упрощает правильную реализацию этих механизмов, но вам все равно нужно хорошо понимать основы безопасности.

Структура проекта для реальных задач



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

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
my_app/
├── alembic/               # Миграции базы данных
├── app/
│   ├── __init__.py
│   ├── main.py            # Точка входа FastAPI
│   ├── config.py          # Конфигурация приложения
│   ├── dependencies.py    # Общие зависимости
│   ├── api/
│   │   ├── __init__.py
│   │   ├── v1/            # API версии 1
│   │   │   ├── __init__.py
│   │   │   ├── endpoints/ # Маршруты сгруппированы по ресурсам
│   │   │   │   ├── __init__.py
│   │   │   │   ├── users.py
│   │   │   │   ├── products.py
│   │   │   │   └── ...
│   │   │   └── router.py  # Сборка всех маршрутов v1
│   │   └── v2/            # API версии 2
│   ├── core/              # Ядро приложения
│   │   ├── __init__.py
│   │   ├── security.py    # JWT, аутентификация
│   │   └── errors.py      # Обработчики ошибок
│   ├── crud/              # Функции CRUD
│   │   ├── __init__.py
│   │   ├── base.py        # Базовый класс CRUD
│   │   ├── users.py
│   │   └── ...
│   ├── db/                # Настройки БД
│   │   ├── __init__.py
│   │   ├── base.py
│   │   └── session.py
│   ├── models/            # SQLAlchemy модели
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── ...
│   └── schemas/           # Pydantic модели
│       ├── __init__.py
│       ├── user.py
│       └── ...
├── tests/                 # Тесты
└── .env                   # Переменные окружения
Такая структура обеспечивает хорошую организацию кода и облегчает его поддержку и масштабирование. Конечно, не нужно слепо следовать этому шаблону — адаптируйте его под свои нужды. Один из ключевых принципов, который я применяю — это строгое разделение между моделями БД (SQLAlchemy) и схемами API (Pydantic). Это позволяет независимо развивать структуру базы данных и API-контракты.

Фоновые задачи и очереди



Для долгих операций, таких как отправка email или обработка файлов, я использую фоновые задачи. FastAPI предлагает простой механизм с BackgroundTasks, но для сложных сценариев лучше использовать Celery или арину:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from fastapi import FastAPI, BackgroundTasks
from fastapi.responses import JSONResponse
import aioredis
import aiohttp
from arq import create_pool
from arq.connections import RedisSettings
 
app = FastAPI()
 
async def process_video(ctx, video_id: str):
# Долгая обработка видео
return {"processed": True, "video_id": video_id}
 
@app.on_event("startup")
async def startup():
app.redis = await create_pool(RedisSettings(host="localhost"))
 
@app.on_event("shutdown")
async def shutdown():
await app.redis.close()
 
@app.post("/videos/{video_id}/process")
async def start_video_processing(video_id: str):
# Добавляем задачу в очередь
job = await app.redis.enqueue_job("process_video", video_id)
return {"job_id": job.job_id, "status": "processing"}
 
@app.get("/jobs/{job_id}")
async def get_job_status(job_id: str):
job = await app.redis.get_job_result(job_id)
if job is None:
    return {"status": "processing"}
return {"status": "completed", "result": job}
В этом примере я использую арину — легковесную асинхронную очередь задач, которая хорошо работает с FastAPI. Для более сложных сценариев с приоритетами, ретраями и мониторингом я бы выбрал Celery с RabbitMQ.

Важный момент: никогда не запускайте тяжелые вычисления прямо в обработчике FastAPI — это блокирует весь сервер. Всегда выносите их в фоновые задачи или микросервисы.

Кеширование и оптимизация производительности



В высоконагруженных системах кеширование становится не роскошью, а необходимостью. Я много раз спасал проекты от перегрузки, добавляя правильную стратегию кеширования. С FastAPI это делается особенно элегантно благодаря системе зависимостей. Помимо уже упомянутого Redis, я часто использую кеширование на уровне запросов с помощью HTTP-заголовков:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from fastapi import FastAPI, Request, Response, Depends
from fastapi.responses import JSONResponse
import time
import hashlib
 
app = FastAPI()
 
def generate_etag(data: dict) -> str:
    # Создаем хеш содержимого для ETag
    content = str(sorted(data.items())).encode()
    return hashlib.md5(content).hexdigest()
 
@app.middleware("http")
async def add_cache_headers(request: Request, call_next):
    response = await call_next(request)
    
    # Добавляем заголовки кеширования только для GET-запросов
    if request.method == "GET":
        response.headers["Cache-Control"] = "max-age=60"  # Кешируем на 1 минуту
        
        # Если есть тело ответа, добавляем ETag
        if hasattr(response, "body"):
            try:
                body = response.body.decode()
                import json
                data = json.loads(body)
                etag = generate_etag(data)
                response.headers["ETag"] = etag
            except:
                pass
    
    return response
Этот мидлвар добавляет заголовки кеширования для GET-запросов и генерирует ETag на основе содержимого ответа. Клиенты могут использовать это для условных запросов, что значительно снижает нагрузку на сервер. Но кеширование - это только часть истории. Для действительно высокопроизводительных API я применяю:

1. Оптимизация запросов к БД: Не только индексы, но и правильное использование select_related и prefetch_related в ORM, чтобы избежать N+1 запросов.
2. Пагинация и курсоры: Для больших наборов данных вместо обычной пагинации с limit/offset использую курсоры, особенно на постоянно обновляющихся данных:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@app.get("/users/")
async def list_users(cursor: str = None, limit: int = 20):
    if cursor:
        # Декодируем курсор (обычно это закодированный timestamp последнего элемента)
        import base64
        last_timestamp = float(base64.b64decode(cursor).decode())
        users = await db.fetch_users(
            where="created_at < :timestamp", 
            params={"timestamp": last_timestamp},
            limit=limit + 1,  # +1 чтобы узнать, есть ли еще элементы
            order_by="created_at DESC"
        )
    else:
        users = await db.fetch_users(limit=limit + 1, order_by="created_at DESC")
    
    has_more = len(users) > limit
    result = users[:limit]  # Отрезаем лишний элемент
    
    # Создаем новый курсор, если есть еще элементы
    next_cursor = None
    if has_more and result:
        last_user = result[-1]
        next_cursor = base64.b64encode(str(last_user["created_at"]).encode()).decode()
    
    return {
        "users": result,
        "has_more": has_more,
        "next_cursor": next_cursor
    }
3. Сжатие ответов: FastAPI автоматически поддерживает сжатие ответов Gzip и Brotli, но нужно убедиться, что ваш сервер настроен правильно:

Python
1
2
3
from fastapi.middleware.gzip import GZipMiddleware
 
app.add_middleware(GZipMiddleware, minimum_size=1000)  # Сжимать ответы больше 1KB
Один из самых недооцененных аспектов оптимизации - профилирование. Я регулярно использую инструменты вроде py-spy для анализа узких мест:

Bash
1
2
# Профилирование запущеного приложения
py-spy record -o profile.svg --pid $(pgrep -f "uvicorn main:app")
Это даёт наглядную диаграмму вызовов, где сразу видно, какие функции занимают больше всего времени.

Контейнеризация FastAPI приложений



Docker и FastAPI - просто созданы друг для друга. Я контейнеризирую все свои FastAPI-приложения для упрощения развертывания и масштабирования. Вот базовый Dockerfile, который я использую:

Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Базовый образ Python
FROM python:3.10-slim
 
# Рабочая директория внутри контейнера
WORKDIR /app
 
# Устанавливаем зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
 
# Копируем код приложения
COPY ./app /app/app
 
# Устанавливаем переменные окружения
ENV PORT=8000
 
# Запускаем приложение
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "${PORT}"]
Но в продакшн я обычно использую многоэтапную сборку, чтобы уменьшить размер образа:

Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Сборочный этап
FROM python:3.10-slim AS builder
 
WORKDIR /app
 
# Устанавливаем только необходимые пакеты для сборки
RUN pip install --no-cache-dir poetry
 
# Копируем только файлы зависимостей
COPY pyproject.toml poetry.lock* ./
 
# Экспортируем зависимости в requirements.txt
RUN poetry export -f requirements.txt > requirements.txt
 
# Финальный этап
FROM python:3.10-slim
 
WORKDIR /app
 
# Копируем requirements.txt из предыдущего этапа
COPY --from=builder /app/requirements.txt .
 
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
 
# Копируем код приложения
COPY ./app /app/app
 
# Пользователь без привилегий для безопасности
RUN useradd -m appuser
USER appuser
 
# Конфигурация для продакшена
ENV PORT=8000
ENV WORKERS=4
 
# Запускаем с несколькими рабочими процессами
CMD uvicorn app.main:app --host 0.0.0.0 --port $PORT --workers $WORKERS
Важный момент - количество воркеров. Я обычно использую формулу (2 * CPU_cores) + 1. На 4-ядерном сервере это будет 9 воркеров. Но не забывайте, что каждый воркер потребляет память, так что нужен баланс. Для еще большей производительности я иногда использую Gunicorn с Uvicorn-воркерами:

Bash
1
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker
Это даёт преимущества обоих миров: стабильность Gunicorn и скорость Uvicorn.
Что касается docker-compose, вот базовая конфигурация для разработки:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3'
 
services:
  api:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app/app
    environment:
      - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db/app
      - DEBUG=true
    depends_on:
      - db
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
 
  db:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=app
 
volumes:
  postgres_data:
Обратите внимание на флаг --reload - он позволяет изменять код и видить результаты без перезапуска контейнера, что очень удобно в разработке.

Развертывание и мониторинг



После того как приложение контейнеризировано, его можно развернуть где угодно: Kubernetes, Heroku, AWS ECS и т.д. Я обычно использую GitHub Actions для CI/CD:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Deploy
 
on:
  push:
    branches: [ main ]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v2
      with:
        context: .
        push: true
        tags: myregistry.io/myapp:latest
    
    - name: Deploy to Kubernetes
      uses: steebchen/kubectl@v2
      with:
        config: ${{ secrets.KUBE_CONFIG }}
        command: apply -f k8s/deployment.yaml
Для мониторинга FastAPI приложений я использую комбинацию Prometheus и Grafana. FastAPI легко интегрируется с Prometheus через starlette-exporter:

Python
1
2
3
4
5
6
7
8
9
10
from fastapi import FastAPI
from starlette_exporter import PrometheusMiddleware, handle_metrics
 
app = FastAPI()
 
# Добавляем middleware для сбора метрик
app.add_middleware(PrometheusMiddleware)
 
# Эндпоинт для Prometheus
app.add_route("/metrics", handle_metrics)
Теперь у вас есть эндпоинт /metrics, который возвращает метрики в формате Prometheus: количество запросов, время ответа, статус коды и т.д.

Для логирования я настоятельно рекомендую использовать структурированные логи в формате JSON - это делает их анализ намного проще:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import logging
import json
from datetime import datetime
 
class JSONFormatter(logging.Formatter):
def format(self, record):
    log_record = {
        "timestamp": datetime.utcnow().isoformat(),
        "level": record.levelname,
        "message": record.getMessage(),
        "module": record.module,
        "function": record.funcName,
    }
    
    if hasattr(record, "request_id"):
        log_record["request_id"] = record.request_id
    
    if record.exc_info:
        log_record["exception"] = self.formatException(record.exc_info)
    
    return json.dumps(log_record)
 
# Настройка логгера
logger = logging.getLogger("api")
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
 
# Middleware для добавления request_id в логи
@app.middleware("http")
async def add_request_id(request: Request, call_next):
    request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
    
    # Добавляем request_id в контекст логгера
    logger_with_context = logging.LoggerAdapter(
        logger, {"request_id": request_id}
    )
    
    # Используем этот логгер в обработчике
    request.state.logger = logger_with_context
    
    response = await call_next(request)
    response.headers["X-Request-ID"] = request_id
    return response
Такой подход позволяет легко трассировать запросы через разные сервисы и микросервисы.

Полный листинг готового приложения



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

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from fastapi import FastAPI, Depends, HTTPException, Query, Path
from fastapi.middleware.cors import CORSMiddleware
from typing import List, Optional
from pydantic import BaseModel, Field
import uvicorn
 
app = FastAPI(title="Простой API каталога продуктов")
 
# Middleware для CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
 
# Модели данных
class ProductBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    price: float = Field(..., gt=0)
    description: Optional[str] = None
 
class ProductCreate(ProductBase):
    pass
 
class ProductResponse(ProductBase):
    id: int
 
# Имитация базы данных
products_db = []
 
# Маршруты API
@app.post("/products/", response_model=ProductResponse)
def create_product(product: ProductCreate):
    new_product = product.dict()
    new_product["id"] = len(products_db)
    products_db.append(new_product)
    return new_product
 
@app.get("/products/{product_id}", response_model=ProductResponse)
def read_product(product_id: int = Path(..., ge=0)):
    if product_id >= len(products_db):
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return products_db[product_id]
 
@app.get("/products/", response_model=List[ProductResponse])
def list_products(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=100),
    min_price: Optional[float] = Query(None, ge=0)
):
    result = products_db
    
    if min_price is not None:
        result = [p for p in result if p["price"] >= min_price]
    
    return result[skip : skip + limit]
 
if __name__ == "__main__":
    uvicorn.run("main:app", reload=True)
Просто сохраните этот код в файл main.py, установите зависимости (pip install fastapi uvicorn) и запустите с помощью python main.py. Готово! У вас есть полноценный API с валидацией данных, обработкой ошибок и автоматической документацией. Перейдите на http://127.0.0.1:8000/docs, чтобы увидеть и протестировать свои эндпоинты.

FastAPI и pytest
Использовал инструкцию https://www.fastapitutorial.com/blog/unit-testing-in-fastapi/ но что-то не...

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

JSON поле в peewee + pydantic (FastAPI)
Доброго времени. Проблема следующая. Есть приложение на FastAPI, в котором описаны модели таблиц...

FastAPI и matplotlib
Напишите API сервис используя фреймворк FastAPI Он должен уметь принимать данные (На ваше...

Ошибка NameError при применении FastAPI
Всем добрый день :) Подскажите, пожалуйста: есть функция, в которую передаю аргумент через...

FastAPI выгрузить html код
возникла небольшая проблема я решил при помощи фраемворка выгрузить html код(так по себе он...

ImportError FastAPI
Всем, привет, прохожу сейчас курс по FastAPI. Остановился на аутентификации, из-за того, что при...

FastAPI и приложение для протоколирования собрания
Добрый день! Я учусь python не так давно и мне нужно написать бэк для веб-приложения для...

Аутентификация в FastAPI
Добрый день. Написал в FastAPI вот такой эндпоинт: from fastapi import FastAPI, Depends,...

FastAPI SQLAlchemy запрос вывести в JSON или CSV?
Выполнил сложный sql запрос через sqlalchemy, результат в консоль печатается. Надо выводить в...

Чем async Flask хуже FastAPI?
День добрый. На рынке видно тренды Flask, FastAPI. Порой (возможно), компании отдают...

В январе Саше подарили пару новорожденных кроликов. Через два месяца они дали первый приплод – новую пару кроликов
суть задания: В январе Саше подарили пару новорожденных кроликов. Через два месяца они дали первый...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru