likes
comments
collection
share

FastAPI请求参数处理

作者站长头像
站长
· 阅读数 3

引言

在FastAPI中,HTTP请求的参数获取和验证是非常重要的功能。本文将详细介绍FastAPI中几种常见的请求参数形式,以及它们的获取方式和验证机制。

请求参数的常见形式

一个典型的RESTful API中,请求参数主要有以下几种形式:

  • 查询字符串参数-Query Parameters:URL中?号后面的查询参数,例如?query=test
  • 路径参数-Path Parameters:URL路径中的参数,例如/path/{param}
  • 请求体-Request Body:请求体中的JSON格式数据
  • 请求头-Headers:请求头中的参数
  • 文件上传-Files
  • 其他的请求信息-Request

下面我将依次对这几种参数的处理进行说明。

请求参数处理Demo

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { 请求参数案例 }
# @Date: 2023/11/06 11:15
import uvicorn
from fastapi import FastAPI, Header, UploadFile
from pydantic import BaseModel, Field

app = FastAPI(summary="请求参数案例")


@app.get("/query_params", summary="查询字符串参数Demo")
def with_query_params(name: str, age: int):
    return {"name": name, "age": age}


@app.get("/user/{user_id}/path_params", summary="路径参数Demo")
def with_path_params(user_id: int):
    return {"user_id": user_id}


class UserLoginIn(BaseModel):
    account: str = Field(..., description="账号")
    password: str = Field(..., description="密码")


@app.post("/user/login/json_params", summary="json参数demo")
def with_json_params(req_model: UserLoginIn):
    return req_model.model_dump()


@app.get("/user/detail/header_params", summary="请求头参数demo")
def with_header_params(
        token: str = Header(description="访问token"),
        user_agent: str = Header(description="用户代理")
):
    return {"token": token, "User-Agent": user_agent}


@app.post("/file_upload/file_params", summary="文件参数demo")
async def with_file_params(file: UploadFile):
    return {"filename": file.filename, "file_size": file.size}


@app.get("/users/{user_id}/path_query_params", summary="路径参数+查询字符串参数demo")
def with_path_query_params(user_id: int, age: int = None):
    return {"user_id": user_id, "age": age}


def main():
    uvicorn.run(app)


if __name__ == '__main__':
    main()

FastAPI请求参数处理

FastAPI请求参数处理

请求处理注意点

这里需要注意,如下几点

  1. 请求头参数需要从 fastapi 导入 Header 来标识是请求头的参数

    • http固定的请求头参数,fastapi处理是中划线改成下划线,大小写不敏感,如下

      • Content-Type => content_type
      • User-Agent => user_agent
  2. 处理文件参数有点特殊,也是需要从 fastapi 导入 UploadFile 进行处理,这个类帮忙封装了一些文件信息

    • 然后还需要下载一个 python-multipart 依赖以支持多媒体传输
  3. 请求体使用json格式交互用了pydantic的BaseModel 可以写参数的描述信息,所以在API文档中有中文的描述,而路径、查询字符串参数没有。需要使用fastapi的Path、Query对象才有。

    • 如果希望get请求的路径、查询字符串参数也可以像body一样使用 pydantic的BaseModel维护,需要借助Depends、 Path、Query对象,但会出现description信息不会显示在API文档中,需要解决则还要再BaseModel类中使用Field与Query等对象嵌套来解决。

针对第3点,我写个Demo展示下如何处理

class UserQueryIn(BaseModel):
    user_id: int = Path(description="用户ID")
    name: Optional[str] = Query(default=None, description="姓名")
    age: Optional[int] = Query(default=None, description="年龄")


@app.get("/users/{user_id}/path_query_params2", summary="路径参数+查询字符串参数BaseModel的demo")
def with_path_query_params(req_model: UserQueryIn):
    # 业务逻辑处理
    # logic_func(req_model)
    return req_model.model_dump()

FastAPI请求参数处理

如果直接用BaseModel 的话 fastapi 会认为是body参数,这明显不合理是错误,因此需要通过 fastapi的Depends函数来解决。

class UserQueryIn(BaseModel):
    user_id: int = Path(description="用户ID")
    name: Optional[str] = Query(default=None, description="姓名")
    age: Optional[int] = Query(default=None, description="年龄")


@app.get("/users/{user_id}/path_query_params2", summary="路径参数+查询字符串参数BaseModel的demo")
def with_path_query_params(req_model: UserQueryIn = Depends(UserQueryIn)):
    # 业务逻辑处理
    # logic_func(req_model)
    return req_model.model_dump()

FastAPI请求参数处理

但会出现description信息不展示,如果需要把API文档导入到APIFOX中的话没有中文描述还是不太好,因此还可以借助在BaseModel中让Field与Query等对象嵌套来解决。

class UserQueryIn(BaseModel):
    user_id: int = Field(Path(description="用户ID"))
    name: Optional[str] = Field(Query(default=None, description="姓名"))
    age: Optional[int] = Field(Query(default=None, description="年龄"))


@app.get("/users/{user_id}/path_query_params2", summary="路径参数+查询字符串参数BaseModel的demo")
def with_path_query_params(req_model: UserQueryIn = Depends(UserQueryIn)):
    # 业务逻辑处理
    # logic_func(req_model)
    return req_model.model_dump()

FastAPI请求参数处理

为了统一使用pydantic的BaseModel类处理请求参数,还是有点小坑,感觉get请求不太好使用BaseModel,希望FastAPI后续能优化下。

为什么要统一使用BaseModel呢?看看如下例子

@app.get("/users/{user_id}/path_query_params", summary="路径参数+查询字符串参数demo")
def with_path_query_params(
        user_id: int = Path(description="用户ID"),
        age: int = Query(default=None, description="年龄查询")
):
    # 业务逻辑处理
    # logic_func(user_id=user_id, age=age)
    return {"user_id": user_id, "age": age}



class UserQueryIn(BaseModel):
    user_id: int = Field(Path(gt=0, description="用户ID"))
    name: Optional[str] = Field(Query(default=None, min_length=1, description="姓名"))
    age: Optional[int] = Field(Query(default=None, gt=0, description="年龄"))


@app.get("/users/{user_id}/path_query_params2", summary="路径参数+查询字符串参数BaseModel的demo")
def with_path_query_params(req_model: UserQueryIn = Depends(UserQueryIn)):
    # 业务逻辑处理
    # logic_func(req_model)
    return req_model.model_dump()

如果查询参数需要改动变化、只需要在BaseModel类中维护就好,后面还可以添加校验器,整体的路由函数更简洁不会一堆的参数,传递给逻辑层处理只要透传一个请求模型 req_model 就可以了,可以说更统一规范、更好维护。麻烦就是要在定义的时候使用Depends与Query等。

请求对象

基本请求信息都可以声明参数获取,但一些额外的请求信息,例如请求的IP、Method、URL等则需要通过Request对象进行获取,具体使用如下:

from fastapi import FastAPI, Request


@app.get("/request_obj", summary="请求对象的demo")
def req_obj_demo(req: Request):
    print("req client ip", req.client.host)
    print("req method", req.method)
    print("req base_url", req.base_url)
    print("req url", req.url)

    print("Request", req)
    return {
        "client_ip": req.client.host,
        "method": req.method,
        "base_url": req.base_url,
        "url": req.url,
    }

Ret

req client ip 127.0.0.1
req method GET
req base_url http://127.0.0.1:8000/
req url http://127.0.0.1:8000/request_obj
Request <starlette.requests.Request object at 0x104bf8af0>

这个Request对象,再每个路由函数中都存在只要声明的参数类型是FastAPI的Request即可,就可以从中获取请求参数、以及一些其他的请求的信息。这个Request对象一般在fastapi的中间件中使用多,后续再介绍。

总结

通过上面的示例可以看出,FastAPI通过声明参数、Depends注入、Pydantic模型验证等方式,提供了非常便捷的请求参数处理机制。各种参数形式也都得到了很好的支持。使用pydantic的BaseModel传递组织参数,相比其他框架使用字典来组织,代码更清晰、更好维护。

源代码

Github:github.com/HuiDBK/Fast…