与使用 Query 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 Path 为路径参数声明相同类型的校验和元数据。

导入 Path

首先,从 fastapi 导入 Path

Python 3.10+Python 3.9+Python 3.8+Python 3.10+ non-AnnotatedPython 3.8+ non-Annotated

  1. from typing import Annotated
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: Annotated[int, Path(title="The ID of the item to get")],
  7. q: Annotated[str | None, Query(alias="item-query")] = None,
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`
  1. from typing import Annotated, Union
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: Annotated[int, Path(title="The ID of the item to get")],
  7. q: Annotated[Union[str, None], Query(alias="item-query")] = None,
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`
  1. from typing import Union
  2. from fastapi import FastAPI, Path, Query from typing_extensions import Annotated
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: Annotated[int, Path(title="The ID of the item to get")],
  7. q: Annotated[Union[str, None], Query(alias="item-query")] = None,
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`

Tip

尽可能选择使用 Annotated 的版本。

  1. from fastapi import FastAPI, Path, Query
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. item_id: int = Path(title="The ID of the item to get"),
  6. q: str | None = Query(default=None, alias="item-query"),
  7. ):
  8. results = {"item_id": item_id}
  9. if q:
  10. results.update({"q": q})
  11. return results`

Tip

尽可能选择使用 Annotated 的版本。

  1. from typing import Union
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: int = Path(title="The ID of the item to get"),
  7. q: Union[str, None] = Query(default=None, alias="item-query"),
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`

声明元数据

你可以声明与 Query 相同的所有参数。

例如,要声明路径参数 item_idtitle 元数据值,你可以输入:

Python 3.10+Python 3.9+Python 3.8+Python 3.10+ non-AnnotatedPython 3.8+ non-Annotated

  1. from typing import Annotated
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: Annotated[int, Path(title="The ID of the item to get")], q: Annotated[str | None, Query(alias="item-query")] = None,
  7. ):
  8. results = {"item_id": item_id}
  9. if q:
  10. results.update({"q": q})
  11. return results`
  1. from typing import Annotated, Union
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: Annotated[int, Path(title="The ID of the item to get")], q: Annotated[Union[str, None], Query(alias="item-query")] = None,
  7. ):
  8. results = {"item_id": item_id}
  9. if q:
  10. results.update({"q": q})
  11. return results`
  1. from typing import Union
  2. from fastapi import FastAPI, Path, Query
  3. from typing_extensions import Annotated
  4. app = FastAPI()
  5. @app.get("/items/{item_id}")
  6. async def read_items(
  7. item_id: Annotated[int, Path(title="The ID of the item to get")], q: Annotated[Union[str, None], Query(alias="item-query")] = None,
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`

Tip

尽可能选择使用 Annotated 的版本。

  1. from fastapi import FastAPI, Path, Query
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. item_id: int = Path(title="The ID of the item to get"), q: str | None = Query(default=None, alias="item-query"),
  6. ):
  7. results = {"item_id": item_id}
  8. if q:
  9. results.update({"q": q})
  10. return results`

Tip

尽可能选择使用 Annotated 的版本。

  1. from typing import Union
  2. from fastapi import FastAPI, Path, Query
  3. app = FastAPI()
  4. @app.get("/items/{item_id}")
  5. async def read_items(
  6. item_id: int = Path(title="The ID of the item to get"), q: Union[str, None] = Query(default=None, alias="item-query"),
  7. ):
  8. results = {"item_id": item_id}
  9. if q:
  10. results.update({"q": q})
  11. return results`

路径参数总是必需的,因为它必须是路径的一部分。

所以,你应该在声明时使用 ... 将其标记为必需参数。

然而,即使你使用 None 声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。

按需对参数排序

假设你想要声明一个必需的 str 类型查询参数 q

而且你不需要为该参数声明任何其他内容,所以实际上你并不需要使用 Query

但是你仍然需要使用 Path 来声明路径参数 item_id

如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。

但是你可以对其重新排序,并将不带默认值的值(查询参数 q)放到最前面。

FastAPI 来说这无关紧要。它将通过参数的名称、类型和默认值声明(QueryPath 等)来检测参数,而不在乎参数的顺序。

因此,你可以将函数声明为:

Python 3.8 non-Annotated

Tip

尽可能选择使用 Annotated 的版本。

  1. from fastapi import FastAPI, Path
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")):
  5. results = {"item_id": item_id}
  6. if q:
  7. results.update({"q": q})
  8. return results`

按需对参数排序的技巧

如果你想不使用 Query 声明没有默认值的查询参数 q,同时使用 Path 声明路径参数 item_id,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。

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

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

  1. from fastapi import FastAPI, Path
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
  5. results = {"item_id": item_id}
  6. if q:
  7. results.update({"q": q})
  8. return results`

数值校验:大于等于

使用 QueryPath(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。

像下面这样,添加 ge=1 后,item_id 将必须是一个大于(greater than)或等于(equal)1 的整数。

  1. from fastapi import FastAPI, Path
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str ):
  6. results = {"item_id": item_id}
  7. if q:
  8. results.update({"q": q})
  9. return results`

数值校验:大于和小于等于

同样的规则适用于:

  • gt:大于(greater than)
  • le:小于等于(less than or equal)
  1. from fastapi import FastAPI, Path
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. *,
  6. item_id: int = Path(title="The ID of the item to get", gt=0, le=1000), q: str,
  7. ):
  8. results = {"item_id": item_id}
  9. if q:
  10. results.update({"q": q})
  11. return results`

数值校验:浮点数、大于和小于

数值校验同样适用于 float 值。

能够声明 gt 而不仅仅是 ge 在这个前提下变得重要起来。例如,你可以要求一个值必须大于 0,即使它小于 1

因此,0.5 将是有效值。但是 0.00 不是。

对于 lt 也是一样的。

  1. from fastapi import FastAPI, Path, Query
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. *,
  6. item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
  7. q: str,
  8. size: float = Query(gt=0, lt=10.5), ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results`

总结

你能够以与 查询参数和字符串校验 相同的方式使用 QueryPath(以及其他你还没见过的类)声明元数据和字符串校验。

而且你还可以声明数值校验:

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

Info

QueryPath 以及你后面会看到的其他类继承自一个共同的 Param 类(不需要直接使用它)。

而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。

技术细节

当你从 fastapi 导入 QueryPath 和其他同类对象时,它们实际上是函数。

当被调用时,它们返回同名类的实例。

如此,你导入 Query 这个函数。当你调用它时,它将返回一个同样命名为 Query 的类的实例。

因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。

这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。