【FastAPI 学习 七】GET和POST请求参数接收以及验证

FastAPI http请求参数的接收

我最开始接触FastAPI的时候,最搞不懂的就是POST方式是如何接收参数的。

GET请求参数

GET方式的参数有两种,一种是路径参数,一种是查询参数。举个例子来说明两者的区别

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_root(item_id: int):
    return {"item_id": item_id}

这种是直接写在请求路径里面的item_id,就是路径参数,(但是个人不喜欢用)

请求示例url, 比如本地8000端口启动 http://127.0.0.1:8000/items/10
启动方式,如文件名为main.py(不限制但是必须和启动的文件名对应) 都是测试环境启动
安装pip install uvicorn才能使用

# 在最下面加上 这一句
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app='main:app', host="127.0.0.1", port=8000, reload=True, debug=True)

或者用命令行启动 注意:得和main.py在同一文件目录下

uvicorn main:app --host=127.0.0.1 --port=8000 --reload
from fastapi import FastAPI

app = FastAPI()

@app.get("/bar")
async def read_item(name: str, age: int=18): # tip: 和python默认参数一样,有默认参数写在后面
    return {"name": name, "age": age}

这种nameage就是查询参数方式GET请求,也就是最常见的?&符号请求方式了
如上请求示例url: http://127.0.0.1:8000/bar?age=22&name=foo

路径参数和查询参数,参数验证的区别

路径参数使用 Path 查询参数使用Query, 可以查看Path和Query的源码,其实现方式和使用参数方式都差不多,我理解的就是用来验证不用的请求方式的参数。参数有很多验证规则。

from typing import Optional
from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/bar/{foo}")
async def read_item(
        foo: int = Path(1, title='描述'),
        age: int = Query(..., le=120, title="年龄"),
        name: Optional[str] = Query(None, min_length=3, max_length=50, regex="^xiaod+$")
):
    return {"foo": foo, "age": age, "name": name}

上述GET请求合法的请求url是


http://127.0.0.1:8000/bar/123?age=18  # Query(...)表示没有默认参数必须得传
# or或者
http://127.0.0.1:8000/bar/123?age=18&name=xiao1  # name可以不传,传了就要符合正则的限制xiao开头数字结尾

以上验证参数简单描述
le 验证数字age最大不超过120
min_length 验证name最短3个字符
max_length 验证name最长50个字符
regex 这个就是正则验证 必须是xiao开头数字结尾(当然前提你得熟悉正则)
还有就是Query(..., le=120) ... 表示没有默认参数,必须要传。

def Query(  # noqa: N802 关于FastAPI源码里面noqa我搜了下,是No Quality Assurance 见issues https://github.com/PyCQA/pycodestyle/issues/476 还是还是Guido提问的
    default: Any,
    *,
    alias: str = None,    # 请求参数别名 如上name字段 Query(..., alias="N-A-N-E") 个人一般认为headers里面得参数用的多比如headers里面自定义的 X-Token
    # 接收name的url就得是这样 http://127.0.0.1:8060/bar/123?age=18&N-A-N-E=xiaoming
    title: str = None,
    description: str = None,  
    gt: float = None,
    ge: float = None,
    lt: float = None,
    le: float = None,
    min_length: int = None,
    max_length: int = None,
    regex: str = None,
    deprecated: bool = None,   # True表示将要是废弃的接口
    **extra: Any,
)

更多验证方式直接查看Query或者Path源码,看到参数名字,如上Query源码,简单的英语就知道是干什么用的。如果还不清楚查看官网

异常处理

额,我感觉我博客顺序有点问题,应该先讲参数处理再说异常捕获的,详细异常捕获可以查看前一个博客。

比如上面的 http://127.0.0.1:8000/bar/123 这个就是参数验证失败了,会触发RequestValidationError异常

{
    "detail": [
        {
            "loc": [
                "query",
                "age"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        }
    ]
}

捕获参数异常

from typing import Optional
from fastapi import FastAPI, Request, Path, Query
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
    print(f"参数不对{request.method} {request.url}") # 可以用日志记录请求信息,方便排错
    return JSONResponse({"code": "400", "message": exc.errors()})

@app.get("/bar/{foo}")
async def read_item(
        foo: int = Path(1, title='描述'),
        age: int = Query(..., le=120, title="年龄"),
        name: Optional[str] = Query(None, min_length=3, max_length=50, regex="^xiaod+$")
):
    return {"foo": foo, "age": age, "name": name}

这样的话,就可以捕获,然后按照自己定义的方式响应json格式。
还是上面的http://127.0.0.1:8000/bar/123 就会如下返回

{
    "code": "400",
    "message": [
        {
            "loc": [
                "query",
                "age"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        }
    ]
}

如果还不熟悉Python3.6之后的typing模块就要好好补补了:typing模块官网传送门 https://docs.python.org/zh-cn/3/library/typing.html

POST 请求参数

还是上面那个例子改成POST请求,与Query或者Path不一样的就是,使用Body函数来限制参数格式, 如下: Body和Query,Path用法基本是一样的。

embed=True 意思是请求体中,使用json key-value形式, 参考官网

from fastapi import FastAPI, Request, Body
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
    print(f"参数不对{request.method} {request.url}")
    return JSONResponse({"code": "400", "message": exc.errors()})


@app.post("/bar")
async def read_item(
        foo: int = Body(1, title='描述', embed=True),
        age: int = Body(..., le=120, title="年龄", embed=True),
        name: str = Body(..., regex="^xiaod+$", embed=True)
):
    return {"foo": foo, "age": age, "name": name}

正确的请求方式是 注意header请求头里面"accept: application/json"

curl -X POST "http://127.0.0.1:8000/bar" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{"foo":1,"age":2,"name":"333"}"

Python requests发送请求

import requests

res = requests.post("http://127.0.0.1:8000/bar", json={"foo": 1, "age": 12, "name": "xiao123"})
print(res.json())  # {'foo': 1, 'age': 12, 'name': 'xiao123'}

上述demo只能接收application/json json方式的参数,表单POST请求的方式是接收不到参数的,如form-data 只能使用Form接收,下面是示例:
注意必须安装 pip install python-multipart 才能接收Form参数官网传送门

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

POST参数验证

POST方式除了使用Body方法来验证外,更多的时候是使用的pydantic 这个库来验证的
比如以上的POST请求参数就可以换成一下例子

import re
from typing import Optional
from fastapi import FastAPI, Request, Body
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, validator, conint, constr

app = FastAPI()


class UserInfo(BaseModel):
    foo: int = 1
    age: conint(le=120) = 18
    # 这里 regex验证 我不知道为什么PyCharm提示语法错误
    name: constr(min_length=5, max_length=10, regex=r"^xiaod+$")  
	
	# 复杂的验证一般用这个
    @validator('name')
    def name_re(cls, v):
        # 自定义验证 正则验证name字段 等同于上面的正则验证
        if not re.match(r"^xiaod+$", v):
            # 抛出 ValueError pydantic接收到后会向外抛出 ValidationError
            raise ValueError("name格式验证错误")
        return v

# FastAPI RequestValidationError本就是继承的ValidationError, 会捕获到请求参数异常错误
@app.exception_handler(RequestValidationError)
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
    print(f"参数不对{request.method} {request.url}")
    return JSONResponse({"code": "400", "message": exc.errors()})


@app.post("/bar")
async def read_item(
        user_info: UserInfo
):
    return {"foo": user_info.foo, "age": user_info.age, "name": user_info.name}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app='main:app', host="127.0.0.1", port=8000, reload=True, debug=True)

上述例子正确访问方式应是

curl -X POST "http://127.0.0.1:8000/bar" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{"foo":1,"age":2,"name":"xiao1"}"

关于POST form表单参数接收

记得一定要安装 这个库,否则接收不到值, 其他的用法都差不多。

pip install python-multipart

参考官网 https://fastapi.tiangolo.com/tutorial/request-forms/ 这里不在赘述,用法差不多

总结

当然我POST例子中只列举了获取Body参数的例子,也可以获取路径参数Path和查询参数Query

完整代码GitHub地址

更多FastAPI信息可以关注人网站

见个人网站 https://www.charmcode.cn/article/2020-07-23_fastapi_get_post

原文地址:https://www.cnblogs.com/CharmCode/p/14191108.html