FastAPI 教程翻译 - 用户指南 16 - 额外模型
FastAPI Tutorial - User Guide - Extra Models
Continuing with the previous example, it will be common to have more than one related model.
继续前面的示例,通常会有多个相关模型。
This is especially the case for user models, because:
用户模型尤其如此,因为:
-
The input model needs to be able to have a password.
输入模型必须具有密码。
-
The output model should not have a password.
输出模型不应具有密码。
-
The database model would probably need to have a hashed password.
数据库模型可能需要具有哈希密码。
Danger
危险
Never store user’s plaintext passwords. Always store a “secure hash” that you can then verify.
切勿存储用户的明文密码。始终存储『安全哈希』,然后可以进行验证。
If you don’t know, you will learn what a “password hash” is in the security chapters.
如果不知道,您将在 安全性章节 中了解『密码哈希』是什么。
Multiple models
多种模型
Here’s a general idea of how the models could look like with their password fields and the places where they are used:
这是关于模型的密码字段和使用位置的大致概念:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: str = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(*, user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
About **user_in.dict()
关于 **user_in.dict()
Pydantic’s .dict()
Pydantic 的 .dict()
user_in
is a Pydantic model of class UserIn
.
user_in
是类 UserIn
的 Pydantic 模型。
Pydantic models have a .dict()
method that returns a dict
with the model’s data.
Pydantic 模型具有一个 .dict()
方法,该方法会返回一个带有模型数据的 dict
。
So, if we create a Pydantic object user_in
like:
因此,如果我们创建一个 Pydantic 对象 user_in
,例如:
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
and then we call:
然后我们调用:
user_dict = user_in.dict()
we now have a dict
with the data in the variable user_dict
(it’s a dict
instead of a Pydantic model object).
现在,我们有一个 dict
,其数据位于变量 user_dict
中(这是一个 dict
,而不是 Pydantic 模型对象)。
And if we call:
如果我们调用:
print(user_dict)
we would get a Python dict
with:
我们将得到一个 Python 的 dict
:
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
Unwrapping a dict
解包 dict
If we take a dict
like user_dict
and pass it to a function (or class) with **user_dict
, Python will “unwrap” it. It will pass the keys and values of the user_dict
directly as key-value arguments.
如果我们采用 user_dict
这样的 dict
并将其传递给带有 **user_dict
的函数(或类),Python 将对其进行『解包』。它将直接传递 user_dict
的键和值作为键值参数。
So, continuing with the user_dict
from above, writing:
因此,继续上面的 user_dict
,编写:
UserInDB(**user_dict)
Would result in something equivalent to:
会导致以下结果:
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
Or more exactly, using user_dict
directly, with whatever contents it might have in the future:
更确切地说,直接使用 user_dict
,以及将来可能包含的任何内容:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
A Pydantic model from the contents of another
来自另一个内容的 Pydantic 模型
As in the example above we got user_dict
from user_in.dict()
, this code:
如上例所示,我们从 user_in.dict()
中获得了 user_dict
,此代码:
user_dict = user_in.dict()
UserInDB(**user_dict)
would be equivalent to:
等同于:
UserInDB(**user_in.dict())
…because user_in.dict()
is a dict
, and then we make Python “unwrap” it by passing it to UserInDB
prepended with **
.
…… 因为 user_in.dict()
是 dict
,然后我们通过将 Python 传递给以 **
开头的 UserInDB
来使其『解包』。
So, we get a Pydantic model from the data in another Pydantic model.
因此,我们从另一个 Pydantic 模型中的数据中获得了 Pydantic 模型。
Unwrapping a dict
and extra keywords
解包 dict
和额外的关键字
And then adding the extra keyword argument hashed_password=hashed_password
, like in:
然后添加额外的关键字参数 hashed_password=hashed_password
,例如:
UserInDB(**user_in.dict(), hashed_password=hashed_password)
…ends up being like:
…… 最终就像:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
Warning
警告
The supporting additional functions are just to demo a possible flow of the data, but they of course are not providing any real security.
支持的附加功能仅仅是演示数据流的可能性,但是它们当然并不能提供任何真正的安全性。
Reduce duplication
减少重复
Reducing code duplication is one of the core ideas in FastAPI.
减少代码重复是 FastAPI
的核心思想之一。
As code duplication increments the chances of bugs, security issues, code desynchronization issues (when you update in one place but not in the others), etc.
随着代码重复的增加,错误、安全性问题,代码不同步问题(当您在一个地方进行更新而不是在另一个地方进行更新)等问题的机会也将增加。
And these models are all sharing a lot of the data and duplicating attribute names and types.
这些模型都共享大量数据,并复制属性名称和类型。
We could do better.
我们可以做得更好。
We can declare a UserBase
model that serves as a base for our other models. And then we can make subclasses of that model that inherit its attributes (type declarations, validation, etc).
我们可以声明一个 UserBase
模型作为其他模型的基础。然后,我们可以使该模型的子类继承其属性(类型声明、验证等)。
All the data conversion, validation, documentation, etc. will still work as normally.
所有数据转换、验证、文档等仍将正常运行。
That way, we can declare just the differences between the models (with plaintext password
, with hashed_password
and without password):
这样,我们可以声明模型之间的差异(使用明文 password
,使用 hashed_password
和不使用密码):
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(*, user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
Union
or anyOf
Union
或 anyOf
You can declare a response to be the Union
of two types, that means, that the response would be any of the two.
您可以将响应声明为两种类型的 Union
,这意味着该响应将是两种类型中的任何一种。
It will be defined in OpenAPI with anyOf
.
将在 OpenAPI 中使用 anyOf
进行定义。
To do that, use the standard Python type hint typing.Union
:
为此,请使用标准的 Python 类型提示 typing.Union
:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type = "car"
class PlaneItem(BaseItem):
type = "plane"
size: int
items = {
"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=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
List of models
型号清单
The same way, you can declare responses of lists of objects.
同样,您可以声明对象列表的响应。
For that, use the standard Python typing.List
:
为此,请使用标准的 Python typing.List
:
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
Response with arbitrary dict
带有任意 dict
的响应
You can also declare a response using a plain arbitrary dict
, declaring just the type of the keys and values, without using a Pydantic model.
您还可以使用简单的任意 dict
声明响应,仅声明键和值的类型,而无需使用 Pydantic 模型。
This is useful if you don’t know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.
如果您事先不知道有效的字段/属性名称(Pydantic 模型需要此名称),这将很有用。
In this case, you can use typing.Dict
:
在这种情况下,您可以使用 typing.Dict
:
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=Dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
Recap
回顾
Use multiple Pydantic models and inherit freely for each case.
使用多个 Pydantic 模型,并针对每种情况自由继承。
You don’t need to have a single data model per entity if that entity must be able to have different “states”. As the case with the user “entity” with a state including password
, password_hash
and no password.
如果每个实体必须能够具有不同的『状态』,则不需要每个实体具有单个数据模型。以用户『实体』为例,其状态包括 password
、password_hash
和没有密码。