初识FastAPI(三)

本文基于Datawhale的开源学习项目wow-fullstac的子项目FastAPI
如果文中出现了错误以及不理解的地方欢迎在留言

响应模型(response model)

FastAPI 将使用 response_model 来:

  • 将输出数据转换为其声明的类型。
  • 校验数据。

修改代码添加response_model=Item并且改为return item

@app.put("/items_rsp/{item_name}", response_model=Item)
async def update_item(
    item_name: str,
    item: Item,
    buyer: Buyer,
    month: Annotated[int, Body(ge=6, le=12, title="It's time to eat apple!")],
):
    return item

构造put部分

import requests

url = "http://127.0.0.1:8000/items_rsp/apple"
data = {
    "item": {"name": "apple", "description": "Big red apple", "price": 15, "tax": 5},
    "buyer": {"buyername": "Jack", "location": "the DC"},
    "month": 6,
}

response = requests.put(url, json=data)
print(response.json())

返回结果

{'name': 'apple', 'description': 'Big red apple', 'price': 15.0, 'tax': 5.0}

能正常返回是因为在返回之前FastAPI通过response_model=Item对 return 的结果进行了校验,二者类型一致才能成功返回结果,
假设我们修改response_model=list[Item],然后会得到下面的错误,因为我们return的内容与reponse_model类型不同

INFO:     127.0.0.1:57606 - "PUT /items_rsp/apple HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application

使用不同模型输入输出

新建一个TotalPrice

class TotalPrice(BaseModel):
    item_name: str
    total_price: float
    description: str | None = None

修改代码

@app.put("/items_rsp/{item_name}", response_model=TotalPrice)
async def update_item(
    item_name: str,
    item: Item,
    buyer: Buyer,
    month: Annotated[int, Body(ge=6, le=12, title="It's time to eat apple!")],
):
    total = TotalPrice
    total.item_name = item.name
    total.total_price = item.price + item.tax
    return total

再次访问我们得到的输出变成了

{'item_name': 'apple', 'total_price': 20.0, 'description': None}

通过构建不同的输入输出模型,不但能够校验信息的准确性,也能保证只提供需要的信息,但是有个很尴尬的问题,就是默认为None的参数也返回回来了。

response_model_exclude_unset参数

通过添加response_model_exclude_unset=True可以避免传递默认值
修改代码

@app.put(
    "/items_rsp/{item_name}",
    response_model=TotalPrice,
    response_model_exclude_unset=True,
)

就可以去掉返回的默认值description

多模型使用

后面的例子我不好编了,直接用官方的例子好了

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    full_name: str | None = 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


if __name__ == "__main__":
    uvicorn.run(app)

**user_in.dict()其实我们并不陌生,经常能看到*args**kwargs,我们知道**kwargs传递的是一个个键值对,.dict()会将其转为字典,不过现在已经弃用了这个函数,我们使用.model_dump()来替代它。举个栗子:

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

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

得到的仍然是字典化的结果

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

接着把user_in.model_dump()解包传递给UserInDB

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    full_name: str | None = None

user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
print(user_in.model_dump())
user_in_db = UserInDB(**user_in.model_dump(), hashed_password="123")
print(user_in_db.model_dump())

得到我们需要的内容,不过需要注意的是,password不属于UserInDBattribute,所以最后结果并没有

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

Union

我们还能够用前面的Union函数进行多个模型选择Union[A,B]等价于A|B

class Fruit(BaseModel):
    type: str
    description: str = "This is a fruit"


class Apple(Fruit):
    type: str = "apple"
    nums: int


class Orange(Fruit):
    type: str = "orange"
    size: int


fruit = {
    "apple": {"description": "This is an apple", "type": "apple", "nums": 5},
    "orange": {
        "description": "This is an orange",
        "type": "orange",
        "size": 3,
    },
}


@app.get("/fruit/{fruit_id}", response_model=Apple | Orange)
async def choose_fruit(fruit_id: str):
    return fruit[fruit_id]

对于这个代码,访问appleorange都是正常相应

{'type': 'apple', 'description': 'This is an apple', 'nums': 5}
{'type': 'orange', 'description': 'This is an orange', 'size': 3}

既然如此,只要我们满足response_model的规定格式就行
比如response_model=dict[str, float]
那么return的内容只要符合是一个字典,并且key是str value是float就行

@app.get("/fruit_price/", response_model=dict[str, float])
async def frult_price():
    return {"apple": 1.99, "orange": 3.99}

可以得到结果

{'apple': 1.99, 'orange': 3.99}

减少重复

既然是python代码,那类的继承自然是理所应当的事情

class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


class UserIn(UserBase):
    password: str


class UserInDB(UserBase):
    hashed_password: str

能少写代码的时候,一定要少写!

响应状态码

打开网页有时候会遇到404 Not Found,404就是状态码,后面跟着的是简短描述

@app.post("/add_fruit/", status_code=FastAPI.status.HTTP_201_CREATED)
async def add_fruit(fruit: str):
    return {"fruit_name": fruit}

尝试访问

import requests

url = "http://127.0.0.1:8000/add_fruit/?fruit=cantaloupe"

response = requests.post(url)
try:
    print(response.json())
except:
    print(response.text)
print(response.status_code)

返回结果

{'fruit_name': 'cantaloupe'}
201

具体需要用到什么状态码等用的时候现场百度就好,没必要死记硬背。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值