Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен PATCH запрос
PATCH и PUT часто путают. Оба используют HTTP метод POST-подобный для обновления, но они принципиально отличаются философией и применением.
PATCH vs PUT: ключевое различие
PUT: Полное замещение (Replace)
PUT /users/123 с телом {"name": "Alice", "email": "alice@example.com"}
Текущее состояние:
{
"id": 123,
"name": "Bob",
"email": "bob@example.com",
"age": 30,
"verified": true
}
После PUT:
{
"id": 123, // id НЕ меняется (primary key)
"name": "Alice", // ← заменено
"email": "alice@example.com", // ← заменено
"age": null, // ← стёрто (не было в запросе)
"verified": null // ← стёрто
}
Проблема: потеряем age и verified!
PATCH: Частичное обновление (Merge)
PATCH /users/123 с телом {"name": "Alice", "email": "alice@example.com"}
Текущее состояние:
{
"id": 123,
"name": "Bob",
"email": "bob@example.com",
"age": 30,
"verified": true
}
После PATCH:
{
"id": 123, // не меняется
"name": "Alice", // ← обновлено
"email": "alice@example.com", // ← обновлено
"age": 30, // ← осталось (не было в запросе, не меняется)
"verified": true // ← осталось
}
Преимущество: сохраняем остальные поля!
Реальный пример: обновление профиля пользователя
# ❌ PUT опасен для частичных обновлений
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class UserUpdate(BaseModel):
name: str | None = None
email: str | None = None
age: int | None = None
verified: bool | None = None
@app.put("/users/{user_id}")
def update_user_put(user_id: int, data: UserUpdate):
user = get_user(user_id)
# PUT ожидает ВСЕХ полей
# Если data.age == None, мы сотрём age
user.name = data.name # Если None — стираем
user.email = data.email # Если None — стираем
user.age = data.age # ❌ Проблема
user.verified = data.verified # ❌ Проблема
db.session.commit()
return user
# Клиент хочет только обновить name:
PUT /users/123
{"name": "Alice"}
# Результат: age и verified становятся NULL!
# Потеря данных!
# ✅ PATCH правильен для частичных обновлений
@app.patch("/users/{user_id}")
def update_user_patch(user_id: int, data: UserUpdate):
user = get_user(user_id)
# PATCH: обновляем только переданные поля
if data.name is not None:
user.name = data.name
if data.email is not None:
user.email = data.email
if data.age is not None:
user.age = data.age
if data.verified is not None:
user.verified = data.verified
db.session.commit()
return user
# Клиент отправляет:
PATCH /users/123
{"name": "Alice"}
# Результат: только name меняется, остальное остаётся!
# Безопасно!
Правильная реализация PATCH
from typing import Any
from pydantic import BaseModel
class UserUpdate(BaseModel):
"""Update schema для PATCH"""
name: str | None = None
email: str | None = None
age: int | None = None
verified: bool | None = None
@app.patch("/users/{user_id}")
def patch_user(user_id: int, update_data: UserUpdate):
user = User.query.get(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Способ 1: Через dict.update (чистый и Pythonic)
update_dict = update_data.model_dump(exclude_unset=True) # Только переданные поля
for key, value in update_dict.items():
setattr(user, key, value)
db.session.commit()
return user
# Способ 2: SQLAlchemy update
from sqlalchemy import update
@app.patch("/users/{user_id}")
def patch_user_v2(user_id: int, update_data: UserUpdate):
update_dict = update_data.model_dump(exclude_unset=True)
db.session.execute(
update(User).where(User.id == user_id).values(**update_dict)
)
db.session.commit()
return User.query.get(user_id)
# Способ 3: Явно для каждого поля (verbose, но понятно)
@app.patch("/users/{user_id}")
def patch_user_v3(user_id: int, update_data: UserUpdate):
user = User.query.get(user_id)
# Обновляем только если значение передано
if update_data.name is not None:
user.name = update_data.name
if update_data.email is not None:
user.email = update_data.email
if update_data.age is not None:
user.age = update_data.age
if update_data.verified is not None:
user.verified = update_data.verified
db.session.commit()
return user
Различия в таблице
| Аспект | PUT | PATCH |
|---|---|---|
| Семантика | Полное замещение | Частичное обновление |
| Требования | Все поля обязательны | Только изменённые поля |
| Безопасность | Может привести к потере данных | Безопаснее |
| Идемпотентность | Идемпотентен | Может быть не идемпотентен* |
| Тело запроса | Полный объект | Только изменённые поля |
| Использование | Замена всего ресурса | Частичное обновление |
| Пример | PUT /resource → весь объект | PATCH /resource → только изменения |
*Про идемпотентность позже.
Реальные примеры
Пример 1: Обновление статуса заказа
# ✅ PATCH
PATCH /orders/456
{"status": "shipped", "tracking_number": "12345ABC"}
# Остальные поля (items, customer, total_price) остаются без изменений
# ❌ PUT был бы неправильным:
PUT /orders/456
{"status": "shipped", "tracking_number": "12345ABC"}
# Потеряем items, customer, total_price!
Пример 2: Обновление настроек пользователя
# ✅ PATCH
PATCH /settings/user/123
{"theme": "dark", "notifications": false}
# Остальные настройки (language, timezone, privacy) не меняются
# PUT опасен:
PUT /settings/user/123
{"theme": "dark", "notifications": false}
# Потеряем language и timezone!
Пример 3: Обновление товара в каталоге
# ✅ PATCH для изменения цены
PATCH /products/789
{"price": 99.99}
# Сохранится description, images, category и т.д.
# PUT для полной замены:
PUT /products/789
{
"name": "Product Name",
"description": "Long description",
"price": 99.99,
"category": "Electronics",
"images": [...],
"stock": 100
}
# Используется когда хочешь полностью переписать товар
Проблема: идемпотентность
# PUT идемпотентен (повтор даёт тот же результат)
PUT /users/123 {"name": "Alice", "age": 30}
# Результат: {id: 123, name: "Alice", age: 30, ...}
PUT /users/123 {"name": "Alice", "age": 30}
# Результат: ТОТ ЖЕ {id: 123, name: "Alice", age: 30, ...}
# Идемпотентно!
# PATCH НЕ идемпотентен в общем случае
PATCH /counters/1 {"value": "+1"} # Increment
// Результат: {value: 5}
PATCH /counters/1 {"value": "+1"} // Повторили
// Результат: {value: 6} // ДРУГОЙ результат!
// Не идемпотентно!
# Но PATCH с абсолютными значениями идемпотентен:
PATCH /counters/1 {"value": 5} // SET, не INCREMENT
// Результат: {value: 5}
PATCH /counters/1 {"value": 5} // Повторили
// Результат: {value: 5} // ТОТ ЖЕ!
// Идемпотентно!
REST стандарты (RFC 7231, 5789)
PUT (RFC 7231):
- Замещает весь ресурс
- Идемпотентен
- Требует полное состояние
PATCH (RFC 5789):
- Применяет частичные изменения
- Может не быть идемпотентным
- Требует JSON Patch (RFC 6902) или JSON Merge Patch (RFC 7386)
JSON Merge Patch (стандартный способ)
Current:
{"name": "Alice", "age": 30, "email": "alice@example.com"}
PATCH запрос с JSON Merge Patch:
{"age": 31}
Результат:
{"name": "Alice", "age": 31, "email": "alice@example.com"}
# Merge происходит на уровне JSON объекта
Реализация в FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
class UserUpdate(BaseModel):
name: str | None = None
age: int | None = None
email: str | None = None
@app.patch("/users/{user_id}")
def patch_user(user_id: int, update: UserUpdate):
user = get_user(user_id)
# JSON Merge Patch: объедини переданные поля
update_data = update.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(user, key, value)
save_user(user)
return user
Когда использовать что
Используй PUT когда:
- Клиент отправляет полное состояние ресурса
- Все поля обязательны
- Хочешь идемпотентное обновление
- Замещаешь весь ресурс
# Пример: загрузка файла конфигурации
PUT /config
{
"database_url": "postgresql://...",
"api_key": "secret",
"debug_mode": true
// ВСЕ параметры
}
Используй PATCH когда:
- Клиент хочет изменить несколько полей
- Не все поля обязательны
- Хочешь сохранить остальные поля
- Защищен от потери данных
# Пример: обновление профиля пользователя
PATCH /profile
{
"bio": "New bio",
"avatar_url": "..."
// Только изменённые поля, остальное остаётся
}
Вывод
PATCH нужен для защиты от потери данных при частичных обновлениях.
Основные преимущества:
- ✅ Безопаснее (не теряем поля)
- ✅ Эффективнее (отправляем только изменённое)
- ✅ Удобнее для клиента (не нужно знать все поля)
- ✅ Семантически правильнее (частичное изменение)
Основное различие:
- PUT = полная замена
- PATCH = частичное обновление
В 90% случаев современных API используется PATCH для любых обновлений. PUT оставлен для специальных случаев полной замены ресурса.