【Python开发】FastAPI 02:请求参数—路径参数、查询参数

进行接口请求时,请求参数是重中之重了!请求参数指客户端向服务端发送请求时,需要传递给服务端的参数,包括路径参数、查询参数、请求体等。举个例子,如果客户端想要获取某个用户的信息,可以向服务端发送一个 GET 请求,并在请求中传递用户的 ID,这个 ID 就是请求参数。本篇文章介绍路径参数和查询参数。

目录

1 基础_路径参数

1.1 声明路径参数

1.2 声明路径参数声明的类型

1.3 路径操作顺序

1.4 预设值

① 创建一个 Enum 类

② 声明路径参数

③ 使用 Python 枚举类型

1.5 包含路径的路径参数

2 基础_查询参数

2.1 声明查询参数

2.2 默认值

2.3 可选参数

2.4 类型转换

3 数值校验_查询参数

3.1 Query 使用

3.2 通过 Query 校验

① default—默认值

② max_length/min_length—字符最大/小长度

③ regex—正则表达式

④ 声明必需参数

⑤ 声明更多元数据

3.3 查询参数设定为列表/多个值

① List[str]

② list

4 数值校验_路径参数

4.1 Path 使用

4.2 按需对参数排序

4.3 通过 Path 校验

① ge—大于等于

② gt—大于,le—小于等于

③ 总结


📌源码地址:

https://gitee.com/yinyuu/fast-api_study_yinyu

1 基础_路径参数

1.1 声明路径参数

首先简单来声明下路径参数(使用与 Python 格式化字符串相同的语法):

from fastapi import FastAPI

app = FastAPI()

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

代码运行后,路径参数 item_id 的值将作为参数 item_id 传递给你的函数。此时 item_id 不限制类型,输入字符串还是数字均可,不过最终都会被后台转化成字符串。

运行示例并访问 http://127.0.0.1:8000/items/yinyu,将会看到如下响应 👇

若访问 http://127.0.0.1:8000/items/88 响应如下,可看到 88 已转化为字符串 "88"

1.2 声明路径参数声明的类型

然后简单声明下路径参数声明的类型(使用标准的 Python 类型标注):

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

该处的声明类型可以理解为限制类型,也就说该路径参数(查询参数相同)声明类型后,你实际在请求该路径时,只能按照提前声明好的类型进行请求。

此时访问 http://127.0.0.1:8000/items/88,响应正常,88 即为 int 类型。

若此时访问 http://127.0.0.1:8000/items/yinyu,将会返回报错信息,因为 yinyu 是字符串类型,而不是  int 类型,这就是声明了类型的作用 👇

所有的数据校验都由 Pydantic 在幕后完成。

你可以使用同样的类型声明来声明 str、float、bool 以及许多其他的复合数据类型。

📌 文档

此时访问 http://127.0.0.1:8000/docs,将看到自动生成的交互式 API 文档:

1.3 路径操作顺序

在创建路径操作时,会发现有些情况下路径是相同的。

比如:/users/me,假设它用来获取关于当前用户的数据;/users/{user_id} ,假设它用来通过用户 ID 获取关于特定用户的数据。

由于路径操作是按顺序依次运行的,你需要确保路径 /users/me 声明在路径 /users/{user_id} 之前!

from fastapi import FastAPI

app = FastAPI()

@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}

@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}

否则,/users/{user_id} 的路径还将与 /users/me 相匹配,"认为"自己正在接收一个值为 "me" 的 user_id 参数。

1.4 预设值

有时只需要给路径参数传递几个常用并且固定的有效值,那么我就可以通过枚举来定制预设值。

① 创建一个 Enum 类

首先导入 Enum 并创建一个继承自 str Enum 的子类,通过从 str 继承,API 文档将能够知道这些值必须为 string 类型并且能够正确地展示出来。

然后创建具有固定值的类属性,比如 yinyu s1s2

from enum import Enum

class ModelName(str, Enum):
    yinyu = "yinyu_v"
    s1 = "s1_v"
    s2 = "s2_v"

枚举(或 enums)从 3.4 版本起在 Python 中可用。

② 声明路径参数

使用定义好的枚举类(ModelName)创建一个带有类型标注的路径参数:

'''包含枚举的路径参数'''
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):

路径参数 model_name 的值将传递给函数 get_model 的参数 model_name,并且这个值的取值范围只能是 ModelName 枚举类中类属性的值。

③ 使用 Python 枚举类型

此时路径参数的值将是一个枚举成员,那么可以做如下的事情,包括比较枚举成员if ModelName is ModelName.yinyu)、获取枚举值(model_name.value)、返回枚举成员(ModelName.s2)等~

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    # 第 1 种判断方式
    if ModelName is ModelName.yinyu:
        return {"model_name": model_name, "message": "yinyu get"}
    # 第 2 种判断方式,效果一样
    if model_name.value == "s1_name":
        return {"model_name": model_name, "message": "s1 get"}
    else:
        return {"model_name": ModelName.s2, "message": "s2 get"}

📌 请求该路径

完全代码:

from enum import Enum
from fastapi import FastAPI

app = FastAPI()

class ModelName(str, Enum):
    yinyu = "yinyu_v"
    s1 = "s1_v"
    s2 = "s2_v"

'''包含枚举的路径参数'''
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    # 第 1 种判断方式
    if ModelName is ModelName.yinyu:
        return {"model_name": model_name, "message": "yinyu get"}
    # 第 2 种判断方式,效果一样
    if model_name.value == "s1_name":
        return {"model_name": model_name, "message": "s1 get"}
    else:
        return {"model_name": ModelName.s2, "message": "s2 get"}

访问 127.0.0.1:8000/models/yinyu_v,响应如下 👇,可以确定地是路径参数中的枚举类校验是以枚举成员的属性为准的,比如 yinyu 的属性值为 yinyu_v

1.5 包含路径的路径参数

适用于文件操作,假设现在你有一个路径操作:/files/{file_path},但是你需要 file_path 本身包含一个 路径, 比如 home/yinyu/myfile.txt

此时使用 Path 转换器就可以进行转换,你可以这样使用它:

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

参数的名称为 file_path,结尾部分的 :path 说明该参数应匹配任意的路径。

你可能会需要参数包含 /home/johndoe/myfile.txt,以斜杠(/)开头。此时,URL 将会是 /files//home/johndoe/myfile.txt,在files 和 home 之间有一个双斜杠(//)。

2 基础_查询参数

声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数

2.1 声明查询参数

简单声明两个查询参数,可以看到,他和路径参数是不一样的 👇

from fastapi import FastAPI

app = FastAPI()

@app.get("/items1/")
async def read_item(skip: int, limit: int):
    return {"skip": skip,"limit": limit}

查询字符串是键值对的集合,这些键值对位于 URL 之后,并以 & 符号分隔。

此时访问 http://127.0.0.1:8000/items/?skip=0&limit=10 👇,同时该查询参数也是必需的,因为这两个查询参数没有设置默认值None

其中:

  • skip:对应的值为 0
  • limit:对应的值为 10

由于它们是 URL 的一部分,因此它们的"原始值"是字符串。 但是为它们声明了 Python 类型(在上面的示例中为 int)时,它们将转换为该类型并针对该类型进行校验。

2.2 默认值

由于查询参数不是路径的固定部分,因此它们可以是可选的,并且可以有默认值。比如给上边接口中的查询参数分别设置默认值为 010

@app.get("/items2/")
async def read_item(skip: int = 0, limit: int = 10):
    return {"skip": skip,"limit": limit}

若此时访问 127.0.0.1:8000/items2/,虽然未设定查询参数,但是查询参数会成为事先设定好的默认值 👇

若此时访问 127.0.0.1:8000/items2/?skip=20skip 将成为在 URL 中设定的值,而 limit 依旧是默认值 👇

2.3 可选参数

通过同样的方式,你可以将它们的默认值设置为 None 来声明可选查询参数:

from typing import Union 

@app.get("/items3/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

其中,查询参数 q 将是可选的,并且默认值为 NoneUnion 的作用是传递两种参数,q 既可以是 str,也可以为空。

若此时访问 127.0.0.1:8000/items3/yinyu,正常返回,说明查询参数 q 已经可选 👇

若此时访问 127.0.0.1:8000/items3/yinyu?q=,也是正常返回,可以简单理解为:q=None,这就是Union 的作用。

 若此时访问 127.0.0.1:8000/items3/yinyu?q=yinyu_v,正常返回 👇

2.4 类型转换

你还可以声明 bool 类型,它们将被自动转换:

@app.get("/items4/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

此时,访问 http://127.0.0.1:8000/items4/yinyu?short=1 ,其中 1 也可以是 True 、true、on、yes中其中一个,包括任何其他的变体形式(大写,首字母大写等等),你的函数接收的 short 参数都会是布尔值 True

对于值为 False 的情况也是一样的。

3 数值校验_查询参数

3.1 Query 使用

Query 是 FastAPI 专门用来装饰查询参数的类,简单使用如下 👇

from typing import Union
#首先从 fastapi 导入 Query
from fastapi import FastAPI,Query

app = FastAPI()

@app.get("/items1/") #然后使用 Query 修饰查询参数
async def read_items(q: Union[str, None] = Query(description="items1 interface")):
    query_items = {"q": q}
    return query_items

description 是 Query 中的一个字段,用来描述该参数;Union 的作用是传递两种参数,q 既可以是 str,也可以为空。

📌 文档

此时访问 http://127.0.0.1:8000/docs,可看到描述信息~

3.2 通过 Query 校验

除了上边的 description ,Query 还可以进行一些额外的校验

① default—默认值

@app.get("/items21/")
async def read_items(q: Union[str, None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

此时,该查询参数等同于 :q: Union[str, None] = None

当然,你也可以将默认值设为有效值:

@app.get("/items22/")
async def read_items(q: str = Query(default="fixedquery")):
    query_items = {"q": q}
    return query_items

此时,该查询参数等同于 :q = "fixedquery"

② max_length/min_length—字符最大/小长度

@app.get("/items2/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50, min_length=3)):
    query_items = {"q": q}
    return query_items

max_length 用于限制查询参数的最大长度,min_length 用于限制查询参数的最小长度,若超出限制将会报错。

注意:max_length 和 min_length 仅可用于 str 类型的查询参数。

③ regex—正则表达式

@app.get("/items4/")
async def read_items(
    q: Union[str, None] = Query(default=None, regex="^fixedquery$")
):
    query_items = {"q": q}
    return query_items

这个指定的正则表达式通过以下规则检查接收到的参数值:

  • ^:以该符号之后的字符开头,符号之前没有字符。
  • fixedquery: 匹配 fixedquery。
  • $: 到此结束,在 fixedquery 之后不匹配任何字符。

也就是说该接口的查询参数只能是"fixedquery",不然就会报错,当然其他的正则表达式都是可以拿来用的!

④ 声明必需参数

首先当使用 Query ,同时需要声明一个值是必需的,只需不声明默认参数:

@app.get("/items5/")
async def read_items(q: str = Query(min_length=3)):
    query_items = {"q": q}
    return query_items

有另一种方法可以显式的声明一个值是必需的,即将该参数的默认值设为 ...

@app.get("/items6/")
async def read_items(q: str = Query(default=..., min_length=3)):
    query_items = {"q": q}
    return query_items

如果不想使用 ...,那么可以从 Pydantic 导入并使用 Required

from pydantic import Required

@app.get("/items7/")
async def read_items(q: str = Query(default=Required, min_length=3)):
    query_items = {"q": q}
    return query_items

总的来说,我认为直接省略 default 即可。

⑤ 声明更多元数据

你可以为查询参数声明额外的校验和元数据,比如:

  • alias:参数别名
  • title:参数标题
  • description:描述
  • deprecated:弃用
@app.get("/items8/")
async def read_items(
    q: Union[str, None] = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        alias="item-query", #别名
        deprecated=True
    )
):
    query_items = {"q": q}
    return query_items

大家不妨自己去试一下,然后访问 http://127.0.0.1:8000/docs 查看这些元数据在文档中的表现~

3.3 查询参数设定为列表/多个值

我们还可以使用 Query 去接收一组值。

① List[str]

一种方式是使用 List[str] 来接收一组值:

@app.get("/items9/")
async def read_items(q: Union[List[str], None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

此时,访问 localhost:8000/items9/?q=foo&q=bar 👇

这是因为参数 q 中以一个 Python list 的形式接收到查询参数 q 的多个值(foo bar)。

同样的,我们可以定义在没有任何给定值时的默认 list 值:

@app.get("/items10/")
async def read_items(q: List[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

② list

你也可以直接使用 list 代替 List [str]

@app.get("/items11/")
async def read_items(q: list = Query(default=[])):
    query_items = {"q": q}
    return query_items

注意:在这种情况下 FastAPI 将不会检查列表的内容。

例如,List[int] 将检查(并记录到文档)列表的内容必须是整数,但是单独的 list 不会。

4 数值校验_路径参数

和前边使用 Query 声明查询参数的更多验证和元数据的方法相同,我们也可以通过Path声明路径参数的相同类型的验证和元数据。

4.1 Path 使用

简单来说,引入 Path,然后

#1.从 fastapi 导入 Path
from fastapi import FastAPI,Path

app = FastAPI()

@app.get("/items1/{item_id}")
async def read_items(
    #2.声明路径参数 item_id的 title 元数据值
    item_id: int = Path(title="The ID of the item to get"),
):
    return {"item_id": item_id}

路径参数总是必需的,你可以在声明时使用 ... 将其标记为必需参数。 不过,即使你不设置或使用 None 声明路径参数,那么也不会有任何影响,它依然会是必需参数。

4.2 按需对参数排序

如果你要声明 q 查询参数而不使用 Query 或任何默认值,并且使用 Path 声明路径参数 item_id 和使用不同的顺序,则 Python 对此有一些特殊的语法。语法规则如下:

  • 传递 * 作为函数的第一个参数。

Python 不会对该 * 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。

@app.get("/items2/{item_id}")
async def read_items(
        *, 
        item_id: int = Path(title="The ID of the item to get"), 
        q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

此时访问 localhost:8000/items2/2222?q=yinyu 👇

若将 q 和 item_id 顺序调换一下,实际接口请求时也不会收到影响:

@app.get("/items2/{item_id}")
async def read_items(
        *,
        q: str,
        item_id: int = Path(title="The ID of the item to get"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

4.3 通过 Path 校验

使用 Query Path(以及后边其他的类)不仅可以声明字符串约束,还可以声明数值约束(intfloat 等数值类型)。

① ge—大于等于

比如,添加 ge=1 后,item_id 将必须是一个大于(greater than)或等于(equal1 的整数。

@app.get("/items3/{item_id}")
async def read_items(
    *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

② gt—大于,le—小于等于

同样的规则适用于:

  • gt:大于(greater than)
  • le:小于等于(less than or equal)
@app.get("/items4/{item_id}")
async def read_items(
    *,item_id: int = Path(title="The ID of the item to get", gt=0, le=1000),q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

③ 总结

  • gt:大于(greater than)
  • ge:大于等于(greater than or equal)
  • lt:小于(less than)
  • le:小于等于(less than or equal)

Query、Path 以及你后面会看到的其他类继承自一个共同的 Param 类(不需要直接使用它)。 而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。