User Guide

First Steps

最简单的FastAPI文件结构:

1
2
3
4
5
6
7
8
9
# --* saved as main.py *-- #
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
return {"message": "Hello World"}

执行指令

1
uvicorn main:app --reload

以启动服务器。

The command uvicorn main:app refers to:

  • main: the file main.py (the Python “module”).
  • app: the object created inside of main.py with the line app = FastAPI().
  • --reload: make the server restart after code changes. Only use for development.

Check it

FastAPI的默认端口号为8000,因此访问根路径的URL为:http://127.0.0.1:8000

由于FastAPI遵守OpenAPI规范,会通过SwaggerUI自动生成可交互API文档界面http://127.0.0.1:8000/docs

另外,http://127.0.0.1:8000/redoc也提供了另一种自动生成的交互文档

FastAPI generates a “schema” with all your API using the OpenAPI standard for defining APIs.

A “schema” is a definition or description of something. Not the code that implements it, but just an abstract description. OpenAPI is a specification that dictates how to define a schema of your API. This schema definition includes your API paths, the possible parameters they take, etc.

The term “schema” might also refer to the shape of some data, like a JSON content. In that case, it would mean the JSON attributes, and data types they have, etc.

OpenAPI defines an API schema for your API. And that schema includes definitions (or “schemas”) of the data sent and received by your API using JSON Schema, the standard for JSON data schemas.

因此实际上OpenAPI定义的API模式是一个自动生成的包含API所有描述信息的JSON,可以在http://127.0.0.1:8000/openapi.json看到它的原始内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {



...

Recap

再看一遍代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
# --* saved as main.py *-- #

# 导入FastAPI
from fastapi import FastAPI

# variable app is an instance of class FastAPI
app = FastAPI()


# Path
@app.get("/")
async def root():
return {"message": "Hello World"}

首先,导入FastAPI

FastAPI is a class that inherits directly from Starlette.

You can use all the Starlette functionality with FastAPI too.

接着创建FastAPI实例

这里定义的FastAPI类的实例app也就是在命令行中使用uvicorn启动服务器时引用的:

1
2
3
uvicorn main:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

这里可以总结uvicorn命令启动服务器的参数:

uvicorn [file_name]:[instance_name] --reload

选项reload的含义是热重载,也就是不需要重新启动服务器就可以展示修改代码的效果

“Path”和“Operation”

The @app.get("/") tells FastAPI that the function right below is in charge of handling requests that go to:

  • the path /
  • using a get operation

“Path” here refers to the last part of the URL starting from the first /. While building an API, the “path” is the main way to separate “concerns” and “resources”.

“Operation” here refers to one of the HTTP “methods”.

Path在装饰器方法的参数中被定义,而装饰器的方法则对应HTTP的方法:

—Normally—

  • POST: to create data. <———->@app.post()
  • GET: to read data. <———->@app.get()
  • PUT: to update data. <———->@app.put()
  • DELETE: to delete data.

…and the more exotic ones:

  • OPTIONS <———->@app.options()
  • HEAD <———->@app.head()
  • PATCH <———->@app.patch()
  • TRACE <———->@app.trace()

That @something syntax in Python is called a “decorator”.

You put it on top of a function. Like a pretty decorative hat (I guess that’s where the term came from).

A “decorator” takes the function below and does something with it.

In our case, this decorator tells FastAPI that the function below corresponds to the path / with an operation get.

It is the “path operation decorator“.

定义路径操作函数

1
2
3
@app.get("/")
async def root():
return {"message": "Hello World"}

“path operation function”:

  • path: is /.
  • operation: is get.
  • function: is the function below the “decorator” (below @app.get("/")).

It will be called by FastAPI whenever it receives a request to the URL “/“ using a GET operation.

返回结果

You can return a dict, list, singular values as str, int, etc.

You can also return Pydantic models (you’ll see more about that later).

There are many other objects and models that will be automatically converted to JSON (including ORMs, etc). Try using your favorite ones, it’s highly probable that they are already supported.

  • Import FastAPI.
  • Create an app instance.
  • Write a path operation decorator (like @app.get("/")).
  • Write a path operation function (like def root(): ... above).
  • Run the development server (like uvicorn main:app --reload).

Path Parameters

FastAPI使用Python中的f-string声明 path “parameters” or “variables”

1
2
3
4
5
6
7
8
from fastapi import FastAPI

app = FastAPI()


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

还可以指定变量的格式:

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

This will give you editor support inside of your function, with error checks, completion, etc.

由此展示了FastAPI的数据相关特性:

  • Data conversion:保证接收传入的数据类型
  • Data validation:对于错误的数据类型会报错

Notice that the value your function received (and returned) is 3, as a Python int, not a string "3".

So, with that type declaration, FastAPI gives you automatic request “parsing”.

FastAPI的数据校验是通过Pydantic实现的。

Order Matters

有时,路径操作中的路径是写死的。

比如要使用 /users/me 获取当前用户的数据。

然后还要使用 /users/{user_id},通过用户 ID 获取指定用户的数据。

由于路径操作是按顺序依次运行的,因此,一定要在 /users/{user_id} 之前声明 /users/me

1
2
3
4
5
6
7
8
9
10
11
12
13
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,FastAPI 会认为正在接收值为 "me"user_id 参数。

有时,路径操作中的路径是写死的。

比如要使用 /users/me 获取当前用户的数据。

然后还要使用 /users/{user_id},通过用户 ID 获取指定用户的数据。

由于路径操作是按顺序依次运行的,因此,一定要在 /users/{user_id} 之前声明 /users/me

1
2
3
4
5
6
7
8
9
10
11
12
13
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,FastAPI 会认为正在接收值为 "me"user_id 参数。

有时,路径操作中的路径是写死的。

比如要使用 /users/me 获取当前用户的数据。

然后还要使用 /users/{user_id},通过用户 ID 获取指定用户的数据。

由于路径操作是按顺序依次运行的,因此,一定要在 /users/{user_id} 之前声明 /users/me

1
2
3
4
5
6
7
8
9
10
11
12
13
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,FastAPI 会认为正在接收值为 "me"user_id 参数。

Predefined Values

路径操作使用 Python 的 Enum 类型接收预设的路径参数

Import Enum and create a sub-class that inherits from str and from Enum.

By inheriting from str the API docs will be able to know that the values must be of type string and will be able to render correctly.

Then create class attributes with fixed values, which will be the available valid values

Then create a path parameter with a type annotation using the enum class you created (ModelName)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from enum import Enum

from fastapi import FastAPI


class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"


app = FastAPI()


@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}

if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}

return {"model_name": model_name, "message": "Have some residuals"}

img

对于包含路径的路径参数,OpenAI不支持这样的声明,不过可以使用Starlette内置工具实现:

1
2
3
4
5
6
7
8
from fastapi import FastAPI

app = FastAPI()


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

注意,包含 /home/johndoe/myfile.txt 的路径参数要以斜杠(/)开头。

本例中的 URL 是 /files//home/johndoe/myfile.txt。注意,fileshome 之间要使用双斜杠//)。

Query Parameters

声明的参数不是路径参数时,路径操作函数会把该参数自动解释为查询参数。

1
2
3
4
5
6
7
8
9
10
from fastapi import FastAPI

app = FastAPI()

fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]

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

1
http://127.0.0.1:8000/items/?skip=0&limit=10

注意,这里的skip和limit是具有缺省值的,也就是如果未给出具体参数值,将自动使用默认值

Optional Parameters

同理,把默认值设为 None 即可声明可选的查询参数:

1
2
3
4
5
6
7
8
9
10
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}

因为默认值为 = None,FastAPI 把 q 识别为可选参数。

FastAPI 不使用 Optional[str] 中的 Optional(只使用 str),但 Optional[str] 可以帮助编辑器发现代码中的错误。

Required Query Parameters

为不是路径参数的参数声明默认值(至此,仅有查询参数),该参数就不是必选的了。

如果只想把参数设为可选,但又不想指定参数的值,则要把默认值设为 None

如果要把查询参数设置为必选,就不要声明默认值:

Request Body

FastAPI 使用Request Body从客户端(例如浏览器)向 API 发送数据。

Request Body是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。

API 基本上肯定要发送响应体,但是客户端不一定发送Request Body

使用 Pydantic 模型声明Request Body,能充分利用它的功能和优点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi import FastAPI
from pydantic import BaseModel


# 创建数据模型Item继承BaseModel
class Item(BaseModel):
"""
name: str类型,必需
price: float类型,必需
description: string类型,可选
tax: float类型,可选
"""
name: str
description: str | None = None
price: float
tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
return item

上述模型声明如下 JSON 对象(即 Python 字典):

1
2
3
4
5
6
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}

FastAPI 支持同时声明Request Body路径参数查询参数,能识别与路径参数匹配的函数参数,还能识别从Request Body中获取的类型为 Pydantic 模型的函数参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None


app = FastAPI()


# 自动识别路径参数
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result

函数参数按如下规则进行识别:

  • 路径中声明了相同参数的参数,是路径参数
  • 类型是(intfloatstrbool 等)单类型的参数,是查询参数
  • 类型是 Pydantic 模型的参数,是Request Body

Query Validation

FastAPI使用Query为参数添加额外校验

1
2
3
4
5
6
7
8
9
10
11
12
13
from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

这里实现的效果是:q 是可选的,但只要提供了该参数,则该参数值不能超过50个字符的长度

1
q: Union[str, None] = Query(default=None)

…使得参数可选,等同于:

1
q: str = None

但是 Query 显式地将其声明为查询参数。

还可以添加正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
q: Union[str, None] = Query(
default=None, min_length=3, max_length=50, pattern="^fixedquery$"
),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

在使用 Query 且需要声明一个值是必需的时,不声明默认参数即可:

1
2
3
4
5
6
7
8
9
10
11
from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Query还可以显式地接收多个值:

1
2
3
4
5
6
7
8
9
10
11
from typing import List, Union

from fastapi import FastAPI, Query

app = FastAPI()


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

还可以定义在没有任何给定值时的默认 list 值:

1
2
3
4
5
6
7
8
9
10
11
from typing import List

from fastapi import FastAPI, Query

app = FastAPI()


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

Metadata

可以添加更多有关该参数的信息。

这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
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",
min_length=3,
),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Alias Parameters

可以用 alias 参数声明一个别名,该别名将用于在 URL 中查找查询参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Recap

这里实际上只是Query的各项参数

通用的校验和元数据:

  • alias
  • title
  • description
  • deprecated

特定于字符串的校验:

  • min_length
  • max_length
  • regex

Path Validation

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from typing import Annotated

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get")],
q: Annotated[str | None, Query(alias="item-query")] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results

Recap

QueryPath还提供了数值校验的参数:

gt:大于(greater than)

ge:大于等于(greater than or equal)

lt:小于(less than)

le:小于等于(less than or equal)

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

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

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

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

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

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

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

Body

Multiple Parameters

实际上,上文提到的Query Validation和Path Validation本质上是一样的,都是使用了各自的一个对象(实质上是函数),使用各项参数来完成验证的。对于Request Body,自然也有与QueryPath相同的Body可用。

参数声明时,可以混合使用PathQuery和Request Body参数,还可以使用多个Request Body作为参数。

对于一个单一值(Singular Value)如果直接在参数列表中声明,会被FastAPI视作一个查询参数。但是如果要想在原Request Body中添加另一个键,可以使用Body来声明,指示FastAPI将其作为Request Body的另一个键进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import Annotated

from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None


class User(BaseModel):
username: str
full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results

此时FastAPI将期望下面这样的Request Body:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"item": {
"name": "string",
"description": "string",
"price": 0,
"tax": 0
},
"user": {
"username": "string",
"full_name": "string"
},
"importance": 0
}

Request Body是客服端(浏览器)发送给服务器的数据信息,一般为JSON格式的数据。而查询参数则是在URL中传递简单数据来进行查询、筛选等操作。也就是说,Request Body是携带可以被服务器处理的复杂的数据信息的,而Query Parameter则只是用于查询、筛选、分页等。

如果想嵌入单个Request Body参数,只需要使用Body的参数embeditem: Item = Body(embed=True)。这时对于单个的Request Bodyitem,FastAPI将会期望得到一个具有键值”item”的Request Body而非原本那样不具有键的Request BodyJSON

Fields

与在路径操作函数中使用 QueryPathBody 声明校验与元数据的方式一样,可以使用 Pydantic 的 Field 在 Pydantic 模型内部声明校验和元数据。

实际上,QueryPath 都是 Params 的子类,而 Params 类又是 Pydantic 中 FieldInfo 的子类。

Pydantic 的 Field 返回也是 FieldInfo 的类实例。

Body 直接返回的也是 FieldInfo 的子类的对象。后文还会介绍一些 Body 的子类。

注意,从 fastapi 导入的 QueryPath 等对象实际上都是返回特殊类的函数。

Nested Model

With FastAPI, you can define, validate, document, and use arbitrarily deeply nested models (thanks to Pydantic).

但是这一部分在我测试API文档的时候发现添加的List和set等对象都不会展示在期望的Request Body中,自行添加值也会报错,等以后搞懂了再写吧

之前的问题是不管怎么修改代码或者重启服务器,API文档展示的都是之前某次代码的结果,然而重启了一下就好了,不知道具体的出错位置(但总归是好了)

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
# tags: list[str] = []
tags: set[str] = set()


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

可以看到对于成员tags,可以使用list[str]声明为子类型是字符串的列表,也可以使用set来声明元素值唯一的集合。在Python3.9以上的版本,标准的listset就可以实现。

怎么嵌套模型?实际上只需要像前面展示的一样声明一个(例如Image)模型,然后在另一个模型(Item)中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
url: str
name: str


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

这样,FastAPI期望获取的Request Body将会是:

1
2
3
4
5
6
7
8
9
10
11
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}

注意到Image中有一个url属性,自然是可以对输入的数据进行校验检查是否符合url格式的。使用正则表达式显然有些复杂,Pydantic提供了HttpUrl类型来代替str,从而保证传入数据必须符合url格式

数据模型的各种嵌套应用、作为list等集合的子类型的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
url: HttpUrl
name: str


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None # Image作为list的子类型


class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item] # 两层嵌套数据模型


@app.post("/offers/")
async def create_offer(offer: Offer):
return offer

If the top level value of the JSON body you expect is a JSON array (a Python list), you can declare the type in the parameter of the function, the same as in Pydantic models:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
url: HttpUrl
name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images

此时FastAPI期望的Request Body为:

1
2
3
4
5
6
[
{
"url": "https://example.com/",
"name": "string"
}
]

You can also declare a body as a dict with keys of some type and values of some other type.

This would be useful if you want to receive keys that you don’t already know.

Another useful case is when you want to have keys of another type (e.g., int).

accept any dict as long as it has int keys with float values

1
2
3
4
5
6
7
8
9
10
11
from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
"""
accept any dict as long as it has int keys with float values
"""
return weights

JSON 仅支持将 str 作为键。

但是 Pydantic 具有自动转换数据的功能。

这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。

然后你接收的名为 weightsdict 实际上将具有 int 类型的键和 float 类型的值。

Recap

With FastAPI you have the maximum flexibility provided by Pydantic models, while keeping your code simple, short and elegant.

总结关于Request Body相关的内容,在参数方面,FastAPI可以自动识别Query Parameter、Path Parameter与Request Body,还可以从fastapi中导入PathQueryBody来对三者进行一定的校验设置。而使用pydantic中导入的Field可以在 Pydantic 模型内部声明校验和元数据,本质上与PathQueryBody一样。最后,使用class继承pydanticBaseModel声明的各种数据模型都可以直接地在参数或模型内部使用,甚至可以作为listset等集合的子类型。

Declare Request Example Data

就像默认值一样,你也可以提前设置好期望的Request Body的“example”,that extra info will be added as-is to the output JSON Schema for that model, and it will be used in the API docs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None

model_config = {
"json_schema_extra": {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
}


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

可以注意到在数据模型中定义了model_config,然后使用了一个dict来定义json_schema_extra信息,其内部则是使用examples关键字设置信息。上面提到的model_configjson_chema_extraexamples都是Pydantic声明的关键字,最好按照上面的格式来写。

在数据模型内部是使用Field来进行声明校验和元数据的。Filed内部也可以使用examples关键字(此时是作为参数,而不是json格式中的字符串)。

examples in JSON Schema - OpenAPI

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
  • Form()
  • File()

以上类型都可以使用examples关键字为JSON Schema添加附加信息,以Body为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from typing import Annotated

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None


@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
],
):
results = {"item_id": item_id, "item": item}
return results

Extra Data Type

下面是讲过N遍的特点:

  • Great editor support.
  • Data conversion from incoming requests.
  • Data conversion for response data.
  • Data validation.
  • Automatic annotation and documentation.

这里是一些可以使用的其他数据类型

  • UUID:
    • 一种标准的 “通用唯一标识符” ,在许多数据库和系统中用作ID。
    • 在请求和响应中将以 str 表示。
  • datetime.datetime:
    • A Python datetime.datetime.
    • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15T15:53:00+05:00.
  • datetime.date:
    • Python datetime.date.
    • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15.
  • datetime.time:
    • A Python datetime.time.
    • 在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 14:23:55.003.
  • datetime.timedelta:
    • A Python datetime.timedelta.
    • 在请求和响应中将表示为 float 代表总秒数。
    • Pydantic 也允许将其表示为 “ISO 8601 时间差异编码”, 查看文档了解更多信息
  • frozenset:
    • 在请求和响应中,作为 set 对待:
      • 在请求中,列表将被读取,消除重复,并将其转换为一个 set
      • 在响应中 set 将被转换为 list
      • 产生的模式将指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。
  • bytes:
    • Standard Python bytes.
    • 在请求和响应中被当作 str 处理。
    • 生成的模式将指定这个 strbinary “格式”。
  • Decimal:
    • Standard Python Decimal.
    • 在请求和响应中被当做 float 一样处理。
  • 您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from datetime import datetime, time, timedelta
from typing import Annotated
from uuid import UUID

from fastapi import Body, FastAPI

app = FastAPI()


@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Annotated[datetime, Body()],
end_datetime: Annotated[datetime, Body()],
process_after: Annotated[timedelta, Body()],
repeat_at: Annotated[time | None, Body()] = None,
):
# 额外的数据类型也可以进行相应的数据运算
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"process_after": process_after,
"repeat_at": repeat_at,
"start_process": start_process,
"duration": duration,
}

类似PathQuery

1
2
3
4
5
6
7
8
9
10
from typing import Annotated

from fastapi import Cookie, FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}

使用和 PathQueryCookie 一样的结构定义 header 参数。

1
2
3
4
5
6
7
8
9
10
from typing import Annotated

from fastapi import FastAPI, Header

app = FastAPI()


@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}

大部分标准请求头用连字符分隔,即减号-),但是 user-agent 这样的变量在 Python 中是无效的。因此,默认情况下,Header 把参数名中的字符由下划线(_)改为连字符(-)来提取并存档请求头 。

同时,HTTP 的请求头不区分大小写,可以使用 Python 标准样式(即 snake_case)进行声明。因此,可以像在 Python 代码中一样使用 user_agent ,无需把首字母大写为 User_Agent 等形式。

Response Model

With declared return type, FastAPI can validate the returned data and add a JSON Schema for the response, in the OpenAPI path operation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []


@app.post("/items/")
"""
这里的箭头声明了返回类型是Item
"""
async def create_item(item: Item) -> Item:
return item


@app.get("/items/")
"""
这里的箭头声明了返回类型是Item列表,即子类型是Item的list
"""
async def read_items() -> list[Item]:
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]

Most importantly: It will limit and filter the output data to what is defined in the return type, which is particularly important for security.

response_model Parameter

What shown above is return type. But, in case of editor comlaining the error that return type (e.g. dictionary, darabase object) is different from what you have declared (Pydantic Model), You can use the path operation decorator parameter response_model instead of the return type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []


@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item


@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]

response_model receives the same type you would declare for a Pydantic model field, so, it can be a Pydantic model, but it can also be, e.g. a list of Pydantic models, like List[Item].

In FastAPI, response_model takes priority over return type. This way you can have FastAPI do the data validation, documentation, etc. simultaneously add correct type annotations to your functions even when you are returning a type different than the response model, to be used by the editor and tools like mypy.

Return the same input data

Declare a model UserIn to input data and use the same model declare the output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None


# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user

Now, whenever a browser is creating a user with a password, the API will return the same password in the response.

Never store the plain password of a user or send it in a response like this. Password must be encrypted.

Add an output model

We can instead create an input model with the plaintext password and an output model without it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None


class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user

we declared the response_model to be our model UserOut, that doesn’t include the password.

This way, FastAPI will take care of filtering out all the data that is not declared in the output model (using Pydantic).

you can declare the function return type as Any. That way you tell the editor that you are intentionally returning anything. But FastAPI will still do the data documentation, validation, filtering, etc. with the response_model.