本文基于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
不属于UserInDB
的attribute
,所以最后结果并没有
{'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]
对于这个代码,访问apple
与 orange
都是正常相应
{'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
具体需要用到什么状态码等用的时候现场百度就好,没必要死记硬背。