官方文档 🔗https://fastapi.tiangolo.com/
官方仓库 🔗https://github.com/tiangolo/fastapi 首次提交于 2018-12
一个用于 Python 3.7+ 构建 API 的现代、高性能 web 框架, 基于标准的 Python 类型提示
🎯主要特点:
- 性能堪比 NodeJS 与 Go
- 可快速开发功能,200% ~300%
- 减少 40% 人为造成的 bug
- 广泛编辑器支持,方便 debug
- 被设计成简单易用,花更少时间阅读文档
- 最少化代码复制
- 代码可随时部署正式环境
- 基于且完全兼容 OpenAPI 与 JSON Schema
- 自带 2 款文档
- 方便数据验证
- 自带安全性及身份认证:OAuth2、API Key
- 依赖注入
- 框架测试覆盖率 100%
1 | 需求
Python 3.7+
2 | 安装
$ pip install fastapi
💡正式环境需要 ASGI 服务器软件,可安装 Uvicorn 或 Hypercorn
$ pip install "uvicorn[standard]"
3 | 可选依赖项
Pydantic 使用:
- ujson:快速 json 格式解析
- email_validator:邮件验证
Starlette 使用:
- requests - 使用
TestClient
必安 - jinja2 - 使用默认模板配置必安
- python-multipart - 使用
request.form()
进行 form 解析必安 - itsdangerous - 使用中间件
SessionMiddleware
必安 - pyyaml - 支持 Starlette 的
SchemaGenerator
- ujson - 使用
UJSONResponse
必安
FastAPI / Starlette 使用:
💡以上所有可以通过 pip install "fastapi[all]"
安装
4 | 开发 CLI
🔗 https://fastapi.tiangolo.com/#typer-the-fastapi-of-clis
开发在终端中使用的 CLI 应用而不是网页 API,可以使用 Typer
5 | 类型提示
🆕 Python 3.6 版本新增
# 普通类型
def get_name_with_age(name: str, age: int):
# 嵌套类型
def process_items(items: List[str]):
4 大作用:
- 提示程序员参数类型
- 帮助 FastAPI 接受请求时校验参数
- FastAPI 验证通过后转换数据
- 让编辑器辅助检查
6 | 用户指南
6-1 | 初始化 APP
🔗https://fastapi.tiangolo.com/tutorial/first-steps/
🧲简单示例:
# main.py
# 1.导入
from fastapi import FastAPI
# 2.实例化
app = FastAPI()
# 3.定义
@app.get("/")
async def root():
return {"message": "Hello World"}
📋FastAPI
类直接继承 Starlette
💻运行开发服务器
-
命令行
通过
$ uvicorn main:app --reload
执行main
: 指代文件 main.pyapp
:main.py
中创建的对象,即 `app = FastAPI()``- ``–reload`: 文件修改时自动重启服务,仅开发时使用⚠️
-
代码
在主业务逻辑
main.py
定义运行服务器代码,避免手动命令行输入指令import uvicorn if __name__ == '__main__': uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
💻其他 HTTP 请求方式
Method |
---|
@app.post() @app.put() @app.delete() |
@app.options() @app.head() @app.patch() @app.trace() |
6-2 | 自带文档
- Swagger 文档:访问
http://127.0.0.1:8000/docs
,样式采用 Swagger UI - RE 文档:访问
http://127.0.0.1:8000/redoc
,样式采用 ReDoc
以上两种文档均可自定义URL,也可以关闭
# https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls
app = FastAPI(docs_url="/documentation", redoc_url=None)
app = FastAPI(redoc_url="/documentation", docs_url=None)
6-3 | OpenAPI
FastAPI 根据 OpenAPI 标准给所有 API 以标准的 JSON 格式生成返回值与参数以及接口描述
💻查看 openapi.json
默认地址为 http://127.0.0.1:8000/openapi.json
, 也可自定义:
app = FastAPI(openapi_url="/api/v1/openapi.json")
6-4 | 路径参数
🔗https://fastapi.tiangolo.com/tutorial/path-params/
-
定义参数
@app.get("/items/{item_id}") async def read_item(item_id):
-
定义参数类型
@app.get("/items/{item_id}") async def read_item(item_id: int):
6-4-1 | 接口顺序
-
路径重叠
⚠️路由匹配采取自上而下的顺序,靠上的先匹配到
@app.get("/users/me") @app.get("/users/{user_id}")
🆚Flask 不会出现这种情况,即使
/users/me
放在下面也可以被访问到 -
路径重复
⚠️相同路径可重复注册,但只有最靠前的会被匹配@app.get("/users/me") def user_me_1(): @app.get("/users/me") def user_me_2():
6-4-2 | 定义参数范围 Enum
编写 API 时需定义参数值的可选值,可采用标准类型 Enum
# 1.导入
from enum import Enum
# 2.定义可选值
class ModelName(str, Enum):
al = "alexnet"
re = "resnet"
le = "lenet"
# 3.作为类型给参数
@app.get("/models/{name}")
async def get_model(name: ModelName):
# 调用值
name.value
ModelName.al.value
# 可直接比较
name is ModelName.al
📋 return
中的 model_name
会转换成相应的值再响应客户端
🆕 Enum
类型需要 Python 3.4+
6-4-3 | 路径中的路径 :path
路径也可以作为 url 参数并被自动转换类型
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
入参 file_path
也可以 /
开头,如
http://127.0.0.1:8000/files//aa.txt
{"file_path":"/aa.txt"}
6-5 | 地址栏参数 Query Param
🔗https://fastapi.tiangolo.com/tutorial/query-params/
访问时可获取地址栏参数 /items/?skip=0&limit=10
:
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
如果需要参数非必填可写为:
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None):
async def read_item(item_id: str, q: Union[str, None] = None):
⚠️Python 3.10 以下版本定义可选参数用 q: Union[str, None] = None
自动转换
布尔类型也可作为地址栏参数值传递,以下均视为 True
/items/foo?short=1
/items/foo?short=True
/items/foo?short=true
/items/foo?short=on
/items/foo?short=yes
多个参数可同时定义,不用关注定义顺序
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
user_id: int, item_id: str, q: str | None = None, short: bool = False
):
**如何设定必填参数?**不传默认值即可
6-6 | 请求体
🔗https://fastapi.tiangolo.com/tutorial/body/
6-6-1 | BaseModel
# 1.引入BaseModel
from pydantic import BaseModel
# 2.定义BaseModel
class Item(BaseModel):
name: str
desc: str | None = None # 可选参数
# 3.作为定义参数
@app.post("/items/")
async def create_item(item: Item):
item.name # 各属性可直接使用
item_dict = item.dict() # 获取所有
请求数据:
{
"name": "Foo",
"desc": "desc 是选填参数,当前键值对可不传!",
}
6-6-2 | Body()
从请求体中获取入参
from fastapi import Body, FastAPI
async def update_item(item: Item, importance: int = Body()):
请求数据
{
"item": {
"name": "Foo",
"desc": "The",
},
"importance": 5
}
6-6-3 | Body(embed=True)
Body(embed=True)
可要求入参嵌套一层而 key 就是参数名称
// Body(embed=True)
{
"item": {
"name": "Foo",
"desc": "The pretender",
}
}
// 普通
{
"name": "Foo",
"desc": "The pretender",
}
6-6-4 | Field()
Field
的工作方式和 Query
、Path
和 Body
相同,包括参数也完全相同,类似 Django
# 1.导入
from pydantic import Field
# 2.定义
class Item(BaseModel):
description: str | None = Field(default=None, title="The title")
price: float = Field(gt=0, description="The price")
6-6-5 | 嵌套模型
https://fastapi.tiangolo.com/tutorial/body-nested-models/
# 1.定义基础模型
class Image(BaseModel):
url: str
# 2.赋给其他类属性
class Item(BaseModel):
image: Union[Image, None] = None # 单个
images: Union[List[Image], None] = None # 多个
请求数据:
{
"image": {"url": "http://example.com/baz.jpg"},
"images": [{"url": "http://example.com/baz.jpg",},
{"url": "http://example.com/dave.jpg",}]
}
📋url 字段可以使用 pydantic 自带的 HttpUrl
,具有验证功能:
from pydantic import BaseModel, HttpUrl
class Image(BaseModel):
url: HttpUrl
name: str
嵌套类属性
from typing import List, Set
class Item(BaseModel):
tags1: List[int] = [] # 具有子类型的 List 类型
tags2: Set[int] = set() # 具有子类型的 Set 类型
weights: Dictp[int, float] = dict()
没有子类型直接用 Python 标准的 list
、set
即可
6-7 | 入参校验 Query
🔗https://fastapi.tiangolo.com/tutorial/query-params-str-validations/
6-7-1 | 定义规则
定义接口时,可以设定校验,让入参符合指定条件,相当于 form
🧲示例:必填入参q
且其长度最大 50,最小长度为 2
# 1.导入
from fastapi import FastAPI, Query
# 2.定义最大最小长度,正则
async def read_items(q: str | None = Query(
min_length=2, max_length=50, regex="")
):
6-7-2 | 校验数字大小
接口的整数,浮点数类型入参可验证大小,Path,Query 均支持:
# Path,Query 定义的入参
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
6-7-3 | 定义选填 & 必填
-
定义选填参数:
Query(default=None)
-
定义必填参数:
- 不写
default=None
- 写为
default=...
- 写为
Query(default=Required)
,需引入from pydantic import Required
- 不写
6-7-4 | 接受参数列表
async def read_items(q: List[str] | None = Query(default=["foo", "bar"])):
async def read_items(q: list = Query(default=[])): # list 等同 List[str]
对应的数据:
{"q": ["foo", "bar"]}
6-8 | 入参描述 & 别名 & 弃用
https://fastapi.tiangolo.com/zh/tutorial/query-params-str-validations/#_10
使用参数 title
或 description
,这些描述会被用在接口文档中
async def read_items(
q: Union[str, None] = Query(
title="Query string",
description="Query string"
)
):
6-8-1 | 入参别名
问题:地址栏中的参数名称与业务逻辑层不一致
@app.get("/items/")
async def read_items(q: Union[str, None] = Query(
alias="item-query"
):
/items/?item-query=test
这样就可以被识别了
6-8-2 | 入参弃用
需弃用参数,但仍有客户端在使用,可通过以下方式在文档中标记
async def read_items(q: Union[str, None] = Query(deprecated=True)):
在 FastAPI 自动生成文档中会将对应参数标记上红色的 Deprecated,实际该参数仍可用!
6-9 | 入参校验 Path
与 Query 除了作用对象是路径参数外,别的都一样,支持的入参都相同
from fastapi import FastAPI, Path, Query
async def read_items(
q: str,
item_id: int = Path(title="The ID of the item to get")
):
💡 如果参数 q
没有默认值,且想放在关键字参数 item_id
后,可用 *
async def read_items(
*,
item_id: int = Path(title="The ID of the item to get", ge=1),
q: str
):
6-10 | 在文档中隐藏参数
定义的参数不需要显示在文档时,使用 include_in_schema=False
async def read_items(q: str | None = Query(default=None, include_in_schema=False)):
6-11 | 演示数据
https://fastapi.tiangolo.com/tutorial/schema-extra-example/
方法一:定义 Config
与 schema_extra
即可,会体现在文档中
方法二:直接在字段中给出,单个演示数据用 example
,多个可以使用 examples
class Item(BaseModel):
name: str
tax: Union[float, None]
# 方法一
class Config:
schema_extra = {"example": {"name": "Foo","tax": 3.2}}
# 方法二
async def update_item(
tax: Union[float, None] = Field(example=3.2)
item: Annotated[
Item, Body(
examples={
"normal": {
"summary": "A normal example",
"description": "A normal item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": { }, # same keys
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
方法二亦可在Path
, Query
, Body
中定义
6-12 | 其他数据类型
https://fastapi.tiangolo.com/tutorial/extra-data-types/
除了常用的 数字、字符串、布尔
还可用 UUID,datetime,frozenset,bytes, Decimal 等
from datetime import datetime, time, timedelta
from typing import Annotated
from uuid import UUID
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start: Annotated[datetime | None, Body()] = None,
end: Annotated[datetime | None, Body()] = None,
):
duration = end - start # 可使用原对象支持的操作
6-13 | Cookie
from fastapi import Cookie, FastAPI
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
用法与 Query()
Path()
相同,不用 Cookie()
会被识别为地址栏参数
6-14 | Header
from fastapi import FastAPI, Header
async def read_items(user_agent: Annotated[str | None, Header()] = None):
由于 Python 不允许变量名中使用 -
,因此 FastAPI 会自动转换请求头中的 -
为 _
不需要自动转化时,设置convert_underscores=False
重复的头部
可以接受名称相同的 Header,将类型设为 List 即可
async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
如:
X-Token: foo
X-Token: bar
6-15 | 响应模型 response_model
需求:入参是 BaseModel 类型,如果直接返回,文档中会对其进行说明,但实际需要返回一个字典,如果直接返回字典,IDE 会提示返回值与定义时的不一致
方法:
class Item(BaseModel):
name: 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},
]
提示:建议返回 Any
, 避免 IDE 类型检查时报错
6-15-1 | response_model
优先级
同时定义了返回类型与 response_model
时,FastAPI 优先使用后者
6-15-2 | 返回相同类型
class UserIn(BaseModel):
username: str
password: str
# 生产环境不要这样写!!!!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
return user
⚠️多个用户同时操作时,一个用户提交的数据可能会展示给另一个用户
正确写法:定义两个模型,一个处理接口入参,一个处理接口响应值
class BaseUser(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(BaseUser):
password: str
@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser: # 这样密码就不会被返回了
return user
6-15-3 | 停用响应类型
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse
@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
# 不写 response_model=None 会报错
# 因为 Response | dict 不是合法的 Pydantic 类型
if teleport:
return RedirectResponse(url="https://..")
return {"message": "Here"}
6-15-4 | 排除空字段
问题:响应模型的属性有默认值,但实际没有存储对应值,需要从返回值中省略它们
可设定固定数据,并通过 response_model_exclude_unset=True
不传空字段
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"saz": {"name": "Saz", "description": None, "price": 50.2, "tax": 10.5},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
响应结果:
{"name": "Foo", "price": 50.2} // 请求入参 item_id 为 foo
FastAPI 优先采用 items
中手动定义的值,而不是默认值
6-15-5 | 排除包含指定字段
response_model_include
和 response_model_exclude
可作为响应中排除、包含少量字段的快捷方式
# 包含
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"}, # 使用 list 或 tuple 亦可
)
async def read_item_name(item_id: str):
return items[item_id]
# 排除
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
6-16 | 状态码
https://fastapi.tiangolo.com/tutorial/response-status-code/
定义响应的状态码,可用自带的常量 status
, 也可直接写整数
from fastapi import FastAPI, status
# from starlette import status 也可以
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
@app.post("/items/", status_code=201)
6-17 | Form
https://fastapi.tiangolo.com/tutorial/request-forms/
使用前需先安装 pip install python-multipart
from fastapi import FastAPI, Form
@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}
⚠️注意:发送请求时,请求头需包含 Content-Type: multipart/form-data;
否则 422,两个字段以 form-field 形式提交,不是请求体中的 json 数据
提示:Form
直接继承自 Body
6-18 | 上传文件
https://fastapi.tiangolo.com/tutorial/request-files/
from fastapi import FastAPI, File, UploadFile
# 方法一:上传的文件会存在内存中,适合小型文件
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
# 方法二:UploadFile
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
# 上传多个文件
async def create_upload_files(files: list[UploadFile]):
# 用 File() 添加额外设定
async def create_upload_files(
files: Annotated[
list[UploadFile], File(description="Multiple files as UploadFile")
],
):
提示:File
直接继承自 Form
6-18-1 | UploadFile
🆚与 bytes 类型相比的优点:
- 上传的文件超过最大限制则存在磁盘中
- 还可以获取上传文件的元信息
- 文件对象有自己的接口可调用
- 生成 SpooledTemporaryFile 对象可传给其他只处理该类型的其他代码
特性 | 异步方法 |
---|---|
filename :原始文件名 | write(data) |
content_type :MIME 类型 | read(size) |
file :SpooledTemporaryFile “类文件”对象 | seek(offset) |
close() |
💡调用时需采用异步方式:contents = await myfile.read()
💡也可直接调用:contents = myfile.file.read()
6-19 | 返回异常
6-19-1 | HTTPException
https://fastapi.tiangolo.com/tutorial/handling-errors/
from fastapi import FastAPI, HTTPException
items = {"foo": "The Foo Wrestlers"}
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
# 用 raise 不是 return !
raise HTTPException(
status_code=404,
detail="Item not found", # 请求体
headers={"X-Error": "There goes my error"}, # 自定义响应头
)
return {"item": items[item_id]}
detail
会与其值以键值对形式出现在响应体,也可以用 dict 或 list
{
"detail": "Item not found"
}
FastAPI 自己的 HTTPException
继承自 Starlette 的,可以添加响应头
6-19-2 | 自定义异常
# 1. 自定义一个异常类
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
# 2. 自定义错误处理方法,并通过装饰器绑定处理的异常
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something"},
)
# 3. 在业务逻辑需要的地方抛出异常
raise UnicornException(name=name)
6-19-3 | 覆写原生错误响应
用装饰器标记即可
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
💡可用此方式在响应中返回请求体中的数据,辅助排查问题
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), # body
)
6-20 | 接口说明
https://fastapi.tiangolo.com/tutorial/path-operation-configuration/
6-20-1 | tags
可以在自动生成文档中标记分类
# 方法一
@app.get("/items/", tags=["items"])
# 方法二:避免 hard code
class Tags(Enum):
items = "items"
users = "users"
@app.get("/items/", tags=[Tags.items])
💡tag 亦可定义元信息:
https://fastapi.tiangolo.com/tutorial/metadata/#create-metadata-for-tags
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://fastapi.tiangolo.com/",
},
},
]
app = FastAPI(openapi_tags=tags_metadata)
6-20-2 | summary 与 description
@app.post(
"/items/",
summary="Create an item",
description="Create an item with all the information",
)
6-20-3 | 从 docstring 中获取
如果接口描述过长,且需要换行,可直接写在 docstring 中,FastAPI 支持识别 Markdown
async def create_item(item: Item):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
"""
return item
文档字符串中的描述会被输出到 /docs
中
6-20-4 | response_description
自动生成文档中,接口响应值的描述
@app.post("/items/", response_description="The created item")
FastAPI 默认值为 Successful response"
6-20-5 | 弃用接口
可在文档中为接口标记 deprecated,但接口依旧可用,接口入参亦可添加此参数
@app.get("/elements/", tags=["items"], deprecated=True)
6-21 | jsonable_encoder
https://fastapi.tiangolo.com/tutorial/encoder/
需要将 Pydantic 数据对象转为 JSON 格式时,可使用自带的,底层 FastAPI 会使用该方法转换数据:
from fastapi.encoders import jsonable_encoder
>>> item
Item(name='aa', description='test', price=1.0, tax=1.0, tags=['asdasd'])
>>> data = jsonable_encoder(item)
>>> data
{'name': 'aa', 'description': 'test', 'price': 1.0, 'tax': 1.0, 'tags': ['asdasd']}
# 其他对象也可转换
>>> jsonable_encoder(datetime.now())
'2022-10-13T21:31:21.016772'
转换后,可直接用 Python 标准库 json.dumps
输出
💡jsonable_encoder()
可用于数据更新
items = {
"foo": {"name": "Foo", "price": 50.2},
}
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
update_item_encoded = jsonable_encoder(item)
items[item_id] = update_item_encoded
6-22 | 更新模型数据
https://fastapi.tiangolo.com/tutorial/body-updates/
Pydantic 模型的接口 dict()
的 exclude_unset
可以排除非手动设定的值,包括默认值
async def update_item(item_id: str, item: Item):
update_data = item.dict(exclude_unset=True)
Pydantic 模型的接口 copy()
的 update
可用于更新数据
stored_item_model = Item(**{"name": "Foo", "price": 50.2})
updated_item = stored_item_model.copy(update=update_data)
6-23 | 依赖注入
https://fastapi.tiangolo.com/tutorial/dependencies/
使用场景:
- 有代码需要重复使用
- 使用同一个数据库连接
- 其他
FastAPI 可自动将请求中的数据先传入指定方法并调用,将返回值赋给入参,以此复用代码
FastAPI 的依赖注入系统使自身可兼容:
- 所有关系型数据库
- NoSQL 数据库
- 外部包、API
- etc
6-23-1 | 创建函数依赖
from fastapi import Depends, FastAPI
# 1.定义会被复用的参数
async def common_parameters(
q: Union[str, None] = None,
skip: int = 0,
limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
# 2.添加依赖,只写名称不要调用!!
async def read_items(commons: dict = Depends(common_parameters)):
⚠️ Depends
只能传一个参数,且该参数必须可调用
💡 def
定义的依赖可传给 async def
的路径操作函数,async def
定义的依赖可传给 def
定义的函数
6-23-2 | “存储”依赖
通过 Annontated
将定义好的依赖存起来,方便复用
from typing import Annotated
CommonsDep = Annotated[dict, Depends(common_parameters)]
async def read_items(commons: CommonsDep):
6-23-3 | 创建类依赖
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
# 简便写法:
async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
6-23-4 | 依赖嵌套
from fastapi import Cookie, Depends
# 1.第一个依赖
def query_extractor(q: str | None = None):
return q
# 2.写入另一个依赖
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)],
last_query: Annotated[str | None, Cookie()] = None,
):
if not q:
return last_query
return q
# 3.使用
@app.get("/items/")
async def read_query(
query_or_default: Annotated[str, Depends(query_or_cookie_extractor)]
):
依赖缓存:在一个路径操作函数中重复使用同一个依赖时,一次请求中 FastAPI 不会重复调用依赖,会缓存结果,不需要缓存时,添加参数 use_cache=False
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
6-23-5 | 装饰器依赖
某些情况下,不需要依赖返回值,只需要执行业务逻辑,此时可在路径操作函数的装饰器上添加
async def verify_token(x_token: Annotated[str, Header()]):
if x_token != "token":
raise HTTPException(status_code=400, detail="invalid")
@app.get("/items/", dependencies=[Depends(verify_token), 1])
6-23-6 | 全局依赖
直接加载整个 APP 上,作用于所有路径操作函数
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
6-23-7 | yield
FastAPI 支持依赖在返回结果后,继续执行额外的逻辑,这种情况用 yield
🧲示例:返回数据库连接
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
6-24 | 安全
https://fastapi.tiangolo.com/tutorial/security/
6-24-1 | OAuth2
一个规范,定义了几种处理身份认证和授权的方法
没有指定如何加密通信,它期望你为应用程序使用 HTTPS 通信
6-24-2 | OAuth1
与 OAuth2 完全不同且更为复杂,包含如何加密通信的规范,已不再广泛使用
6-24-3 | OpenID Connect
一个基于 OAuth2 的扩展,明确了一些其中模糊的内容
6-24-4 | OpenAPI
旧称 Swagger,用于构建 API 的开放规范
OAuth2PasswordBearer
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # /token
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
6-25 | 中间件
https://fastapi.tiangolo.com/tutorial/middleware/
FastAPI 支持中间件,在请求处理之前和响应处理之后操作
定义方式:@app.middleware('')
需要接受的参数:request
对象,call_next
将前者接受为参数
import time
from fastapi import FastAPI, Request
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time) # 自定义添加响应头
return response
6-26 | CORSMiddleware
https://fastapi.tiangolo.com/tutorial/cors/
# 1.引入
from fastapi.middleware.cors import CORSMiddleware
# 2.定义信任的域名
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
# 3.添加中间件
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # 是否支持跨域 cookie
allow_methods=["*"], # 支持哪些HTTP请求方式
allow_headers=["*"],
)
其他设置项:
max_age
让浏览器缓存 CORS 响应的最大时间,单位秒allow_origin_regex
:可用正则定义域名expose_headers
:定义 CORS 响应暴露给浏览器的头部
6-27 | 关系型数据库
安装 SQLAlchemy
完成操作数据库,该章节内容主要为操作型,需动手实践
https://fastapi.tiangolo.com/tutorial/sql-databases/
6-28 | 多文件项目
https://fastapi.tiangolo.com/tutorial/bigger-applications/
6-28-1 | APIRouter
问题:两个不同业务逻辑的 .py
文件都实例化了 app = FastAPI()
但是运行服务器时只能指定一个.py 文件定位 app
解决:使用 APIRouter
# 1.引入
from fastapi import APIRouter
# 2.实例化
router = APIRouter()
# 3.在其他业务逻辑文件中使用 router 定义 API 不再使用 app
@router.get("/users/")
# 4. 在实例 app 中注册,否则不起作用
from another_main import router
app.include_router(router)
💡APIRouter
可视为迷你版的FastAPI
,支持后者的所有参数
💡路由可互相包含
router.include_router(other_router)
6-28-2 | 为路由加参
💻为路由添加路径前缀,FastAPI()
没有该参数,注意前缀的结尾绝不能加 /
⚠️
router = APIRouter(prefix="/items")
💡同一 router
可定义多个不同前缀
💡提示:为避免各个路由的设置分散写在不同 .py
文件中,可以在 app
注册路由时定义:
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
6-28-3 | 引用
引用其他项目内模块时:
.pkg.main import *
从当前文件所在包的目录中找到模块pkg
的文件main
并引入所有..pkg.main import *
从当前文件所在包的父级目录中找到模块…...pkg.main import *
从当前文件所在包的父级的父级的目录中找到模块…
6-29 | 后台任务
https://fastapi.tiangolo.com/tutorial/background-tasks/
请求响应后,继续执行代码,不用让客户端等待
6-29-2 | BackgroundTasks
直接作为参数
# 1.导入
from fastapi import BackgroundTasks, FastAPI
# 2.定义 task 函数,同步异步均可
def task_func(param_a):
# 3.调用add_task(),不用谢 async 或 await
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(task_func, param_a)
6-29-2 | 作为依赖使用
from typing import Annotated
from fastapi import BackgroundTasks, Depends, FastAPI
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(message)
def get_query(background_tasks: BackgroundTasks, q: str | None = None):
if q:
message = f"found query: {q}\n"
background_tasks.add_task(write_log, message)
return q
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
):
message = f"message to {email}\n"
background_tasks.add_task(write_log, message)
return {"message": "Message sent"}
6-29-3 | 用 Celery?
如果执行的是繁琐的计算任务,则不必开后台任务在 FastAPI 进程中运行,用功能更强大的 Celery 效果更佳
FastAPI 的后台任务适合需要获取FastAPI 进程在内存中的资源(如变量)的情况,以及“小型”后台任务
6-30 | APP 元信息
https://fastapi.tiangolo.com/tutorial/metadata/
参数 | 类型 | 描述 |
---|---|---|
title | str | The title of the API. |
description | str | A short description of the API. It can use Markdown. |
version | string | The version of the API. This is the version of your own application, not of OpenAPI. For example 2.5.0 . |
terms_of_service | str | A URL to the Terms of Service for the API. If provided, this has to be a URL. |
contact | dict | The contact information for the exposed API. It can contain several fields |
license_info | dict |
🧲示例:
description = """
ChimichangApp API helps you do awesome stuff. 🚀
## Items
You can **read items**.
"""
app = FastAPI(
title="ChimichangApp",
description=description,
version="0.0.1",
terms_of_service="http://example.com/terms/",
contact={"name": "Deadpoolio the Amazing",},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
6-31 | StaticFiles
https://fastapi.tiangolo.com/tutorial/static-files/
# 1.导入
from fastapi.staticfiles import StaticFiles
# 2.将静态资源的 URL 挂载到指定路径
app.mount("/static", StaticFiles(directory="static"), name="static")
6-32 | 测试
https://fastapi.tiangolo.com/tutorial/testing/
Starlette 的测试基于 HTTPX,后者基于请求设计,测试可直接使用 pytest
6-32-1 | TestClient
使用该类需先安装 pip install httpx
, 否则 ModuleNotFoundError
😂
# 1.导入
from fastapi.testclient import TestClient
app = FastAPI()
# 待测函数
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
# 2.导入app
client = TestClient(app)
# 3.用test_开头声明测试用例
def test_read_main():
response = client.get("/", headers={"X-Token": "asdas"})
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"} # 用 assert 判断
定义后 pip install pytest
,命令行执行 pytest
即可
6-31 | StaticFiles
https://fastapi.tiangolo.com/tutorial/static-files/
# 1.导入
from fastapi.staticfiles import StaticFiles
# 2.将静态资源的 URL 挂载到指定路径
app.mount("/static", StaticFiles(directory="static"), name="static")
6-32 | 测试
https://fastapi.tiangolo.com/tutorial/testing/
Starlette 的测试基于 HTTPX,后者基于请求设计,测试可直接使用 pytest
6-32-1 | TestClient
使用该类需先安装 pip install httpx
, 否则 ModuleNotFoundError
😂
# 1.导入
from fastapi.testclient import TestClient
app = FastAPI()
# 待测函数
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
# 2.导入app
client = TestClient(app)
# 3.用test_开头声明测试用例
def test_read_main():
response = client.get("/", headers={"X-Token": "asdas"})
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"} # 用 assert 判断
定义后 pip install pytest
,命令行执行 pytest
即可
6-33 | 排除故障
https://fastapi.tiangolo.com/tutorial/debugging/
利用 IDE 调试,与 FastAPI 无太大关系
END