Ключевые отличия между PUT и POST:
- PUT: используется для обновления ресурса. Предполагается, что данные, которые мы отправляем, полностью заменяют существующий объект.
- POST: используется для создания нового объекта или выполнения сложных операций на сервере.
Представьте, что PUT — это словно "переписать полностью", а POST — это "добавить что-то новое".
Пример сценария:
У нас есть пользователь с ID 123, и мы хотим изменить его имя на "Иван Иванов". Это типичная задача для PUT-запроса, так как мы обновляем уже существующую запись.
Асинхронная обработка PUT-запросов в FastAPI
FastAPI предоставляет нативную поддержку обработки PUT-запросов с помощью декоратора @app.put. Давайте разберём всё на практике.
Пример базового PUT-запроса
Для начала создадим эндпоинт, который позволяет обновлять данные о пользователе. Предположим, у нас есть простой словарь, хранящий информацию о пользователях:
from fastapi import FastAPI
app = FastAPI()
# Пример данных о пользователях
users = {
1: {"name": "Анна", "age": 25},
2: {"name": "Борис", "age": 30},
}
@app.put("/users/{user_id}")
async def update_user(user_id: int, updated_data: dict):
if user_id not in users:
return {"error": "User not found"} # Простая обработка отсутствующего пользователя
# Обновляем данные пользователя
users[user_id] = updated_data
return {"message": "User updated successfully", "data": users[user_id]}
Разберём кусочек кода:
- Маршрут PUT-запроса:
Мы указали@app.put("/users/{user_id}"). Это означает, что запросы с методом PUT и указаннымuser_idбудут обрабатываться этим эндпоинтом. - Параметры пути:
user_idизвлекается из URL как параметр пути. - Тело запроса:
updated_data— это тело запроса, которое передаётся клиентом (например, JSON). Оно содержит данные для обновления. - Обновляем данные:
Мы просто заменяем значение в словареusers.
Добавляем валидацию данных
Использовать "сырой" dict — это как жить без тестов: возможно, но сложно и не очень безопасно. К счастью, FastAPI поддерживает Pydantic для валидации данных. Давайте создадим модель для пользователя:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
Теперь изменим эндпоинт, чтобы использовать эту модель:
@app.put("/users/{user_id}")
async def update_user(user_id: int, updated_data: User):
if user_id not in users:
return {"error": "User not found"}
# Обновляем данные пользователя, используя Pydantic-модель
users[user_id] = updated_data.dict()
return {"message": "User updated successfully", "data": users[user_id]}
- Теперь
updated_dataавтоматически проверяется на соответствие моделиUser: имя должно быть строкой, возраст — целым числом. - В случае некорректных данных (например, если возраст указан строкой), FastAPI автоматически вернёт ошибку 422 с подробным описанием.
Обновление данных частично: когда нужен PATCH?
Может возникнуть вопрос: "А что если я хочу изменить только имя, не трогая возраст?". Для этого лучше подходит метод PATCH, который позволяет частично обновить данные. Мы разберём PATCH позже в курсе, а пока обратите внимание, что стандартный PUT-запрос предполагает полное замещение данных.
Работа с базой данных: асинхронный пример
Давайте теперь представим, что мы используем базу данных для хранения данных. В качестве примера возьмём SQLite с библиотекой databases.
Установка зависимостей:
pip install databases
Пример асинхронного обновления в базе данных:
from databases import Database
DATABASE_URL = "sqlite:///./test.db"
database = Database(DATABASE_URL)
# Таблица пользователей
users_table = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL
)
"""
@app.on_event("startup")
async def startup():
await database.connect()
await database.execute(users_table)
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.put("/users/{user_id}")
async def update_user(user_id: int, updated_data: User):
query = "UPDATE users SET name = :name, age = :age WHERE id = :id"
values = {"name": updated_data.name, "age": updated_data.age, "id": user_id}
# Выполняем асинхронный запрос к базе данных
result = await database.execute(query=query, values=values)
if result == 0:
return {"error": "User not found"}
return {"message": "User updated successfully"}
- База данных:
Мы используем SQLite в качестве базы данных и управляем ей через библиотекуdatabases. - Асинхронные функции:
Запросы к базе данных выполняются асинхронно (await database.execute()). - Обновление записи:
Мы обновляем данные пользователя поuser_idс помощью SQL-запросаUPDATE.
Ошибки и исключения при работе с PUT-запросами
При попытке обновить несуществующий ресурс (например, user_id = 999), сервер должен вернуть соответствующую ошибку. В FastAPI это можно сделать с помощью HTTPException:
from fastapi import HTTPException
if user_id not in users:
raise HTTPException(status_code=404, detail="User not found")
Чрезмерное обновление
Использование PUT для "частичного" обновления (например, только имени) может привести к непредсказуемым результатам, если не указать остальные поля. Всегда проверяйте, что передаваемые данные соответствуют модели.
Практическое применение
PUT-запросы широко используются в реальных проектах. Например:
- Редактирование профиля пользователя: обновление имени, электронной почты и других полей.
- Изменение информации о продукте: обновление цены, описания или характеристик.
- Обновление настроек приложения: сохранение пользовательских предпочтений.
Теперь вы знаете, как работать с PUT-запросами, использовать Pydantic для валидации данных и взаимодействовать с базой данных асинхронно. В следующей лекции мы перейдём к удалению данных через DELETE-запросы, а пока можете потренироваться обновлять свои данные (без нарушений GDPR, конечно 😄).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ