Body - Actualizaciones¶
Actualizar reemplazando con PUT¶
Para actualizar un item puedes usar la operación HTTP PUT.
Puedes usar jsonable_encoder para convertir los datos de entrada a datos que pueden almacenarse como JSON (por ejemplo, con una base de datos NoSQL). Por ejemplo, convirtiendo datetime a 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 se usa para recibir datos que deben reemplazar los datos existentes.
Advertencia sobre reemplazar¶
Eso significa que si quieres actualizar el item bar usando PUT con un body que contenga:
{
"name": "Barz",
"price": 3,
"description": None,
}
porque no incluye el atributo ya almacenado "tax": 20.2, el modelo de entrada tomaría el valor por defecto de "tax": 10.5.
Y los datos se guardarían con ese "nuevo" tax de 10.5.
Actualizaciones parciales con PATCH¶
También puedes usar la operación HTTP PATCH para actualizar datos parcialmente.
Esto significa que puedes enviar solo los datos que quieres actualizar, dejando el resto intacto.
Nota
PATCH es menos comúnmente usado y conocido que PUT.
Y muchos equipos usan solo PUT, incluso para actualizaciones parciales.
Eres libre de usarlos como quieras, FastAPI no impone ninguna restricción.
Pero esta guía te muestra, más o menos, cómo se pretende usarlos.
Usar el parámetro exclude_unset de Pydantic¶
Si quieres recibir actualizaciones parciales, es muy útil usar el parámetro exclude_unset en el .model_dump() del modelo de Pydantic.
Como item.model_dump(exclude_unset=True).
Eso generaría un dict con solo los datos que se establecieron al crear el modelo item, excluyendo los valores por defecto.
Luego puedes usar esto para generar un dict con solo los datos que se establecieron (enviados en la petición), omitiendo los valores por defecto:
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
Usar el parámetro update de Pydantic¶
Ahora, puedes crear una copia del modelo existente usando .model_copy(), y pasar el parámetro update con un dict conteniendo los datos a actualizar.
Como 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
Resumen de actualizaciones parciales¶
En resumen, para aplicar actualizaciones parciales harías:
- (Opcionalmente) usar
PATCHen lugar dePUT. - Recuperar los datos almacenados.
- Poner esos datos en un modelo Pydantic.
- Generate a
dictwithout default values from the input model (usingexclude_unset).- De esta manera puedes actualizar solo los valores realmente establecidos por el usuario, en lugar de sobrescribir valores ya almacenados con valores por defecto en tu modelo.
- Crear una copia del modelo almacenado, actualizando sus atributos con las actualizaciones parciales recibidas (usando el parámetro
update). - Convert the copied model to something that can be stored in your DB (for example, using the
jsonable_encoder).- Esto es comparable a usar el método
.model_dump()del modelo de nuevo, pero se asegura (y convierte) los valores a tipos de datos que pueden convertirse a JSON, por ejemplo,datetimeastr.
- Esto es comparable a usar el método
- Guardar los datos en tu DB.
- Devolver el modelo actualizado.
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
Consejo
En realidad puedes usar esta misma técnica con una operación HTTP PUT.
Pero el ejemplo aquí usa PATCH porque fue creado para estos casos de uso.
Nota
Nota que el modelo de entrada sigue siendo validado.
Así que, si quieres recibir actualizaciones parciales que pueden omitir todos los atributos, necesitas tener un modelo con todos los atributos marcados como opcionales (con valores por defecto o None).
Para distinguir entre los modelos con todos los valores opcionales para actualizaciones y los modelos con valores requeridos para creación, puedes usar las ideas descritas en Extra Models.