以下内容写于2023年8月11日
bug 1
请求体 - 多个参数 - FastAPI (tiangolo.com)中“请求体中的单一值”处,选python3.6,接口示例代码是
from typing import Unionfrom fastapi import Body, FastAPI
from pydantic import BaseModel
from typing_extensions import Annotatedapp = FastAPI()class Item(BaseModel):name: strdescription: Union[str, None] = Noneprice: floattax: Union[float, None] = Noneclass User(BaseModel):username: strfull_name: Union[str, None] = None@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):results = {"item_id": item_id, "item": item, "user": user, "importance": importance}return results
根据给的请求体的示例代码,我写的请求代码是
import requestsurl = 'http://127.0.0.1:8000/items/7'
form_data = {"item": {"name": "Foo","description": "The pretender","price": 42.0,"tax": 3.2},"user": {"username": "dave","full_name": "Dave Grohl"},"importance": 5
}
response = requests.put(url=url, json=form_data)
print(response.status_code)
print(response.text)
若新建一个python3.8的环境(装好是3.8.17,称为likeadmin环境),然后按照likeadmin的requirements.txt安装库,运行服务端接口代码,再运行请求代码,不能得到正确的结果,提示
{"detail":[{"loc":["query","importance"],"msg":"field required","type":"value_error.missing"}]}
若新建一个python3.8的环境(装好是3.8.17,称为纯fastapi环境),然后pip install fastapi[all],运行服务端接口代码,再运行请求代码,能得到正确的结果
{"item_id":7,"item":{"name":"Foo","description":"The pretender","price":42.0,"tax":3.2},"user":{"username":"dave","full_name":"Dave Grohl"},"importance":5}
判断结果是否正确的依据是文档中的一句话“在这种情况下,FastAPI 将期望像这样的请求体”。
likeadmin的环境要想得到正确的结果,查看127.0.0.1:8000/docs得知应把importance用问号拼接在请求url中,因为自动生成的文档显示它是query即查询参数。
总结一下就是likeadmin的环境运行fastapi的示例代码,无法得到fastapi文档中说的预期的结果;而新建一个相同python版本的虚拟环境,里面只pip install fastapi[all],可以得到fastapi文档中说的预期的结果。
截止到2023年8月11日,likeadmin的requirements.txt的内容是
aiofiles==22.1.0
aiomysql==0.1.1
alibabacloud_dysmsapi20170525==2.0.23
alibabacloud_tea_openapi
anyio==3.6.1
async-timeout==4.0.2
click==8.1.3
colorama==0.4.5
commonmark==0.9.1
cos-python-sdk-v5==1.9.22
databases==0.6.1
Deprecated==1.2.13
fastapi==0.85.0
fastapi-cache2==0.1.9
fastapi-pagination==0.10.0
greenlet==1.1.3.post0
h11==0.14.0
httptools==0.5.0
idna==3.4
Jinja2==3.1.2
MarkupSafe==2.1.1
optionaldict==0.1.2
oss2==2.16.0
pendulum==2.1.2
psutil==5.9.3
pydantic==1.10.2
Pygments==2.13.0
PyMySQL==1.0.2
python-dateutil==2.8.2
python-dotenv==0.21.0
python-multipart==0.0.5
pytz==2022.5
pytzdata==2020.1
PyYAML==6.0
qiniu==7.9.0
redis==4.4.0rc2
rich==12.6.0
shellingham==1.5.0
six==1.16.0
sniffio==1.3.0
SQLAlchemy==1.4.41
starlette==0.20.4
tencentcloud-sdk-python==3.0.841
typer==0.6.1
typing-extensions==4.4.0
ua-parser==0.16.1
user-agents==2.2.0
# uvloop==0.17.0
uvicorn==0.18.3
watchfiles==0.17.0
websockets==10.3
wrapt==1.14.1
wechatpy==1.8.18
bug 2
与bug1类似,也是likeadmin的环境运行fastapi的示例代码,无法得到fastapi文档中说的预期的结果;而新建一个相同python版本的虚拟环境,里面只pip install fastapi[all],可以得到fastapi文档中说的预期的结果。
请求体 - 多个参数 - FastAPI (tiangolo.com)中“嵌入单个请求体参数”处,选python3.6,接口示例代码是
from typing import Unionfrom fastapi import Body, FastAPI
from pydantic import BaseModel
from typing_extensions import Annotatedapp = FastAPI()class Item(BaseModel):name: strdescription: Union[str, None] = Noneprice: floattax: Union[float, None] = None@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):results = {"item_id": item_id, "item": item}return results
文档中说“在这种情况下,FastAPI 将期望像这样的请求体:”
{"item": {"name": "Foo","description": "The pretender","price": 42.0,"tax": 3.2}
}
而不是:
{"name": "Foo","description": "The pretender","price": 42.0,"tax": 3.2
}
根据给的请求体的示例代码,我写的请求代码是
import requestsurl = 'http://127.0.0.1:8000/items/7'
form_data_1 = {"item": {"name": "Foo","description": "The pretender","price": 42.0,"tax": 3.2}
}
form_data_2 = {# "item": {"name": "Foo","description": "The pretender","price": 42.0,"tax": 3.2# }
}
response_1 = requests.put(url=url, json=form_data_1)
response_2 = requests.put(url=url, json=form_data_2)
print(response_1.status_code)
print(response_1.text)
print(response_2.status_code)
print(response_2.text)
# 上面的打印结果:
# fastapi环境
# 200
# {"item_id":7,"item":{"name":"Foo","description":"The pretender","price":42.0,"tax":3.2}}
# 422
# {"detail":[{"type":"missing","loc":["body","item"],"msg":"Field required","input":null,"url":"https://errors.pydantic.dev/2.1/v/missing"}]}
# likeadmin环境
# 422
# {"detail":[{"loc":["body","name"],"msg":"field required","type":"value_error.missing"},{"loc":["body","price"],"msg":"field required","type":"value_error.missing"}]}
# 200
# {"item_id":7,"item":{"name":"Foo","description":"The pretender","price":42.0,"tax":3.2}}
bug 3
Header 参数 - FastAPI (tiangolo.com)中"声明 Header
参数"处(还有Cookie 参数 - FastAPI (tiangolo.com)中"声明 Cookie
参数"处),选python3.6,接口示例代码是
from typing import Unionfrom fastapi import FastAPI, Header
from typing_extensions import Annotatedapp = FastAPI()@app.get("/items/")
async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):return {"User-Agent": user_agent}
from typing import Unionfrom fastapi import Cookie, FastAPI
from typing_extensions import Annotatedapp = FastAPI()@app.get("/items/")
async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):return {"ads_id": ads_id}
我实际写的接口代码是
from typing import Unionfrom fastapi import FastAPI, Header, Cookie
from typing_extensions import Annotatedapp = FastAPI()@app.get("/items/")
async def read_items(BIDUPSID: Annotated[Union[str, None], Cookie()] = None, # 从请求头的cookie中获取键为BIDUPSID的值PSTM: Annotated[Union[str, None], Cookie()] = None,BA_HECTOR: Annotated[Union[str, None], Cookie()] = None,user_agent: Annotated[Union[str, None], Header()] = None # fastapi自动将user_agent转为user-agent,又因HTTP headers是大小写不敏感的,所以user_agent就是User-Agent。from https://fastapi.tiangolo.com/zh/tutorial/header-params/#_1中"自动转换处"):return {"Cookie": f'BIDUPSID={BIDUPSID}; PSTM={PSTM}; BA_HECTOR={BA_HECTOR}', "User-Agent": user_agent}'''
# from https://fastapi.tiangolo.com/zh/tutorial/header-params/#_1中"自动转换处"
自动转换
Header 在 Path, Query 和 Cookie 提供的功能之上有一点额外的功能。大多数标准的headers用 "连字符" 分隔,也称为 "减号" (-)。但是像 user-agent 这样的变量在Python中是无效的。因此, 默认情况下, Header 将把参数名称的字符从下划线 (_) 转换为连字符 (-) 来提取并记录 headers.同时,HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 "snake_case")声明它们。因此,您可以像通常在Python代码中那样使用 user_agent ,而不需要将首字母大写为 User_Agent 或类似的东西。如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False
'''
我写的请求代码是
import requestsurl = 'http://127.0.0.1:8000/items/'
headers = {'Cookie': 'BIDUPSID=A9DA10BEC1294DD8D4F6E654A52CBDC8; PSTM=1691732828; BAIDUID=A9DA10BEC1294DD8402C67E5E8156634:FG=1; BD_UPN=12314753; BA_HECTOR=8h8k05052l000la0a1810h0j1idbjgf1p; BAIDUID_BFESS=A9DA10BEC1294DD8402C67E5E8156634:FG=1; BD_CK_SAM=1; PSINO=2; ZFY=FJzChFI0tlXn0pdMbu9GDJzHS1iNKW8:A:AjdKkCxk2W0:C; delPer=0; BD_HOME=1; COOKIE_SESSION=21_0_0_0_3_0_0_0_0_0_0_0_23_0_4_0_1691734574_0_1691734570%7C2%230_0_1691734570%7C1; H_PS_PSSID=36546_39112_38831_39008_39114_39038_38917_26350_39138_39132_39137_22157_39100; H_PS_645EC=d157JRTS54%2FnnbXzJd5jNKP%2FMviUleulBfUEwB4MIKKEUrwiHAKJcjpnt4A','Referer': 'https://www.baidu.com/','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203'
}
# cookie = {
# 'BIDUPSID': 'A9DA10BEC1294DD8D4F6E654A52CBDC8',
# 'PSTM': '1691732828',
# 'BA_HECTOR': '8h8k05052l000la0a1810h0j1idbjgf1p'
# }response = requests.get(url=url, headers=headers)
# response = requests.get(url=url, headers=headers, cookies=cookie) # 注释掉headers中Cookie,取消cookie的注释,再用这个请求,和上一行的结果一样
print(response.status_code)
print(response.text)
likeadmin的环境获取不到Cookie和User-Agent;纯fastapi环境,Cookie和User-Agent都能获取。
# likeadmin的环境的结果
{"Cookie":"BIDUPSID=None; PSTM=None; BA_HECTOR=None","User-Agent":null}# 纯fastapi环境的结果
{"Cookie":"BIDUPSID=A9DA10BEC1294DD8D4F6E654A52CBDC8; PSTM=1691732828; BA_HECTOR=8h8k05052l000la0a1810h0j1idbjgf1p","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203"}
bug 4
路径操作配置 - FastAPI (tiangolo.com)中"status_code状态码"处,接口示例代码是
from typing import Set, Unionfrom fastapi import FastAPI, status
from pydantic import BaseModelapp = FastAPI()class Item(BaseModel):name: strdescription: Union[str, None] = Noneprice: floattax: Union[float, None] = Nonetags: Set[str] = set()@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED) # 也可以直接用status_code=201
async def create_item(item: Item):return item
我写的请求代码是
import requestsurl = 'http://127.0.0.1:8000/items/'
form_data = {'name': 'Bob','price': 12.3,
}
response = requests.post(url=url, json=form_data)
print(response.status_code)
print(response.text)
likeadmin的环境打印的状态码还是200,没变成设置的201;纯fastapi环境的是201。
bug 1 与 bug 2 与 bug 3 与 bug 4的解决
既然两个虚拟环境的python版本一样,忽然想到是不是fastapi版本不一样导致的,查看纯fastapi环境(2023年8月11日)得到
Package Version
-------------------- -----------
annotated-types 0.5.0
anyio 3.7.1
certifi 2023.7.22
click 8.1.6
colorama 0.4.6
dnspython 2.4.2
email-validator 2.0.0.post2
exceptiongroup 1.1.2
fastapi 0.101.0
h11 0.14.0
httpcore 0.17.3
httptools 0.6.0
httpx 0.24.1
idna 3.4
itsdangerous 2.1.2
Jinja2 3.1.2
MarkupSafe 2.1.3
orjson 3.9.4
pip 23.2.1
pydantic 2.1.1
pydantic_core 2.4.0
pydantic-extra-types 2.0.0
pydantic-settings 2.0.2
python-dotenv 1.0.0
python-multipart 0.0.6
PyYAML 6.0.1
setuptools 68.0.0
sniffio 1.3.0
starlette 0.27.0
typing_extensions 4.7.1
ujson 5.8.0
uvicorn 0.23.2
watchfiles 0.19.0
websockets 11.0.3
wheel 0.38.4
将likeadmin的fastapi库由0.85.0升级到和纯fastapi环境一样的0.101.0,再运行上面的示例代码,就能得到正确的结果了。