请求体 - 多个参数

既然我们已经知道了如何使用 PathQuery,下面让我们来了解一下请求体声明的更高级用法。

混合使用 PathQuery 和请求体参数

首先,毫无疑问地,你可以随意地混合使用 PathQuery 和请求体参数声明,FastAPI 会知道该如何处理。

你还可以通过将默认值设置为 None 来将请求体参数声明为可选参数:

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
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: str | None = None
  8. price: float
  9. tax: float | None = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(
  12. item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
  13. q: str | None = None,
  14. item: Item | None = None,
  15. ):
  16. results = {"item_id": item_id}
  17. if q:
  18. results.update({"q": q})
  19. if item:
  20. results.update({"item": item})
  21. return results
  1. from typing import Annotated, Union
  2. from fastapi import FastAPI, Path
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(
  12. item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
  13. q: Union[str, None] = None,
  14. item: Union[Item, None] = None,
  15. ):
  16. results = {"item_id": item_id}
  17. if q:
  18. results.update({"q": q})
  19. if item:
  20. results.update({"item": item})
  21. return results
  1. from typing import Union
  2. from fastapi import FastAPI, Path
  3. from pydantic import BaseModel
  4. from typing_extensions import Annotated
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: str
  8. description: Union[str, None] = None
  9. price: float
  10. tax: Union[float, None] = None
  11. @app.put("/items/{item_id}")
  12. async def update_item(
  13. item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
  14. q: Union[str, None] = None,
  15. item: Union[Item, None] = None,
  16. ):
  17. results = {"item_id": item_id}
  18. if q:
  19. results.update({"q": q})
  20. if item:
  21. results.update({"item": item})
  22. return results

Tip

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

  1. from fastapi import FastAPI, Path
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class Item(BaseModel):
  5. name: str
  6. description: str | None = None
  7. price: float
  8. tax: float | None = None
  9. @app.put("/items/{item_id}")
  10. async def update_item(
  11. *,
  12. item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
  13. q: str | None = None,
  14. item: Item | None = None,
  15. ):
  16. results = {"item_id": item_id}
  17. if q:
  18. results.update({"q": q})
  19. if item:
  20. results.update({"item": item})
  21. return results

Tip

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

  1. from typing import Union
  2. from fastapi import FastAPI, Path
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(
  12. *,
  13. item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
  14. q: Union[str, None] = None,
  15. item: Union[Item, None] = None,
  16. ):
  17. results = {"item_id": item_id}
  18. if q:
  19. results.update({"q": q})
  20. if item:
  21. results.update({"item": item})
  22. return results

Note

请注意,在这种情况下,将从请求体获取的 item 是可选的。因为它的默认值为 None

多个请求体参数

在上面的示例中,路径操作将期望一个具有 Item 的属性的 JSON 请求体,就像:

  1. {
  2. "name": "Foo",
  3. "description": "The pretender",
  4. "price": 42.0,
  5. "tax": 3.2
  6. }

但是你也可以声明多个请求体参数,例如 itemuser

Python 3.10+Python 3.8+

  1. from fastapi import FastAPI
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class Item(BaseModel):
  5. name: str
  6. description: str | None = None
  7. price: float
  8. tax: float | None = None
  9. class User(BaseModel):
  10. username: str
  11. full_name: str | None = None
  12. @app.put("/items/{item_id}")
  13. async def update_item(item_id: int, item: Item, user: User):
  14. results = {"item_id": item_id, "item": item, "user": user}
  15. return results
  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Union[str, None] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(item_id: int, item: Item, user: User):
  15. results = {"item_id": item_id, "item": item, "user": user}
  16. return results

在这种情况下,FastAPI 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。

因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体:

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. },
  8. "user": {
  9. "username": "dave",
  10. "full_name": "Dave Grohl"
  11. }
  12. }

Note

请注意,即使 item 的声明方式与之前相同,但现在它被期望通过 item 键内嵌在请求体中。

FastAPI 将自动对请求中的数据进行转换,因此 item 参数将接收指定的内容,user 参数也是如此。

它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。

请求体中的单一值

与使用 QueryPath 为查询参数和路径参数定义额外数据的方式相同,FastAPI 提供了一个同等的 Body

例如,为了扩展先前的模型,你可能决定除了 itemuser 之外,还想在同一请求体中具有另一个键 importance

如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数。

但是你可以使用 Body 指示 FastAPI 将其作为请求体的另一个键进行处理。

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 Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: str | None = None
  8. price: float
  9. tax: float | None = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: str | None = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(
  15. item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
  16. ):
  17. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  18. return results
  1. from typing import Annotated, Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Union[str, None] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(
  15. item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
  16. ):
  17. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  18. return results
  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. from typing_extensions import Annotated
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: str
  8. description: Union[str, None] = None
  9. price: float
  10. tax: Union[float, None] = None
  11. class User(BaseModel):
  12. username: str
  13. full_name: Union[str, None] = None
  14. @app.put("/items/{item_id}")
  15. async def update_item(
  16. item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
  17. ):
  18. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  19. return results

Tip

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

  1. from fastapi import Body, FastAPI
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class Item(BaseModel):
  5. name: str
  6. description: str | None = None
  7. price: float
  8. tax: float | None = None
  9. class User(BaseModel):
  10. username: str
  11. full_name: str | None = None
  12. @app.put("/items/{item_id}")
  13. async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
  14. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  15. return results

Tip

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

  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Union[str, None] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
  15. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  16. return results

在这种情况下,FastAPI 将期望像这样的请求体:

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. },
  8. "user": {
  9. "username": "dave",
  10. "full_name": "Dave Grohl"
  11. },
  12. "importance": 5
  13. }

同样的,它将转换数据类型,校验,生成文档等。

多个请求体参数和查询参数

当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。

由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 Query,你可以仅执行以下操作:

  1. q: str = None

比如:

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 Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: str | None = None
  8. price: float
  9. tax: float | None = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: str | None = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(
  15. *,
  16. item_id: int,
  17. item: Item,
  18. user: User,
  19. importance: Annotated[int, Body(gt=0)],
  20. q: str | None = None,
  21. ):
  22. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  23. if q:
  24. results.update({"q": q})
  25. return results
  1. from typing import Annotated, Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Union[str, None] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(
  15. *,
  16. item_id: int,
  17. item: Item,
  18. user: User,
  19. importance: Annotated[int, Body(gt=0)],
  20. q: Union[str, None] = None,
  21. ):
  22. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  23. if q:
  24. results.update({"q": q})
  25. return results
  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. from typing_extensions import Annotated
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: str
  8. description: Union[str, None] = None
  9. price: float
  10. tax: Union[float, None] = None
  11. class User(BaseModel):
  12. username: str
  13. full_name: Union[str, None] = None
  14. @app.put("/items/{item_id}")
  15. async def update_item(
  16. *,
  17. item_id: int,
  18. item: Item,
  19. user: User,
  20. importance: Annotated[int, Body(gt=0)],
  21. q: Union[str, None] = None,
  22. ):
  23. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  24. if q:
  25. results.update({"q": q})
  26. return results

Tip

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

  1. from fastapi import Body, FastAPI
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class Item(BaseModel):
  5. name: str
  6. description: str | None = None
  7. price: float
  8. tax: float | None = None
  9. class User(BaseModel):
  10. username: str
  11. full_name: str | None = None
  12. @app.put("/items/{item_id}")
  13. async def update_item(
  14. *,
  15. item_id: int,
  16. item: Item,
  17. user: User,
  18. importance: int = Body(gt=0),
  19. q: str | None = None,
  20. ):
  21. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  22. if q:
  23. results.update({"q": q})
  24. return results

Tip

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

  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Union[str, None] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(
  15. *,
  16. item_id: int,
  17. item: Item,
  18. user: User,
  19. importance: int = Body(gt=0),
  20. q: Union[str, None] = None,
  21. ):
  22. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  23. if q:
  24. results.update({"q": q})
  25. return results

Info

Body 同样具有与 QueryPath 以及其他后面将看到的类完全相同的额外校验和元数据参数。

嵌入单个请求体参数

假设你只有一个来自 Pydantic 模型 Item 的请求体参数 item

默认情况下,FastAPI 将直接期望这样的请求体。

但是,如果你希望它期望一个拥有 item 键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 Body 参数 embed

  1. item: Item = Body(embed=True)

比如:

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 Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: str | None = None
  8. price: float
  9. tax: float | None = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
  12. results = {"item_id": item_id, "item": item}
  13. return results
  1. from typing import Annotated, Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
  12. results = {"item_id": item_id, "item": item}
  13. return results
  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. from typing_extensions import Annotated
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: str
  8. description: Union[str, None] = None
  9. price: float
  10. tax: Union[float, None] = None
  11. @app.put("/items/{item_id}")
  12. async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
  13. results = {"item_id": item_id, "item": item}
  14. return results

Tip

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

  1. from fastapi import Body, FastAPI
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class Item(BaseModel):
  5. name: str
  6. description: str | None = None
  7. price: float
  8. tax: float | None = None
  9. @app.put("/items/{item_id}")
  10. async def update_item(item_id: int, item: Item = Body(embed=True)):
  11. results = {"item_id": item_id, "item": item}
  12. return results

Tip

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

  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(item_id: int, item: Item = Body(embed=True)):
  12. results = {"item_id": item_id, "item": item}
  13. return results

在这种情况下,FastAPI 将期望像这样的请求体:

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. }
  8. }

而不是:

  1. {
  2. "name": "Foo",
  3. "description": "The pretender",
  4. "price": 42.0,
  5. "tax": 3.2
  6. }

总结

你可以添加多个请求体参数到路径操作函数中,即使一个请求只能有一个请求体。

但是 FastAPI 会处理它,在函数中为你提供正确的数据,并在路径操作中校验并记录正确的模式。

你还可以声明将作为请求体的一部分所接收的单一值。

你还可以指示 FastAPI 在仅声明了一个请求体参数的情况下,将原本的请求体嵌入到一个键中。