FastAPI文档学习
User Guide
First Steps
最简单的FastAPI文件结构:
1 | # --* saved as main.py *-- # |
执行指令
1 | uvicorn main:app --reload |
以启动服务器。
The command uvicorn main:app
refers to:
main
: the filemain.py
(the Python “module”).app
: the object created inside ofmain.py
with the lineapp = 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 | { |
Recap
再看一遍代码结构:
1 | # --* saved as main.py *-- # |
首先,导入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 | uvicorn main:app --reload |
这里可以总结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 |
|
“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 | from fastapi import FastAPI |
还可以指定变量的格式:
1 |
|
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 | from fastapi import FastAPI |
否则,/users/{user_id}
将匹配 /users/me
,FastAPI 会认为正在接收值为 "me"
的 user_id
参数。
有时,路径操作中的路径是写死的。
比如要使用 /users/me
获取当前用户的数据。
然后还要使用 /users/{user_id}
,通过用户 ID 获取指定用户的数据。
由于路径操作是按顺序依次运行的,因此,一定要在 /users/{user_id}
之前声明 /users/me
:
1 | from fastapi import FastAPI |
否则,/users/{user_id}
将匹配 /users/me
,FastAPI 会认为正在接收值为 "me"
的 user_id
参数。
有时,路径操作中的路径是写死的。
比如要使用 /users/me
获取当前用户的数据。
然后还要使用 /users/{user_id}
,通过用户 ID 获取指定用户的数据。
由于路径操作是按顺序依次运行的,因此,一定要在 /users/{user_id}
之前声明 /users/me
:
1 | from fastapi import FastAPI |
否则,/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 | from enum import Enum |
对于包含路径的路径参数,OpenAI不支持这样的声明,不过可以使用Starlette内置工具实现:
1 | from fastapi import FastAPI |
注意,包含 /home/johndoe/myfile.txt
的路径参数要以斜杠(/
)开头。
本例中的 URL 是 /files//home/johndoe/myfile.txt
。注意,files
和 home
之间要使用双斜杠(//
)。
Query Parameters
声明的参数不是路径参数时,路径操作函数会把该参数自动解释为查询参数。
1 | from fastapi import FastAPI |
查询字符串是键值对的集合,这些键值对位于 URL 的 ?
之后,以 &
分隔。
1 | http://127.0.0.1:8000/items/?skip=0&limit=10 |
注意,这里的skip和limit是具有缺省值的,也就是如果未给出具体参数值,将自动使用默认值
Optional Parameters
同理,把默认值设为 None
即可声明可选的查询参数:
1 | from fastapi import FastAPI |
因为默认值为 = 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 | from fastapi import FastAPI |
上述模型声明如下 JSON 对象(即 Python 字典):
1 | { |
FastAPI 支持同时声明Request Body、路径参数和查询参数,能识别与路径参数匹配的函数参数,还能识别从Request Body中获取的类型为 Pydantic 模型的函数参数。
1 | from fastapi import FastAPI |
函数参数按如下规则进行识别:
- 路径中声明了相同参数的参数,是路径参数
- 类型是(
int
、float
、str
、bool
等)单类型的参数,是查询参数 - 类型是 Pydantic 模型的参数,是Request Body
Query Validation
FastAPI使用Query为参数添加额外校验
1 | from typing import Union |
这里实现的效果是:q
是可选的,但只要提供了该参数,则该参数值不能超过50个字符的长度
1 | q: Union[str, None] = Query(default=None) |
…使得参数可选,等同于:
1 | q: str = None |
但是 Query
显式地将其声明为查询参数。
还可以添加正则表达式
1 | from typing import Union |
在使用 Query
且需要声明一个值是必需的时,不声明默认参数即可:
1 | from fastapi import FastAPI, Query |
Query
还可以显式地接收多个值:
1 | from typing import List, Union |
还可以定义在没有任何给定值时的默认 list
值:
1 | from typing import List |
Metadata
可以添加更多有关该参数的信息。
这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。
1 | from typing import Union |
Alias Parameters
可以用 alias 参数声明一个别名,该别名将用于在 URL 中查找查询参数值
1 | from typing import Union |
Recap
这里实际上只是Query
的各项参数
通用的校验和元数据:
alias
title
description
deprecated
特定于字符串的校验:
min_length
max_length
regex
Path Validation
与使用 Query 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 Path 为路径参数声明相同类型的校验和元数据。
1 | from typing import Annotated |
Recap
Query
和Path
还提供了数值校验的参数:
gt:大于(greater than)
ge:大于等于(greater than or equal)
lt:小于(less than)
le:小于等于(less than or equal)
Query
、Path
以及你后面会看到的其他类继承自一个共同的 Param
类(不需要直接使用它)。
而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。
当你从 fastapi
导入 Query
、Path
和其他同类对象时,它们实际上是函数。
当被调用时,它们返回同名类的实例。
如此,你导入 Query
这个函数。当你调用它时,它将返回一个同样命名为 Query
的类的实例。
因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。
这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。
Body
Multiple Parameters
实际上,上文提到的Query Validation和Path Validation本质上是一样的,都是使用了各自的一个对象(实质上是函数),使用各项参数来完成验证的。对于Request Body,自然也有与Query
和Path
相同的Body
可用。
参数声明时,可以混合使用Path
、Query
和Request Body参数,还可以使用多个Request Body作为参数。
对于一个单一值(Singular Value)如果直接在参数列表中声明,会被FastAPI视作一个查询参数。但是如果要想在原Request Body中添加另一个键,可以使用Body
来声明,指示FastAPI将其作为Request Body的另一个键进行处理。
1 | from typing import Annotated |
此时FastAPI将期望下面这样的Request Body:
1 | { |
Request Body是客服端(浏览器)发送给服务器的数据信息,一般为JSON格式的数据。而查询参数则是在URL中传递简单数据来进行查询、筛选等操作。也就是说,Request Body是携带可以被服务器处理的复杂的数据信息的,而Query Parameter则只是用于查询、筛选、分页等。
如果想嵌入单个Request Body参数,只需要使用Body
的参数embed
:item: Item = Body(embed=True)
。这时对于单个的Request Bodyitem,FastAPI将会期望得到一个具有键值”item”的Request Body而非原本那样不具有键的Request BodyJSON
Fields
与在路径操作函数中使用 Query
、Path
、Body
声明校验与元数据的方式一样,可以使用 Pydantic 的 Field
在 Pydantic 模型内部声明校验和元数据。
实际上,Query
、Path
都是 Params
的子类,而 Params
类又是 Pydantic 中 FieldInfo
的子类。
Pydantic 的 Field
返回也是 FieldInfo
的类实例。
Body
直接返回的也是 FieldInfo
的子类的对象。后文还会介绍一些 Body
的子类。
注意,从 fastapi
导入的 Query
、Path
等对象实际上都是返回特殊类的函数。
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 | from fastapi import FastAPI |
可以看到对于成员tags
,可以使用list[str]
声明为子类型是字符串的列表,也可以使用set
来声明元素值唯一的集合。在Python3.9以上的版本,标准的list
或set
就可以实现。
怎么嵌套模型?实际上只需要像前面展示的一样声明一个(例如Image
)模型,然后在另一个模型(Item
)中使用:
1 | from fastapi import FastAPI |
这样,FastAPI期望获取的Request Body将会是:
1 | { |
注意到Image
中有一个url
属性,自然是可以对输入的数据进行校验检查是否符合url格式的。使用正则表达式显然有些复杂,Pydantic提供了HttpUrl
类型来代替str,从而保证传入数据必须符合url格式
数据模型的各种嵌套应用、作为list
等集合的子类型的示例如下:
1 | from fastapi import FastAPI |
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 | from fastapi import FastAPI |
此时FastAPI期望的Request Body为:
1 | [ |
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 | from fastapi import FastAPI |
JSON 仅支持将 str
作为键。
但是 Pydantic 具有自动转换数据的功能。
这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。
然后你接收的名为 weights
的 dict
实际上将具有 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
中导入Path
、Query
、Body
来对三者进行一定的校验设置。而使用pydantic
中导入的Field
可以在 Pydantic 模型内部声明校验和元数据,本质上与Path
、Query
、Body
一样。最后,使用class
继承pydantic
的BaseModel
声明的各种数据模型都可以直接地在参数或模型内部使用,甚至可以作为list
、set
等集合的子类型。
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 | from fastapi import FastAPI |
可以注意到在数据模型中定义了model_config
,然后使用了一个dict
来定义json_schema_extra
信息,其内部则是使用examples
关键字设置信息。上面提到的model_config
、json_chema_extra
、examples
都是Pydantic声明的关键字,最好按照上面的格式来写。
在数据模型内部是使用Field
来进行声明校验和元数据的。Filed
内部也可以使用examples
关键字(此时是作为参数,而不是json格式中的字符串)。
examples
in JSON Schema - OpenAPI
Path()
Query()
Header()
Cookie()
Body()
Form()
File()
以上类型都可以使用examples
关键字为JSON Schema添加附加信息,以Body
为例:
1 | from typing import Annotated |
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
.
- A Python
datetime.date
:- Python
datetime.date
. - 在请求和响应中将表示为 ISO 8601 格式的
str
,比如:2008-09-15
.
- Python
datetime.time
:- A Python
datetime.time
. - 在请求和响应中将表示为 ISO 8601 格式的
str
,比如:14:23:55.003
.
- A Python
datetime.timedelta
:- A Python
datetime.timedelta
. - 在请求和响应中将表示为
float
代表总秒数。 - Pydantic 也允许将其表示为 “ISO 8601 时间差异编码”, 查看文档了解更多信息。
- A Python
frozenset
:- 在请求和响应中,作为
set
对待:- 在请求中,列表将被读取,消除重复,并将其转换为一个
set
。 - 在响应中
set
将被转换为list
。 - 产生的模式将指定那些
set
的值是唯一的 (使用 JSON 模式的uniqueItems
)。
- 在请求中,列表将被读取,消除重复,并将其转换为一个
- 在请求和响应中,作为
bytes
:- Standard Python
bytes
. - 在请求和响应中被当作
str
处理。 - 生成的模式将指定这个
str
是binary
“格式”。
- Standard Python
Decimal
:- Standard Python
Decimal
. - 在请求和响应中被当做
float
一样处理。
- Standard Python
- 您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.
使用示例:
1 | from datetime import datetime, time, timedelta |
Cookie
类似Path
、Query
:
1 | from typing import Annotated |
Header
使用和 Path
、Query
、Cookie
一样的结构定义 header 参数。
1 | from typing import Annotated |
大部分标准请求头用连字符分隔,即减号(-
),但是 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 | from fastapi import FastAPI |
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 | from typing import Any |
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 | from typing import Union |
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 | from typing import Any |
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
.