• likeadmin和fastapi的bug


    以下内容写于2023年8月11日

    bug 1

    请求体 - 多个参数 - FastAPI (tiangolo.com)中“请求体中的单一值”处,选python3.6,接口示例代码是

    1. from typing import Union
    2. from fastapi import Body, FastAPI
    3. from pydantic import BaseModel
    4. from typing_extensions import Annotated
    5. app = FastAPI()
    6. class Item(BaseModel):
    7. name: str
    8. description: Union[str, None] = None
    9. price: float
    10. tax: Union[float, None] = None
    11. class User(BaseModel):
    12. username: str
    13. full_name: Union[str, None] = None
    14. @app.put("/items/{item_id}")
    15. async def update_item(
    16. item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
    17. ):
    18. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    19. return results

    根据给的请求体的示例代码,我写的请求代码是

    1. import requests
    2. url = 'http://127.0.0.1:8000/items/7'
    3. form_data = {
    4. "item": {
    5. "name": "Foo",
    6. "description": "The pretender",
    7. "price": 42.0,
    8. "tax": 3.2
    9. },
    10. "user": {
    11. "username": "dave",
    12. "full_name": "Dave Grohl"
    13. },
    14. "importance": 5
    15. }
    16. response = requests.put(url=url, json=form_data)
    17. print(response.status_code)
    18. 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环境自动生成的文档

     总结一下就是likeadmin的环境运行fastapi的示例代码,无法得到fastapi文档中说的预期的结果;而新建一个相同python版本的虚拟环境,里面只pip install fastapi[all],可以得到fastapi文档中说的预期的结果。

    截止到2023年8月11日,likeadmin的requirements.txt的内容是

    1. aiofiles==22.1.0
    2. aiomysql==0.1.1
    3. alibabacloud_dysmsapi20170525==2.0.23
    4. alibabacloud_tea_openapi
    5. anyio==3.6.1
    6. async-timeout==4.0.2
    7. click==8.1.3
    8. colorama==0.4.5
    9. commonmark==0.9.1
    10. cos-python-sdk-v5==1.9.22
    11. databases==0.6.1
    12. Deprecated==1.2.13
    13. fastapi==0.85.0
    14. fastapi-cache2==0.1.9
    15. fastapi-pagination==0.10.0
    16. greenlet==1.1.3.post0
    17. h11==0.14.0
    18. httptools==0.5.0
    19. idna==3.4
    20. Jinja2==3.1.2
    21. MarkupSafe==2.1.1
    22. optionaldict==0.1.2
    23. oss2==2.16.0
    24. pendulum==2.1.2
    25. psutil==5.9.3
    26. pydantic==1.10.2
    27. Pygments==2.13.0
    28. PyMySQL==1.0.2
    29. python-dateutil==2.8.2
    30. python-dotenv==0.21.0
    31. python-multipart==0.0.5
    32. pytz==2022.5
    33. pytzdata==2020.1
    34. PyYAML==6.0
    35. qiniu==7.9.0
    36. redis==4.4.0rc2
    37. rich==12.6.0
    38. shellingham==1.5.0
    39. six==1.16.0
    40. sniffio==1.3.0
    41. SQLAlchemy==1.4.41
    42. starlette==0.20.4
    43. tencentcloud-sdk-python==3.0.841
    44. typer==0.6.1
    45. typing-extensions==4.4.0
    46. ua-parser==0.16.1
    47. user-agents==2.2.0
    48. # uvloop==0.17.0
    49. uvicorn==0.18.3
    50. watchfiles==0.17.0
    51. websockets==10.3
    52. wrapt==1.14.1
    53. wechatpy==1.8.18

    bug 2

    与bug1类似,也是likeadmin的环境运行fastapi的示例代码,无法得到fastapi文档中说的预期的结果;而新建一个相同python版本的虚拟环境,里面只pip install fastapi[all],可以得到fastapi文档中说的预期的结果。

    请求体 - 多个参数 - FastAPI (tiangolo.com)中“嵌入单个请求体参数”处,选python3.6,接口示例代码是

    1. from typing import Union
    2. from fastapi import Body, FastAPI
    3. from pydantic import BaseModel
    4. from typing_extensions import Annotated
    5. app = FastAPI()
    6. class Item(BaseModel):
    7. name: str
    8. description: Union[str, None] = None
    9. price: float
    10. tax: Union[float, None] = None
    11. @app.put("/items/{item_id}")
    12. async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
    13. results = {"item_id": item_id, "item": item}
    14. return results

    文档中说“在这种情况下,FastAPI 将期望像这样的请求体:”

    1. {
    2. "item": {
    3. "name": "Foo",
    4. "description": "The pretender",
    5. "price": 42.0,
    6. "tax": 3.2
    7. }
    8. }

    而不是:

    1. {
    2. "name": "Foo",
    3. "description": "The pretender",
    4. "price": 42.0,
    5. "tax": 3.2
    6. }

    根据给的请求体的示例代码,我写的请求代码是

    1. import requests
    2. url = 'http://127.0.0.1:8000/items/7'
    3. form_data_1 = {
    4. "item": {
    5. "name": "Foo",
    6. "description": "The pretender",
    7. "price": 42.0,
    8. "tax": 3.2
    9. }
    10. }
    11. form_data_2 = {
    12. # "item": {
    13. "name": "Foo",
    14. "description": "The pretender",
    15. "price": 42.0,
    16. "tax": 3.2
    17. # }
    18. }
    19. response_1 = requests.put(url=url, json=form_data_1)
    20. response_2 = requests.put(url=url, json=form_data_2)
    21. print(response_1.status_code)
    22. print(response_1.text)
    23. print(response_2.status_code)
    24. print(response_2.text)
    25. # 上面的打印结果:
    26. # fastapi环境
    27. # 200
    28. # {"item_id":7,"item":{"name":"Foo","description":"The pretender","price":42.0,"tax":3.2}}
    29. # 422
    30. # {"detail":[{"type":"missing","loc":["body","item"],"msg":"Field required","input":null,"url":"https://errors.pydantic.dev/2.1/v/missing"}]}
    31. # likeadmin环境
    32. # 422
    33. # {"detail":[{"loc":["body","name"],"msg":"field required","type":"value_error.missing"},{"loc":["body","price"],"msg":"field required","type":"value_error.missing"}]}
    34. # 200
    35. # {"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,接口示例代码是

    1. from typing import Union
    2. from fastapi import FastAPI, Header
    3. from typing_extensions import Annotated
    4. app = FastAPI()
    5. @app.get("/items/")
    6. async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):
    7. return {"User-Agent": user_agent}
    1. from typing import Union
    2. from fastapi import Cookie, FastAPI
    3. from typing_extensions import Annotated
    4. app = FastAPI()
    5. @app.get("/items/")
    6. async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
    7. return {"ads_id": ads_id}

     我实际写的接口代码是

    1. from typing import Union
    2. from fastapi import FastAPI, Header, Cookie
    3. from typing_extensions import Annotated
    4. app = FastAPI()
    5. @app.get("/items/")
    6. async def read_items(
    7. BIDUPSID: Annotated[Union[str, None], Cookie()] = None, # 从请求头的cookie中获取键为BIDUPSID的值
    8. PSTM: Annotated[Union[str, None], Cookie()] = None,
    9. BA_HECTOR: Annotated[Union[str, None], Cookie()] = None,
    10. 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中"自动转换处"
    11. ):
    12. return {"Cookie": f'BIDUPSID={BIDUPSID}; PSTM={PSTM}; BA_HECTOR={BA_HECTOR}', "User-Agent": user_agent}
    13. '''
    14. # from https://fastapi.tiangolo.com/zh/tutorial/header-params/#_1中"自动转换处"
    15. 自动转换
    16. Header 在 Path, Query 和 Cookie 提供的功能之上有一点额外的功能。
    17. 大多数标准的headers用 "连字符" 分隔,也称为 "减号" (-)。
    18. 但是像 user-agent 这样的变量在Python中是无效的。
    19. 因此, 默认情况下, Header 将把参数名称的字符从下划线 (_) 转换为连字符 (-) 来提取并记录 headers.
    20. 同时,HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 "snake_case")声明它们。
    21. 因此,您可以像通常在Python代码中那样使用 user_agent ,而不需要将首字母大写为 User_Agent 或类似的东西。
    22. 如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False
    23. '''

    我写的请求代码是

    1. import requests
    2. url = 'http://127.0.0.1:8000/items/'
    3. headers = {
    4. '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',
    5. 'Referer': 'https://www.baidu.com/',
    6. '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'
    7. }
    8. # cookie = {
    9. # 'BIDUPSID': 'A9DA10BEC1294DD8D4F6E654A52CBDC8',
    10. # 'PSTM': '1691732828',
    11. # 'BA_HECTOR': '8h8k05052l000la0a1810h0j1idbjgf1p'
    12. # }
    13. response = requests.get(url=url, headers=headers)
    14. # response = requests.get(url=url, headers=headers, cookies=cookie) # 注释掉headers中Cookie,取消cookie的注释,再用这个请求,和上一行的结果一样
    15. print(response.status_code)
    16. print(response.text)

     likeadmin的环境获取不到Cookie和User-Agent;纯fastapi环境,Cookie和User-Agent都能获取。 

    1. # likeadmin的环境的结果
    2. {"Cookie":"BIDUPSID=None; PSTM=None; BA_HECTOR=None","User-Agent":null}
    3. # 纯fastapi环境的结果
    4. {"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状态码"处,接口示例代码是

    1. from typing import Set, Union
    2. from fastapi import FastAPI, status
    3. from pydantic import BaseModel
    4. app = FastAPI()
    5. class Item(BaseModel):
    6. name: str
    7. description: Union[str, None] = None
    8. price: float
    9. tax: Union[float, None] = None
    10. tags: Set[str] = set()
    11. @app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED) # 也可以直接用status_code=201
    12. async def create_item(item: Item):
    13. return item

    我写的请求代码是

    1. import requests
    2. url = 'http://127.0.0.1:8000/items/'
    3. form_data = {
    4. 'name': 'Bob',
    5. 'price': 12.3,
    6. }
    7. response = requests.post(url=url, json=form_data)
    8. print(response.status_code)
    9. print(response.text)

     likeadmin的环境打印的状态码还是200,没变成设置的201;纯fastapi环境的是201。

    bug 1 与 bug 2 与 bug 3 与 bug 4的解决

    既然两个虚拟环境的python版本一样,忽然想到是不是fastapi版本不一样导致的,查看纯fastapi环境(2023年8月11日)得到

    1. Package Version
    2. -------------------- -----------
    3. annotated-types 0.5.0
    4. anyio 3.7.1
    5. certifi 2023.7.22
    6. click 8.1.6
    7. colorama 0.4.6
    8. dnspython 2.4.2
    9. email-validator 2.0.0.post2
    10. exceptiongroup 1.1.2
    11. fastapi 0.101.0
    12. h11 0.14.0
    13. httpcore 0.17.3
    14. httptools 0.6.0
    15. httpx 0.24.1
    16. idna 3.4
    17. itsdangerous 2.1.2
    18. Jinja2 3.1.2
    19. MarkupSafe 2.1.3
    20. orjson 3.9.4
    21. pip 23.2.1
    22. pydantic 2.1.1
    23. pydantic_core 2.4.0
    24. pydantic-extra-types 2.0.0
    25. pydantic-settings 2.0.2
    26. python-dotenv 1.0.0
    27. python-multipart 0.0.6
    28. PyYAML 6.0.1
    29. setuptools 68.0.0
    30. sniffio 1.3.0
    31. starlette 0.27.0
    32. typing_extensions 4.7.1
    33. ujson 5.8.0
    34. uvicorn 0.23.2
    35. watchfiles 0.19.0
    36. websockets 11.0.3
    37. wheel 0.38.4

    将likeadmin的fastapi库由0.85.0升级到和纯fastapi环境一样的0.101.0,再运行上面的示例代码,就能得到正确的结果了。

  • 相关阅读:
    mtbatisplus
    记录一次异常访问场景
    Python超好用的命令行界面实现工具,我保证你肯定不知道...
    Spring基础之AOP
    里奥哈大使撰文 | 来一场云旅行吧,盘点里奥哈那些美轮美奂的酒庄~
    docker 可用镜像服务地址(2024.10.31亲测可用)
    下载、安装并配置 Node.js
    Android键盘监听
    LeetCode 3217.从链表中移除在数组中存在的节点
    【Spring Boot】Day03
  • 原文地址:https://blog.csdn.net/fj_changing/article/details/132224527