Extra Models


Extra Models

有时候我们常常会有不止一个关系模型

用户模型尤其如此,因为:

  • input model 需要有密码字段
  • output model 不应该有密码字段
  • database model 需要有哈希密码字段

警告:永远不要存储用户的明文密码,应该总是存储可以验证的安全哈希

Multiple models

下面的例子是关于在用户模型中如何放置密码字段的通用思想

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

关于 **user_in.dict()

user_in 是 类 UserInPydantic 模型实例

Pydantic 模型的 .dict() 方法返回一个含有模型数据的字典

例如,如果我们创建一个 Pydantic 对象 user_in

user_in = UserIn(username="john", password="secret", email="john.doe@example.com")

接着调用

user_dict = user_in.dict()

那么 user_dict 实际上是一个 Python dict,其内容为

{
    'username': 'john',
    'password': 'secret',
    'email': 'john.doe@example.com',
    'full_name': None,
}

Unwrapping a dict

如果我们在函数调用或者类实例化时,用 **user_dict 的方法传递像 user_dict 这样的字典参数,Python 将会对其解包,即以关键字参数的方式传递 user_dict 的所有键和值

在上面的例子中,使用

UserInDB(**user_dict)

相当于

UserInDB(
    username="john",
    password="secret",
    email="john.doe@example.com",
    full_name=None,
)

更准确的说,是

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
)

Unwrapping a dict and extra keywords

在上面的例子中,我们除了进行解包 **user_in.dict(),还添加了额外的关键字参数

UserInDB(**user_in.dict(), hashed_password=hashed_password)

则实际上传递的参数为

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
    hashed_password = hashed_password,
)

Reduce duplication

FastAPI 的核心宗旨之一是减少代码冗余

因为代码冗余会增加出现 bug,安全问题,代码不同步问题(在某一处更新代码,而其他的地方没有)的几率

在上面的例子中,模型共享了具有大量重复属性名字和类型的数据

我们可以做得更好

我们可以声明一个 UserBase 模型作为其他模型的基本模型。然后,我们可以使该模型的子类继承其属性(类型声明,验证等)

所有的数据转换、验证、自动文档等都会正常工作

这样的话,我们只需要声明这些模型不同的部分(passwordhashed_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

我们可以声明相应为两种类型的联合,即,相应为两种类型之一

OpenAPI 中将会使用 anyOf 来定义

要做到这一点,可以使用 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

同样,我们可以声明返回一组对象

要做到这一点,使用 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 声明返回响应,在 dict 中,只需要声明键和值的类型,而不需要使用 Pydantic 模型

如果事先不知道有效的字段/属性名称(Pydantic模型需要此名称),这将很有用。

例如

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}

文章作者: qiufeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 qiufeng !
评论
  目录