二、FastApi-参数接收和验证

官网:路径参数 - FastAPI

参考文献:https://zhuanlan.zhihu.com/p/667301390

1.参数接收

1.1 路径参数

在app/router 下,新增 demo_router.py文件, 内容如下:

from fastapi import APIRouter

router = APIRouter(
    prefix="/demo",
    tags=["演示接口"]
)

@router.get("/path/{order_id}")
async def pathParamReceive(order_id: int):
    """
    路径参数接收演示
    """
    return {
        "接受结果": order_id,
    }

 注意:当我们定义参数类型时, FastAPI 接受参数时,会自动进行"解析",如果类型不一致,则会报错。

请求验证

请求地址:http://localhost:8000/demo/path/999  (正常传参)

返回结果:

{
    "接受结果": 999
}

 请求地址:http://127.0.0.1:8000/demo/path/hello   (参数类型不匹配时,接口定义是:int)

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "path",
                "order_id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "hello"
        }
    ]
}

请求地址:http://127.0.0.1:8000/demo/path/  (不传参)

{
    "detail": "Not Found"
}

请求参数顺序问题

假如我们定义两个接口:

带路径参数的: /path/{order_id}

不带路径参数的: /path/test

可能会因为顺序问题,导致我们无法正常访问/path/test,例如路由注册时代码如下:

@router.get("/path/{order_id}")
async def pathParamReceive(order_id: int):
    """
    路径参数接收-演示-带路径参数
    """
    return {
        "接受结果": order_id,
    }

@router.get("/path/test")
async def pathParamReceive2():
    """
    路径参数接收-演示-不带路径参数
    """
    return {
        "msg": "hello",
    }

备注:此顺序存在问题

请求验证

请求地址:http://localhost:8000/demo/path/99999

返回结果:

{
  "接受结果": 99999
}

请求验证

请求地址:http://localhost:8000/demo/path/test

返回结果:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": [
        "path",
        "order_id"      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "test"    }
  ]
}

解决方案:

from fastapi import APIRouter

router = APIRouter(
    prefix="/demo",
    tags=["演示接口"]
)

@router.get("/path/test")
async def pathParamReceive2():
    """
    路径参数接收-演示-不带路径参数
    """
    return {
        "msg": "hello",
    }

@router.get("/path/{order_id}")
async def pathParamReceive(order_id: int):
    """
    路径参数接收-演示-带路径参数
    """
    return {
        "接受结果": order_id,
    }

备注:无参请求放在前面,有参请求放在后面,这样请求就可以正常访问了

1.2 查询参数

(1)在app/router下,新增demo_router.py文件,内容如下:

from typing import Union
# 导入APIRouter
from fastapi import APIRouter
# 实例化APIRouter实例
router = APIRouter(tags=["默认路由"])
@router.get("/query/receive")
async def queryParamReceive(username: str, sex: str = '男', city: Union[str, None] = None):
    """
    查询参数接受-演示
    """
    return {
        "msg": "查询参数接收",
        "result": {
            "username": username,
            "sex": sex,
            "city": city,
        }
    }

(2)参数约束

  • username: str: 代表参数username为必填字段;

  • sex: str = '男': 代表参数sex为选填字段, 并且有默认值男;

  • city: Union[str, None] = None: 代表参数city为选填字段,并无默认值; Union 是 typing 模块中的一个泛型类,用于表示多个类型中的一个;

注意: 当参数有默认值时, 顺序一定要放在没有默认值参数后面,否则会提示语法错误:SyntaxError: non-default argument follows default argument

1.3 请求体(推荐)

使用请求体接受参数,一般分为两个步骤:

  • 第一步: 使用Pydantic模型声明一个请求体(其实就是class);

  • 第二步: 路由函数的参数绑定上这个模型;

(1)定义模型

文件位置:app/parameter/demo_param.py

from typing import Union
# 导入pydantic对应的模型基类
from pydantic import BaseModel

class DemoParam(BaseModel):
    """
    请求体参数对应的模型
    """
    user_name: str
    age: int
    city: Union[str, None]

(2)优化导入

文件位置:app/parameter/__init__.py

from dbgpt.app.parameter.demo_param import DemoParam

(3)使用

文件位置: app/router/demo_router.py

from typing import Union
# 导入APIRouter
from fastapi import APIRouter
# 实例化APIRouter实例
router = APIRouter(tags=["默认路由"])
# 如果没有优化导入,这行会报错
from ..parameter import DemoParam

router = APIRouter(
    prefix="/demo",
    tags=["演示接口"]
)

@router.post("/query/receive")
async def bodyReceive(body: DemoParam):
    """
    请求体参数接受-演示
    """
    return {
        "msg": "请求体参数接受",
        "result": {
            "body": body,
        }
    }

(4)验证

  请求url:  http://localhost:8000/demo/query/receive

  请求参数:

{
    "user_name" : "gezongyang",
    "age" : 12,
    "city" : "beijing"
}

返回值:

{
    "msg": "请求体参数接受",
    "result": {
        "body": {
            "user_name": "gezongyang",
            "age": 12,
            "city": "beijing "
        }
    }
}

1.4 多参数接收

(1)定义模型

在app/parameter/demo_param.py文件中,新增内容如下:

from typing import Union
# 导入pydantic对应的模型基类
from pydantic import BaseModel, constr, conint

class DemoParam(BaseModel):
    """
    请求体参数对应的模型
    """
    user_name: str
    age: int
    city: Union[str, None]


class StudentParam(BaseModel):
    """
    学生信息
    """
    name: constr(min_length=2, max_length=4)  # 长度
    age: conint(ge=18, le=30)  # 整数范围:18 <= age <= 30
    class_name: str  # 班级名称

class ClassInfoParam(BaseModel):
    """
    班级信息
    """
    class_name: str  # 班级名称
    class_num: int  # 班级人数
    teacher_name: str  # 老师名称

(2)编写路由

@router.post("/query/pydantic/multipleParamReceive")
async def multipleParamReceive(student: request.StudentParam, classInfo: request.ClassInfoParam):
    """
    请求体-多参数接收-演示
    """
    return {
        "msg": "请求体-多参数接收",
        "result": {
            "student": student,
            "classInfo": classInfo,
        }
    }

(3)验证结果

请求url:  http://localhost:8000/demo/query/pydantic/multipleParamReceive

请求参数:

{
    "student": {
        "name": "san",
        "age": 0,
        "class_name": "一班"
    },
    "classInfo": {
        "class_name": "string",
        "class_num": 12,
        "teacher_name": "string"
    }
}

返回值:

{
  "detail": [
    {
      "type": "greater_than_equal",
      "loc": [
        "body",
        "student",
        "age"
      ],
      "msg": "Input should be greater than or equal to 18",
      "input": 0,
      "ctx": {
        "ge": 18
      }
    }
  ]
}

1.5 嵌套模型

(1)定义模型

在app/parameter/demo_param.py文件中,新增内容如下:

@router.post("/query/pydantic/multipleParamReceive")
async def multipleParamReceive(student: StudentParam, classInfo: ClassInfoParam):
    """
    请求体-多参数接收-演示
    """
    return {
        "msg": "请求体-多参数接收",
        "result": {
            "student": student,
            "classInfo": classInfo,
        }
    }

 (2)编写路由

@router.post("/query/pydantic/nestedModel")
async def nestedModelDemo(param: request.NestedParam):
    """
    请求体-嵌套模型接收-演示
    """
    return {
        "msg": "嵌套模型接收使用-示例",
        "result": {
            "param": param,
        }
    }

(3)验证结果

请求url:  http://localhost:8000/demo/query/pydantic/multipleParamReceive

请求参数:

{
    "teacher_id": 11,
    "teacher_name": "gql",
    "class_list" : [
        {
            "class_name" : "string",
            "class_num" : 0
        }
    ]
   
}

返回值:

{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "body",
        "class_list",
        0,
        "teacher_name"
      ],
      "msg": "Field required",
      "input": {
        "class_name": "string",
        "class_num": 0
      }
    }
  ]
}

1.6 Field 模型

(1) Field 模型使用场景

在开发Api和写Api文档的过程中,经常会遇到以下场景:

把每个字段的中文说明加上,这样方便使用者理解;

参数设置默认值,如果参数有值则覆盖;

对于每个字段,最好在文档中,都能给个示例;

入参名和定义名不一致,如何处理?比如定义的属性是className,入参是class_name;

(2)Field 参数预览

def Field(
    default: Any = Undefined, #设置参数默认值,场景2
    *,
    default_factory: Optional[NoArgAnyCallable] = None, #指定一个函数,该函数的返回值将被用作字段的默认值
    alias: Optional[str] = None,# 字段别名,场景4
    title: Optional[str] = None,
    description: Optional[str] = None,# 字段说明,用于文档生成,,场景1
    exclude: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny', Any]] = None,
    include: Optional[Union['AbstractSetIntStr', 'MappingIntStrAny', Any]] = None,
    const: Optional[bool] = None,
    gt: Optional[float] = None, # 条件判断:大于
    ge: Optional[float] = None, # 条件判断:等于
    lt: Optional[float] = None, # 条件判断:小于
    le: Optional[float] = None, # 条件判断:小于等于
    multiple_of: Optional[float] = None, # 用于指定数值字段的值必须是某个特定值的倍数
    allow_inf_nan: Optional[bool] = None,
    max_digits: Optional[int] = None,
    decimal_places: Optional[int] = None,
    min_items: Optional[int] = None,# 用于验证列表或元组字段的元素个数不少于指定的最小值
    max_items: Optional[int] = None,# 用于验证列表或元组字段的元素个数不大于指定的最大值
    unique_items: Optional[bool] = None, # 设置true,则验证列表或元组字段的元素不能重复
    min_length: Optional[int] = None,# 字符串字段的最小长度
    max_length: Optional[int] = None,# 字符串字段的最大长度
    allow_mutation: bool = True,
    regex: Optional[str] = None, # 正则验证
    discriminator: Optional[str] = None,
    repr: bool = True,
    **extra: Any,) -> Any:

参数: example,用来给出参数示例

(3)定义参数模型

在app/parameter/demo_param.py文件中,新增内容如下:

class FieldParam(BaseModel):
    """
    Field使用示例
    """
    name: str = Field(default='', max_length=4, description="填写姓名", example="张三")
    age: int = Field(default='', gt=18, description="填写年龄,必须大于18", example=20)
    phone: str = Field(default='', description="填写手机号", example="17600000000", regex=r'^1\d{10}$')
    likes: List[str] = Field(default='[]', description="填写爱好", example=["篮球", "足球"], min_items=2,
                             unique_items=True)

(4)参数查看

2.参数验证

2.1 Pydantic介绍

官方文档:Welcome to Pydantic - Pydantic

Pydantic 是一个 Python 库,用于数据验证和设置,特别是用于验证数据模型。它通过声明性的方式定义数据模型,并提供了强大的数据验证和转换功能。Pydantic 最初是为 FastAPI 框架设计的,但它也可以在其他 Python 项目中独立使用。

使用Pydantic 的本质,其实就是如何编写对应的数据验证规则,下面列举一些常用的规则

2.2 常用验证

下面列举一些常用的验证规则:

  • 基本数据类型:int, float, str, bool;

  • 可选参数: Optional[type] 表示可选参数, Union[x, None]也可以表示可选;

  • 整数范围: 结合conint函数判断数字范围 ,如age: conint(ge=18, le=30); ge:大于等于、gt:大于、le:小于等于、lt:小于

  • 字符长度: 结合constr函数判断字符长度,如: constr(min_length=6, max_length=10);

  • 正则表达式: 使用constr函数中的参数regex ,可以用于进行正则表达式验证;

  • 枚举验证: 使用Enum 定义枚举类,验证。

  • 列表类型: 使用List[type] 来限制列表值的类型,并尝试把参数转成对应的类型。

  • 字典类型:Dict[key_type, value_type] 来限制字典key和val类型,并尝试把参数转成对应的类型。

from enum import Enum
from typing import Union, Optional, List, Dict
# 导入pydantic对应的模型基类
from pydantic import BaseModel, constr, conint

class GenderEnum(str, Enum):
    """
    性别枚举
    """
    male = "男"
    female = "女"

class PydanticVerifyParam(BaseModel):
    """
    用来学习使用pydantic模型验证
    """
    user_name: str  # 基本类型
    age: conint(ge=18, le=30)  # 整数范围:18 <= age <= 30
    password: constr(min_length=6, max_length=10)  # 字符长度
    phone: constr(regex=r'^1\d{10}$')  # 正则验证手机号
    address: Optional[str] = None  # 可选参数
    sex: GenderEnum  # 枚举验证,只能传: 男和女
    likes: List[str]  # 值会自动转成传字符串列表
    scores: Dict[str, float]  # key会转成字符串,val 会转成浮点型

注意:上面列举的都是基本使用,实际中可以组合进行多项组合使用,如items:List[constr(min_length=1, max_length=3)] : 限制列表中的每个字符串长度的范围

2.3 自定义验证

@validator 装饰器用于定义自定义验证函数,具体是如下:

from pydantic import BaseModel, constr, conint, validator


class PydanticVerifyParam(BaseModel):
    """
    用来学习使用pydantic模型验证
    """
    user_name: str  # 基本类型
    ...
    @validator("user_name")
    def validateUsername(cls, value: str):
        """
        自定义验证函数
        """
        if value.find("傻") > -1:
            raise ValueError("user_name不能包含敏感词")
        return value

验证:

url http://localhost:8000/demo/query/receive

参数:

{
    "user_name" : "你好傻",
    "age" : 12,
    "city" : "beijing"
}

app/router/demo_router.py

from typing import Union
# 导入APIRouter
from fastapi import APIRouter
# 实例化APIRouter实例
router = APIRouter(tags=["默认路由"])
# 如果没有优化导入,这行会报错
from ..parameter import DemoParam
from ..parameter import PydanticVerifyParam

router = APIRouter(
    prefix="/demo",
    tags=["演示接口"]
)

@router.post("/query/receive")
async def bodyReceive(body: DemoParam):
    """
    请求体参数接受-演示
    """
    PydanticVerifyParam.validateUsername(body.user_name)
    return {
        "msg": "请求体参数接受",
        "result": {
            "body": body,
        }
    }

验证结果:

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 401, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 70, in __call__
    return await self.app(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\fastapi\applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\middleware\errors.py", line 186, in __call__
    raise exc
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\middleware\exceptions.py", line 65, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\routing.py", line 754, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\routing.py", line 774, in app
    await route.handle(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\routing.py", line 295, in handle
    await self.app(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\routing.py", line 77, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\starlette\routing.py", line 74, in app
    response = await f(request)
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\fastapi\routing.py", line 297, in app
    raw_response = await run_endpoint_function(
  File "D:\devpinstall\anaconda3\envs\dbgpt_env\lib\site-packages\fastapi\routing.py", line 210, in run_endpoint_function
    return await dependant.call(**values)
  File "F:\pyproject\DB-GPT\dbgpt\app\router\demo_router.py", line 20, in bodyReceive
    PydanticVerifyParam.validateUsername(body.user_name)
  File "F:\pyproject\DB-GPT\dbgpt\app\parameter\PydanticVerifyParam.py", line 15, in validateUsername
    raise ValueError("user_name不能包含敏感词")
ValueError: user_name不能包含敏感词

2.4 其他验证

  • EmailStr: 用于验证字符串是否是有效的电子邮件地址。

  • IPvAnyAddress: 用于验证字符串是否是有效的 IPv4 或 IPv6 地址。

  • StrictBool: 用于验证字符串是否是严格的布尔值(true 或 false)。

  • AnyHttpUrl: 用于验证字符串是否是有效的 URL,包括以 http 或 https 开头的URL。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独行客-编码爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值