以下内容写于2023年8月11日
请求体 - 多个参数 - FastAPI (tiangolo.com)中“请求体中的单一值”处,选python3.6,接口示例代码是
- from typing import Union
-
- from fastapi import Body, FastAPI
- from pydantic import BaseModel
- from typing_extensions import Annotated
-
- app = FastAPI()
-
-
- class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
- class User(BaseModel):
- username: str
- full_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 requests
-
- url = '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
与bug1类似,也是likeadmin的环境运行fastapi的示例代码,无法得到fastapi文档中说的预期的结果;而新建一个相同python版本的虚拟环境,里面只pip install fastapi[all],可以得到fastapi文档中说的预期的结果。
请求体 - 多个参数 - FastAPI (tiangolo.com)中“嵌入单个请求体参数”处,选python3.6,接口示例代码是
- from typing import Union
-
- from fastapi import Body, FastAPI
- from pydantic import BaseModel
- from typing_extensions import Annotated
-
- app = FastAPI()
-
-
- class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: 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 requests
-
- url = '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}}
Header 参数 - FastAPI (tiangolo.com)中"声明 Header
参数"处(还有Cookie 参数 - FastAPI (tiangolo.com)中"声明 Cookie
参数"处),选python3.6,接口示例代码是
- from typing import Union
-
- from fastapi import FastAPI, Header
- from typing_extensions import Annotated
-
- app = FastAPI()
-
-
- @app.get("/items/")
- async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):
- return {"User-Agent": user_agent}
- from typing import Union
-
- from fastapi import Cookie, FastAPI
- from typing_extensions import Annotated
-
- app = FastAPI()
-
-
- @app.get("/items/")
- async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
- return {"ads_id": ads_id}
我实际写的接口代码是
- from typing import Union
-
- from fastapi import FastAPI, Header, Cookie
- from typing_extensions import Annotated
-
- app = 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 requests
-
- url = '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"}
路径操作配置 - FastAPI (tiangolo.com)中"status_code状态码"处,接口示例代码是
- from typing import Set, Union
-
- from fastapi import FastAPI, status
- from pydantic import BaseModel
-
- app = FastAPI()
-
-
- class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: 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 requests
-
- url = '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。
既然两个虚拟环境的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,再运行上面的示例代码,就能得到正确的结果了。