fastapi 请求相关
路径参数,查询参数,请求体
Path,Query,Body,Field,Form,File,UploadFile
api交互文档
Header,Cookie

概述

决定再把官方文档再啃一啃!!!本篇摘抄和总结于官方文档,更多细节请参考官方文档

FastAPI 是一个用于构建 API 的高性能的 web 框架,FastAPI基于 Starlette(负责web部分)和 Pydantic(负责数据部分),fastapi具有可与 NodeJS 和 Go 比肩的极高性能

  • Pydantic 是一个基于 Python 类型提示来定义数据验证,序列化和文档(使用JSON模式)的库,可进一步参看我之前的文章Pydantic
  • Starlette 是一个轻量级的 ASGI 框架/工具包,是python中构建高性能 async web 服务的理想选择

WSGI和ASGI

  • WSGI(Web Server Gateway Interface)即Web服务器网关接口,是基于http协议模式开发的,为同步应用程序提供标准,不支持Websocket。可进一步参看之前的文章Python WSGI Interface
  • ASGI(Asynchronous Server Gateway Interface)即异步服务器网关接口,构建于WSGI接口规范之上的异步服务器网关接口,支持原有模式和Websocket的扩展,即ASGI是WSGI的延伸和扩展
  • 更多关于WSGI和ASGI可进一步参看

啃前可以先看下 What inspired FastAPI

官方文档

学习前的准备

  • 安装
pip install fastapi
pip install uvicorn[standard]
  • 可选依赖
    • 用于Pydantic的如 email_validator (用于 email 校验)等
    • 用于Starlette的如 aiofiles(使用 FileResponse 或 StaticFiles 时)、jinja2(使用默认模板配置时)、python-multipart(需要通过 request.form() 对表单进行解析时)等
  • 安装所有依赖pip install fastapi[all]

认识URL和RESTFul api

[scheme:]//[user[:password]@]host[:port][/path][?query][#fragment]

更多知识点可参看之前的文章URLRESTFul api
常用url:

schema://host/path[?query-string]

路径参数path

import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get('/users/me')
async def read_user_me():
    return {"user_id": "the current user"}

# 与上面的api调换位置再访问127.0.0.1:8000/users/me看下效果
@app.get('/users/{user_id}')
async def read_user(user_id: str):
    return {"user_id": user_id}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)
  • 这里的users/{user_id}路径,user_id是可变动的参数
  • 路径操作是按顺序依次运行的
  • 如果调换如上路由的位置,访问users/me就会匹配/users/{user_id},此时user_id值为me
from enum import Enum

import uvicorn
from fastapi import FastAPI

app = FastAPI()


class UserGender(Enum):
    Male = "male"
    Female = "female"


@app.get('/users/jerry/{user_gender}')
async def get_user_gender(user_gender: UserGender):
    print(user_gender)
    if user_gender == UserGender.Male:
        return {"user_gender": user_gender, "message": "jerry is male"}
    # if user_gender.value == UserGender.Female.value:  # 使用UserGender.Female.value来获取实际值
    if user_gender.value == "female":   # user_gender.value来获取实际的值
        return {"user_gender": user_gender, "message": "jerry is female"}
    return {"user_gender": user_gender}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

声明路径参数,并使用了python的枚举

包含路径的路径参数

如果我们有一个路径操作/files/{file_path},其中file_path本身就包含路径如home/jerry/myfile.txt,如果使用如下的方式访问http://127.0.0.1:8000/files/home/jerry/myfile.txt则会返回{"detail":"Not Found"}

import uvicorn
from fastapi import FastAPI

app = FastAPI()


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

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)
  • 使用Starlette的选项来声明一个包含路径的路径参数:path
# 修改如上例子
import uvicorn
from fastapi import FastAPI

app = FastAPI()


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

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

注意此时访问该url需要使用http://127.0.0.1:8000/files//home/jerry/myfile.txt(在files和home之间使用//)则会返回{"file_path":"/home/jerry/myfile.txt"}

fastapi.Path

  • 可使用 Path 为路径参数声明相同类型的校验和元数据
  • Path 源码如下
def Path(  # noqa: N802
    default: Any,
    *,
    alias: Optional[str] = None,
    title: Optional[str] = None,
    description: Optional[str] = None,
    gt: Optional[float] = None,
    ge: Optional[float] = None,
    lt: Optional[float] = None,
    le: Optional[float] = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
    regex: Optional[str] = None,
    deprecated: Optional[bool] = None,
    **extra: Any,
) -> Any:
    return params.Path(
        default=default,
        alias=alias,
        title=title,
        description=description,
        gt=gt,
        ge=ge,
        lt=lt,
        le=le,
        min_length=min_length,
        max_length=max_length,
        regex=regex,
        deprecated=deprecated,
        **extra,
    )
  • 增加更多校验
    • 数值校验gt大于/ge大于等于/lt小于/le小于等于
    • 字符串长度校验min_length, max_length
    • 体现在docs上的title、description
    • regex正则表达式的校验
    • 路径参数总是必须的,在声明时可使用...将其标记为必须参数
import uvicorn
from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/get_item_by_id/{item_id}")
async def read_item(item_id: int = Path(...,
                                        title="The ID of the item to get",
                                        description="the id of the item",
                                        ge=0, lt=1000)
                    ):
    return {"item_id": item_id}


@app.get("/get_item_by_name/{item_name}")
async def read_item(item_name: str = Path(...,
                                          title="The name of the item to get",
                                          description="the name of the item",
                                          min_length=2, max_length=10,
                                          regex="^j", deprecated=True)   # 以j开头的字符串,deprecated为True表示将来会弃用
                    ):
    return {"item_id": item_name}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

查询参数query

  • 声明不属于路径参数的其他函数参数时,则会解释为查询参数
  • 查询字符串是键值对的集合,这些键值对位于 URL 的 ? 之后,并以 & 符号分隔
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/")
async def read_user_item(user_id: str):
    return {"user_id": user_id}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

访问http://127.0.0.1:8000/users/?user_id=jerry

  • 可使用多路径参数
  • 可使用多查询参数(查询参数可以是必填也可以是可选)
from typing import Optional
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}/items/{item_id}")  # 多路径参数
# 多查询参数(必填和可选)
async def read_user_item(user_id: int, item_id: str, neddy: str, q: Optional[str] = None, short: bool = False):
    item = {"item_id": item_id, "owner_id": user_id, "neddy_query_string": neddy}
    if q:
        item.update({"q": q})
    if not short:
        item.update({"description": "This is an amazing item that has a long description"})
    return item

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

尝试访问:
http://127.0.0.1:8000/users/100/items/jerry?neddy=neddystring
http://127.0.0.1:8000/users/100/items/jerry?neddy=neddystring&q=optinal
http://127.0.0.1:8000/users/100/items/jerry?neddy=neddystring&q=optinal&short=on
bool类型true,on,yes,1(或其变体如字母大写、首字母大写)都等效为true,相反false,off,no,0(或其变体)都等效为false

fastapi.Query

同上面介绍的fastapi.Path,也可使用Query来声明额外的信息和校验

  • fastapi.Query源码如下
def Query(  # noqa: N802
    default: Any,
    *,
    alias: Optional[str] = None,
    title: Optional[str] = None,
    description: Optional[str] = None,
    gt: Optional[float] = None,
    ge: Optional[float] = None,
    lt: Optional[float] = None,
    le: Optional[float] = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
    regex: Optional[str] = None,
    deprecated: Optional[bool] = None,
    **extra: Any,
) -> Any:
    return params.Query(
        default,
        alias=alias,
        title=title,
        description=description,
        gt=gt,
        ge=ge,
        lt=lt,
        le=le,
        min_length=min_length,
        max_length=max_length,
        regex=regex,
        deprecated=deprecated,
        **extra,
    )
  • 增加更多校验
    • 数值校验gt大于/ge大于等于/lt小于/le小于等于
    • 字符串长度校验min_length, max_length
    • 体现在docs上的title、description
    • regex正则表达式的校验
    • 查询参数是非必须的,可使用...将其标记为必须参数,也可设置默认值
    • 别名参数alias
from typing import Optional

import uvicorn
from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_item(q: Optional[str] = Query("fixedquery",  # 默认值fixedquery
                                             alias="item-query",
                                             title="Query string",
                                             description="Query string for the items to search in the database that have a good match",
                                             min_length=3,
                                             max_length=50,
                                             regex="^fixedquery$",
                                             deprecated=True,
                                             )
                    ):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

浏览器访问:
http://127.0.0.1:8000/items/
http://127.0.0.1:8000/items/?q=fixedquery
http://127.0.0.1:8000/items/?item-query=fixedquery

  • 查询参数接受列表/同一查询参数多个值
    如浏览器访问http://127.0.0.1:8000/items/?q=foo&q=bar期望返回{"q": ["foo", "bar"]}
from typing import List

import uvicorn
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
async def read_item(q: List[str] = Query(["jerry", "tom"])):
    return {"q": q}

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

fastapi.Path和fastapi.Query

  • 从上面的介绍,我们知道fastapi.Path和fastapi.Query其实都是函数
  • 当被调用时,它们返回同名类(class Path(Param)、class Query(Param)--> 继承自一个共同的 Param 类)的实例
from fastapi import Path, Query

print(Path, type(Path))
print(Query, type(Query))

p = Path(..., description="path description")
q = Query("fixedquery", description="query description", max_length=10)
print(p, type(p))
print(q, type(q))

from fastapi.params import Path, Query
print(Path, Path.__base__)
print(Query, Query.__base__)
##################输出结果###################3
<function Path at 0x104e8aca0> <class 'function'>
<function Query at 0x104e8ad30> <class 'function'>
default=Ellipsis description='path description' extra={} <class 'fastapi.params.Path'>
default='fixedquery' description='query description' max_length=10 extra={} <class 'fastapi.params.Query'>
<class 'fastapi.params.Path'> <class 'fastapi.params.Param'>
<class 'fastapi.params.Query'> <class 'fastapi.params.Param'>
  • 查询参数和路径参数的顺序
from typing import Optional

import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(item_id: int, q: Optional[str] = 'jerry'):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

如果我们函数定义为async def read_items(q: Optional[str] = 'jerry', item_id: int):即将带有「默认值」的参数放在没有「默认值」的参数之前,在Python 显然会报错:SyntaxError: non-default argument follows default argument

  • 使用fastapi.Path和fastapi.Query来声明参数则不用关心参数的顺序
# 修改上面的例子
from typing import Optional

import uvicorn
from fastapi import FastAPI, Query, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(q: Optional[str] = Query('jerry'), item_id: int = Path(...)):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)
  • 关键字传参,使用*作为函数的第一个参数
# 如果不需要默认值的查询参数放在路径参数后面,且不使用Query
# 显然,使用async def read_items(item_id: int = Path(...), q: str) 会报错
# 则可使用关键字传参,修改代码如下
import uvicorn
from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(*, item_id: int = Path(...), q: str):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

请求体body

我们知道如果需要发送数据的话,需要使用如下方法:POST(较常见)、PUT、DELETE 或 PATCH,在fastapi中使用 Pydantic 模型来声明请求体

from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

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

@app.post("/items/")
async def create_item(item: Item):
    print(item, type(item))
    print(item.dict(), type(item.dict()))
    print(item.name, item.price)
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

请求体结合路径参数或查询参数

  • 如果参数属于单一类型(比如 int、float、str、bool 等)它将被解释为查询参数
  • 如果参数的类型被声明为一个 Pydantic 模型,它将被解释为请求体
from typing import Optional

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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

@app.post("/items/{item_id}")
async def create_item(item_id: int, q1: str, item: Item, q2: Optional[str] = None):
    result = {"item_id": item_id, **item.dict(), "q1": q1}
    if q2:
        result.update({"q2": q2})
    return result

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

使用postman发送json数据{"name": "apple", "price": 12.2, "tax": 12}127.0.0.1:8000/items/123?q1=jerry&q2=tom试下

多个请求体参数

期望请求体参数是这个结构:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    }
}
  • 可声明多个请求体参数(使用多个 Pydantic 模型参数)如 item 和 user
from typing import Optional

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


class User(BaseModel):
    username: str
    full_name: Optional[str] = None


@app.post("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    return {"item_id": item_id, "item": item, "user": user}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

fastapi.Body

相关参数基本同前面介绍的fastapi.Path和fastapi.Query

# 在如上的例子上再声明一个单一值的请求体
from typing import Optional

import uvicorn
from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()


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

class User(BaseModel):
    username: str
    full_name: Optional[str] = None

@app.post("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body(..., gt=0)):
    return {"item_id": item_id, "item": item, "user": user, "importance": importance}

if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

如果函数定义为async def update_item(item_id: int, item: Item, user: User, importance: int):,因为importance为单一值,则被当作是查询参数,不符合要求

  • fastapi.Body的embed参数
    如果期望单一请求体参数作为键值对的内容如
{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    }
}
# 而不是
{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}
# 设置Body 参数 embed为true
from typing import Optional

import uvicorn
from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()


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


@app.post("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
    return {"item_id": item_id, "item": item}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

pydantic.Field

Field 的工作方式和前面介绍的 Query、Path 和 Body 相同,它们的参数也基本相同

from typing import Optional

import uvicorn
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = Field(None, title="The description of the item", max_length=300)
    price: float = Field(..., gt=0, description="The price must be greater than zero")
    tax: Optional[float] = None


@app.post("/items/{item_id}")
async def update_item(*, item_id: int, item: Item = Body(..., embed=True), q: Optional[str] = None):
    results = {"item_id": item_id, "item": item}
    if q:
        results.update({'q': q})
    return results


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

请求体-嵌套模型

  • 嵌套模型:Pydantic 模型的每个属性都具有类型,这个类型本身可以是另一个 Pydantic 模型
  • 可以定义任意深度的嵌套模型
from typing import Optional, List

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl   # 该字符串将被检查是否为有效的 URL
    name: str


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    images: Optional[List[Image]] = None


class Offer(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    items: List[Item]


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


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

则期望的请求体如:

{
    "name": "Foo",
    "description": "offer desp",
    "price": 10,
    "items":
    [
        {
            "name": "jerry",
            "price": 9.5,
            "images":
            [
                {
                    "url": "https://example1.com",
                    "name": "image1"
                },
                {
                    "url": "https://example2.com",
                    "name": "image2"
                }
            ]
        }
    ]
}
  • 注意其中pytantic HttpUrl的使用
  • 我们可以使用更多数据类型,可进一步参看Pydantic的外部类型如datetime.datetime、datetime.date、datetime.time等
  • 纯列表请求体及任意dict构成的请求体
    • JSON 请求体的最外层是一个 JSON array(即 Python list),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样如images: List[Image]
    • 请求体JSON 仅支持将 str 作为键。但是 Pydantic 具有自动转换数据的功能,如下例
from typing import Dict
import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
    return weights


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

Form表单数据

请求体不是JSON而是Form表单时需要使用fastapi.Form,如 OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 username 和 password

  • 要使用表单,需预先安装 python-multipart:pip install python-multipart
  • Form 可以声明与 Body (及 Query、Path、Cookie)相同的元数据和验证
import uvicorn
from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(..., min_length=6)):
    return {"username": username, "password": password}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

如发送json如{ "username": "jerry", "password": "123458"}则会校验错误

上传文件

  • 同样,上传文件以以表单数据形式发送,所以要预先安装 python-multipart
from typing import List

import uvicorn
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

很多情况下, 定义 File 参数时使用 UploadFile,更多细节官方文档

  • 在postman中上传文件,在body中选择form-data,key选择file,在value中选择要上传的文件即可

完善的api docs

fastapi自动生成API交互文档

  • http://127.0.0.1:8000/docs:由Swagger UI生成(常用),点击Try it out-> 填写参数-> Execute,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示,当然我们也可以选择用postman之类的工具来做调试
  • http://127.0.0.1:8000/redoc:由ReDoc生成,不常用

示例

  • 如下目录
$ tree .
.
├── app1
│   ├── __init__.py
│   └── api.py
├── app2
│   ├── __init__.py
│   └── api.py
├── app3
│   ├── __init__.py
│   └── api.py
└── main.py
  • app1.api内容如下
# app1/api.py
from typing import Optional
from fastapi import APIRouter, Body
from pydantic import BaseModel

router = APIRouter()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None


@router.get("/", description="app1的接口描述1")
def read_root():
    return {"Hello": "World"}


@router.get("/items/{item_id}", description="app1的接口描述2")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}


@router.put("/items/{item_id}", summary="app1更新商品的接口", response_description="app1更新成功则返回商品名称")
def update_item(item_id: int,
                item: Item = Body(
                    ...,
                    example={
                        "name": "jerry",
                        "price": 12.3,
                        "is_offer": False
                    })
                ):
    """
    更新商品:
    - itemid: 商品id
    - item: 需要更新的商品信息
    """
    return {"item_name": item.name, "item_id": item_id}
  • app2.api内容如下
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel, Field

router = APIRouter()


class Item(BaseModel):
    name: str = Field(..., example="jerry")
    price: float = Field(..., example=12.3)
    is_offer: Optional[bool] = Field(None, example=False)


@router.get("/", description="app2的接口描述1")
def read_root():
    return {"Hello": "World"}


# response_description 只用于描述响应,description 一般则用于描述路径操作
@router.get("/items/{item_id}", description="app2的接口描述2")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}


@router.put("/items/{item_id}", summary="app2更新商品的接口", response_description="app2更新成功则返回商品名称")
def update_item(item_id: int, item: Item):
    """
    更新商品:
    - itemid: 商品id
    - item: 需要更新的商品信息
    """
    return {"item_name": item.name, "item_id": item_id}
  • app3.api 内容如下
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel, Field

router = APIRouter()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None

    class Config:
        schema_extra = {
            "example": {
                "name": "jerry",
                "price": 12.3,
                "is_offer": False,
            }
        }


@router.get("/", description="app3的接口描述1")
def read_root():
    return {"Hello": "World"}


# response_description 只用于描述响应,description 一般则用于描述路径操作
@router.get("/items/{item_id}", description="app3的接口描述2")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}


@router.put("/items/{item_id}", summary="app3更新商品的接口", response_description="app3更新成功则返回商品名称")
def update_item(item_id: int, item: Item):
    """
    更新商品:
    - itemid: 商品id
    - item: 需要更新的商品信息
    """
    return {"item_name": item.name, "item_id": item_id}
  • init内容如下
# app1/__init__.py、app2/__init__.py 、app3/__init__.py
from .api import router
  • main.py 内容如下
import uvicorn
from fastapi import FastAPI

import app1
import app2
import app3

description = '''
`支持markdown来描述`
## api文档描述
- read_root
- read_item、update_item
'''

app = FastAPI(
    title="这是api文档标题",
    description=description,
    version="0.0.1"
)

app.include_router(app1.router, prefix='/pre_app1', tags=["app1 tags"])
app.include_router(app2.router, prefix='/pre_app2', tags=["app2 tags"])
app.include_router(app3.router, prefix='/pre_app3', tags=["app3 tags"])

if __name__ == '__main__':
    uvicorn.run('main:app', reload=True)
  • 运行main.py并浏览器打开http://127.0.0.1:8000/docs则可看到如下api交互文档

总结

  • 传递的额外参数不会添加任何验证,只会添加注释,用于文档的目的
    • 可以使用 Config 和 schema_extra 为Pydantic模型声明一个示例
    • 可以使用 Field 的附加参数example添加一个示例
    • 可以使用 Body 的额外参数example添加一个示例
  • title、description、version
  • tags、summary、response_description、responses、docstring
  • 也可使用openapi_tags:创建标签元数据并把它传递给 openapi_tags 参数

Cookie和Header参数

  • 同之前介绍的Query, Path,相关参数基本相同,这里不做过多介绍
from typing import Optional, List
import uvicorn
from fastapi import FastAPI, Cookie, Header

app = FastAPI()


@app.get("/items/")
async def read_items(ads_id: Optional[str] = Cookie(None), user_agent: Optional[str] = Header(None),
                     x_token: Optional[List[str]] = Header(None)):
    return {"ads_id": ads_id, "User-Agent": user_agent, "X-Token": x_token}


if __name__ == '__main__':
    uvicorn.run("main:app", reload=True)

注意-的处理,因为在python中变量中包含-为不规范的变量命名
在标准的http请求中如User-Agent,自定义的headers如X-Token,且headers中参数是大小写不敏感的
在fastapi中,fastapi.Header 将把参数名称的字符从下划线 (_) 转换为连字符 (-) 来提取并记录 headers

在postman中访问:http://127.0.0.1:8000/items并在headers中添加相关参数,效果如下

扩展阅读和学习