FastAPI 官方文档学习笔记(简明)

在这里插入图片描述
官方文档 🔗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 服务器软件,可安装 UvicornHypercorn

$ pip install "uvicorn[standard]"

3 | 可选依赖项

Pydantic 使用

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.py
    • appmain.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 的工作方式和 QueryPathBody 相同,包括参数也完全相同,类似 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 标准的 listset即可

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)

  • 定义必填参数

    1. 不写 default=None
    2. 写为 default=...
    3. 写为 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

使用参数 titledescription ,这些描述会被用在接口文档中

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/

方法一:定义 Configschema_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_includeresponse_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 | 引用

引用其他项目内模块时:

  1. .pkg.main import * 从当前文件所在包的目录中找到模块 pkg 的文件 main 并引入所有
  2. ..pkg.main import * 从当前文件所在包的父级目录中找到模块…
  3. ...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/

参数类型描述
titlestrThe title of the API.
descriptionstrA short description of the API. It can use Markdown.
versionstringThe version of the API. This is the version of your own application, not of OpenAPI. For example 2.5.0.
terms_of_servicestrA URL to the Terms of Service for the API. If provided, this has to be a URL.
contactdictThe contact information for the exposed API. It can contain several fields
license_infodict

🧲示例

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

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值