Modelos Extra¶
🌐 Traducción por IA y humanos
Esta traducción fue hecha por IA guiada por humanos. 🤝
Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖
Puedes mejorar esta traducciónayudándonos a guiar mejor al LLM de IA.
Continuando con el ejemplo anterior, será común tener más de un modelo relacionado.
Esto es especialmente el caso para los modelos de usuario, porque:
- Elmodelo de entrada necesita poder tener una contraseña.
- Elmodelo de salida no debería tener una contraseña.
- Elmodelo de base de datos probablemente necesitaría tener una contraseña hasheada.
Peligro
Nunca almacenes contraseñas de usuarios en texto plano. Siempre almacena un "hash seguro" que puedas verificar luego.
Si no lo sabes, aprenderás qué es un "hash de contraseña" en loscapítulos de seguridad.
Múltiples modelos¶
Aquí tienes una idea general de cómo podrían ser los modelos con sus campos de contraseña y los lugares donde se utilizan:
fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserIn(BaseModel):username:strpassword:stremail:EmailStrfull_name:str|None=NoneclassUserOut(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserInDB(BaseModel):username:strhashed_password:stremail:EmailStrfull_name:str|None=Nonedeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_savedAcerca de**user_in.model_dump()¶
.model_dump() de Pydantic¶
user_in es un modelo Pydantic de la claseUserIn.
Los modelos Pydantic tienen un método.model_dump() que devuelve undict con los datos del modelo.
Así que, si creamos un objeto Pydanticuser_in como:
user_in=UserIn(username="john",password="secret",email="john.doe@example.com")y luego llamamos a:
user_dict=user_in.model_dump()ahora tenemos undict con los datos en la variableuser_dict (es undict en lugar de un objeto modelo Pydantic).
Y si llamamos a:
print(user_dict)obtendríamos undict de Python con:
{'username':'john','password':'secret','email':'john.doe@example.com','full_name':None,}Desempaquetando undict¶
Si tomamos undict comouser_dict y lo pasamos a una función (o clase) con**user_dict, Python lo "desempaquetará". Pasará las claves y valores deluser_dict directamente como argumentos clave-valor.
Así que, continuando con eluser_dict anterior, escribir:
UserInDB(**user_dict)sería equivalente a algo como:
UserInDB(username="john",password="secret",email="john.doe@example.com",full_name=None,)O más exactamente, usandouser_dict directamente, con cualquier contenido que pueda tener en el futuro:
UserInDB(username=user_dict["username"],password=user_dict["password"],email=user_dict["email"],full_name=user_dict["full_name"],)Un modelo Pydantic a partir del contenido de otro¶
Como en el ejemplo anterior obtuvimosuser_dict deuser_in.model_dump(), este código:
user_dict=user_in.model_dump()UserInDB(**user_dict)sería equivalente a:
UserInDB(**user_in.model_dump())...porqueuser_in.model_dump() es undict, y luego hacemos que Python lo "desempaquete" al pasarlo aUserInDB con el prefijo**.
Así, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic.
Desempaquetando undict y palabras clave adicionales¶
Y luego agregando el argumento de palabra clave adicionalhashed_password=hashed_password, como en:
UserInDB(**user_in.model_dump(),hashed_password=hashed_password)...termina siendo como:
UserInDB(username=user_dict["username"],password=user_dict["password"],email=user_dict["email"],full_name=user_dict["full_name"],hashed_password=hashed_password,)Advertencia
Las funciones adicionales de soportefake_password_hasher yfake_save_user son solo para demostrar un posible flujo de datos, pero por supuesto no proporcionan ninguna seguridad real.
Reducir duplicación¶
Reducir la duplicación de código es una de las ideas centrales enFastAPI.
Ya que la duplicación de código incrementa las posibilidades de bugs, problemas de seguridad, problemas de desincronización de código (cuando actualizas en un lugar pero no en los otros), etc.
Y estos modelos están compartiendo muchos de los datos y duplicando nombres y tipos de atributos.
Podríamos hacerlo mejor.
Podemos declarar un modeloUserBase que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (declaraciones de tipos, validación, etc).
Toda la conversión de datos, validación, documentación, etc. seguirá funcionando normalmente.
De esa manera, podemos declarar solo las diferencias entre los modelos (conpassword en texto plano, conhashed_password y sin contraseña):
fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserBase(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserIn(UserBase):password:strclassUserOut(UserBase):passclassUserInDB(UserBase):hashed_password:strdeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_savedUnion oanyOf¶
Puedes declarar un response que sea laUnion de dos o más tipos, eso significa que el response sería cualquiera de ellos.
Se definirá en OpenAPI conanyOf.
Para hacerlo, usa la anotación de tipos estándar de Pythontyping.Union:
Nota
Al definir unaUnion, incluye el tipo más específico primero, seguido por el tipo menos específico. En el ejemplo a continuación, el más específicoPlaneItem viene antes deCarItem enUnion[PlaneItem, CarItem].
fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classBaseItem(BaseModel):description:strtype:strclassCarItem(BaseItem):type:str="car"classPlaneItem(BaseItem):type:str="plane"size:intitems={"item1":{"description":"All my friends drive a low rider","type":"car"},"item2":{"description":"Music is my aeroplane, it's my aeroplane","type":"plane","size":5,},}@app.get("/items/{item_id}",response_model=PlaneItem|CarItem)asyncdefread_item(item_id:str):returnitems[item_id]Union en Python 3.10¶
En este ejemplo pasamosUnion[PlaneItem, CarItem] como el valor del argumentoresponse_model.
Porque lo estamos pasando como unvalor a un argumento en lugar de ponerlo en unaanotación de tipos, tenemos que usarUnion incluso en Python 3.10.
Si estuviera en una anotación de tipos podríamos haber usado la barra vertical, como:
some_variable:PlaneItem|CarItemPero si ponemos eso en la asignaciónresponse_model=PlaneItem | CarItem obtendríamos un error, porque Python intentaría realizar unaoperación inválida entrePlaneItem yCarItem en lugar de interpretar eso como una anotación de tipos.
Lista de modelos¶
De la misma manera, puedes declarar responses de listas de objetos.
Para eso, usa lalist estándar de Python:
fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classItem(BaseModel):name:strdescription:stritems=[{"name":"Foo","description":"There comes my hero"},{"name":"Red","description":"It's my aeroplane"},]@app.get("/items/",response_model=list[Item])asyncdefread_items():returnitemsResponse condict arbitrario¶
También puedes declarar un response usando undict arbitrario plano, declarando solo el tipo de las claves y valores, sin usar un modelo Pydantic.
Esto es útil si no conoces los nombres de los campos/atributos válidos (que serían necesarios para un modelo Pydantic) de antemano.
En este caso, puedes usardict:
fromfastapiimportFastAPIapp=FastAPI()@app.get("/keyword-weights/",response_model=dict[str,float])asyncdefread_keyword_weights():return{"foo":2.3,"bar":3.4}Recapitulación¶
Usa múltiples modelos Pydantic y hereda libremente para cada caso.
No necesitas tener un solo modelo de datos por entidad si esa entidad debe poder tener diferentes "estados". Como el caso con la "entidad" usuario con un estado que incluyepassword,password_hash y sin contraseña.







