Перейти к содержанию

Тело запроса - Обновления

🌐 Перевод выполнен с помощью ИИ и людей

Этот перевод был сделан ИИ под руководством людей. 🤝

В нем могут быть ошибки из-за неправильного понимания оригинального смысла или неестественности и т. д. 🤖

Вы можете улучшить этот перевод, помогая нам лучше направлять ИИ LLM.

Английская версия

Обновление с заменой при помощи PUT

Чтобы обновить элемент, вы можете использовать операцию HTTP PUT.

Вы можете использовать jsonable_encoder, чтобы преобразовать входные данные в данные, которые можно сохранить как JSON (например, в NoSQL-базе данных). Например, преобразование datetime в str.

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str | None = None
    description: str | None = None
    price: float | None = None
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    update_item_encoded = jsonable_encoder(item)
    items[item_id] = update_item_encoded
    return update_item_encoded

PUT используется для получения данных, которые должны заменить существующие данные.

Предупреждение о замене

Это означает, что если вы хотите обновить элемент bar, используя PUT с телом, содержащим:

{
    "name": "Barz",
    "price": 3,
    "description": None,
}

поскольку оно не включает уже сохраненный атрибут "tax": 20.2, входная модель примет значение по умолчанию "tax": 10.5.

И данные будут сохранены с этим «новым» tax, равным 10.5.

Частичное обновление с помощью PATCH

Также можно использовать операцию HTTP PATCH для частичного обновления данных.

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

Примечание

PATCH менее распространен и известен, чем PUT.

А многие команды используют только PUT, даже для частичного обновления.

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

Но в данном руководстве более или менее понятно, как они должны использоваться.

Использование параметра exclude_unset в Pydantic

Если вы хотите получать частичные обновления, очень полезно использовать параметр exclude_unset в .model_dump() модели Pydantic.

Например, item.model_dump(exclude_unset=True).

В результате будет сгенерирован dict, содержащий только те данные, которые были заданы при создании модели item, без учета значений по умолчанию.

Затем вы можете использовать это для создания dict только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str | None = None
    description: str | None = None
    price: float | None = None
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}")
async def update_item(item_id: str, item: Item) -> Item:
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.model_dump(exclude_unset=True)
    updated_item = stored_item_model.model_copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

Использование параметра update в Pydantic

Теперь можно создать копию существующей модели, используя .model_copy(), и передать параметр update с dict, содержащим данные для обновления.

Например, stored_item_model.model_copy(update=update_data):

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str | None = None
    description: str | None = None
    price: float | None = None
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}")
async def update_item(item_id: str, item: Item) -> Item:
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.model_dump(exclude_unset=True)
    updated_item = stored_item_model.model_copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

Кратко о частичном обновлении

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

  • (Опционально) использовать PATCH вместо PUT.
  • Извлечь сохранённые данные.
  • Поместить эти данные в Pydantic-модель.
  • Сгенерировать dict без значений по умолчанию из входной модели (с использованием exclude_unset).
    • Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять уже сохраненные значения значениями по умолчанию из вашей модели.
  • Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра update).
  • Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью jsonable_encoder).
    • Это сравнимо с повторным использованием метода модели .model_dump(), но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, datetime в str.
  • Сохранить данные в своей БД.
  • Вернуть обновленную модель.
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str | None = None
    description: str | None = None
    price: float | None = None
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}")
async def update_item(item_id: str, item: Item) -> Item:
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.model_dump(exclude_unset=True)
    updated_item = stored_item_model.model_copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

Подсказка

На самом деле эту же технику можно использовать и для операции HTTP PUT.

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

Примечание

Обратите внимание, что входная модель по-прежнему валидируется.

Таким образом, если вы хотите получать частичные обновления, в которых могут быть опущены все атрибуты, вам необходимо иметь модель, в которой все атрибуты помечены как необязательные (со значениями по умолчанию или None).

Чтобы отличить модели со всеми необязательными значениями для обновления и модели с обязательными значениями для создания, можно воспользоваться идеями, описанными в Дополнительные модели.