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

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

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

Нажмите на изображение для увеличения
Название: API на базе FastAPI с Python за пару минут.jpg
Просмотров: 260
Размер:	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
Комментарии
 
Новые блоги и статьи
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru