FastAPI从入门到运用

TOC

一、FastAPI的基本了解

什么是FastAPI?

FastAPI是一个基于Python 3.6+类型注解的现代Web框架,支持同步与异步编程,特点是速度快、易用、安全且开发效率高。用于快速构建高性能的Web API。

核心技术和组件

FastAPI 并不是单独实现所有功能,而是巧妙地集成了多个强大的Python库和框架。

  • Starlette:负责异步 HTTP 处理、路由、中间件、WebSocket、后台任务等。
  • Pydantic:用于请求体、查询参数、响应体的数据校验和序列化。
  • Uvicorn:ASGI 服务器,运行 FastAPI 应用的高性能异步服务器。
  • OpenAPI/JSON Schema:自动生成 API 文档(Swagger / ReDoc 基于 OpenAPI)。
  • 类型注解 (Python 3.6+):用于静态分析、自动生成校验逻辑和文档。

二、安装和第一个应用

fastapi安装

使用pip工具安装,由于fastapi的启动需要依赖uvicorn,需要还需要安装uvicron工具:

pip install fastapi uvicron
# 使用国内阿里云源
pip install fastapi uvicron -i https://mirrors.aliyun.com/pypi/simple

第一个fastapi应用

from fastapi import FastAPI   # 导入FastAPI模块
from uvicorn import run       # 导入run方法
app = FastAPI()      # 初始化FastAPI对象

def home():
    return 'Hello fastAPI!'

if __name__ == '__main__':
    # 创建路由绑定方法
    app.get('/')(home)
    # 运行程序
    run(app)

启动成功后会出现一下字样:

INFO:     Started server process [91227]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

上面信息可以看到默认地址是访问http://127.0.0.1:8000,说明启动成功。
设置host访问地址和port启动端口

run(app, host='0.0.0.0', port=8080)

使用装饰器完成路由绑定

@app.get('/')
def home():
    return 'Hello fastAPI!'

还可以将get方法改成:@app.post()@app.put()@app.delete()等方法。

开启uvicron的其他方式

使用终端开启uvicron服务,如下所示:

uvicorn app:app --reload

参数详解:

  • app:app:app.py主程序文件和app.py文件中app = FastAPI()语句创建的app对象。
  • --reload:在代码改变后重启服务器,只能在开发时候使用
    更多参数设置:
参数
说明
示例
–host 绑定主机地址(默认 127.0.0.1) –host 0.0.0.0
–port 绑定端口(默认 8000) –port 8080
–reload 自动重载代码(开发模式必备) –reload
–reload-dir 监控指定目录变化触发重载 –reload-dir src
–workers 进程数(默认 1,生产可设多进程) –workers 4
–loop 异步事件循环实现,默认 auto –loop uvloop
–http HTTP 协议实现(默认 auto) –http h11
–proxy-headers 信任 X-Forwarded-* 代理头(反向代理部署时要开) –proxy-headers
–forwarded-allow-ips 允许代理的 IP 列表,默认 127.0.0.1 –forwarded-allow-ips *
–root-path 应用根路径(反向代理路径前缀) –root-path /api/v1
–log-level 日志级别(critical、error、warning、info、debug、trace)
–log-config 日志配置文件(JSON/YAML)
–access-log / --no-access-log 启用或关闭访问日志

启动示例:

uvicorn app:app --reload --host 0.0.0.0 --port 8080 --root-path /api

接口文档

FastAPI框架内置了Swagger UIReDoc两种交互式 API 文档页面,并且会根据你的接口定义(包括路径、参数、数据模型、返回类型等)自动生成文档内容。
Swagger UI地址: http://127.0.0.1:8000/docs
ReDoc地址: http://127.0.0.1:8000/redoc
Swagger UI界面如下图所示:
fastapi
我们可以点开接口查看详细信息,如下图所示:
fastapi
ReDoc界面如下图所示:
fastapi

三、请求和响应

路径参数和查询参数

路径参数

基本路由(静态)

我们设立两个路径,分别返回两个不同的值,通过路径来控制调用不同的方法。
代码示例:

@app.get('/admin')
def admin():
    return '管理后台'
 
@app.get('/user')
def user():
    return '用户界面'

动态路由(参数)

后面的路径是个变量,没有固定的值。

1.声明路径参数

在路径后面设置路径参数来获取后面参数user_name的值,再作为user函数返回user_name变量的值,代码示例如下:

@app.get('/user/{user_id}')
def user(user_id):
    return f'user_id: {user_id}'
2.声明路径参数的类型

我们可以声明路径参数变量的类型,代码示例如下:

@app.get('/user/{user_id}')  
def get_user(user_id: int):  
    return f'user_id: {user_id}'
3.路径类型参数

假如你需要一个路径操作,路径为/files/{file_name}这个路径操作中包含的变量是一个类似home/demo/test.txt的路径,最终的路径为:/files/home/demo/test.txt
这种变量我们需要声明路径参数为路径类型,需要使用path转换器
代码示例如下:

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

可以看出这个类型的参数类型配置和其他的类型配置位置不一样,其他都是在函数方法中的参数后面声明,而path类型的声明在路由修饰中的变量中声明。
测试访问返回内容:

$ curl -l http://127.0.0.1:8080/files/demo/20150706/test.txt
"file_path: demo/20150706/test.txt
4.限定路径参数有效值

将路径参数进行限制,只有访问自己设置的路由规定内,才能正常访问。代码示例如下:

from enum import Enum
app = FastAPI()


# 定义一个类存放可用的有效值
class Type_Class_name(str, Enum):  
	Name = 'jack'  
	Year = 2025  
	Id = '37847364'  
	isOK = True


@app.get('/select/{user_data}')  
def select(user_data: Type_Class_name):   # 将变量类型设置成上面的类
    return {'status': user_data}

下面进行测试,当我们访问有效值时会正常返回设置的返回值,比如:访问http://127.0.0.1:8080/select/2015则会返回{"status":"2015"}类似这样,访问无效值的时候则不会返回正常的值。测试结果如下:

$ curl -l http://127.0.0.1:8080/select/jack
{"status":"jack"}
$ curl -l http://127.0.0.1:8080/select/True
{"status":"True"}
$ curl -l http://127.0.0.1:8080/select/347874
{
  "detail": [
    {
      "type": "enum",
      "loc": [
        "path",
        "user_data"
      ],
      "msg": "Input should be 'jack', '2025', '37847364' or 'True'",
      "input": "347874",
      "ctx": {
        "expected": "'jack', '2025', '37847364' or 'True'"
      }
    }
  ]
}

查询参数

当你声明不属于路径参数的其他函数参数时,它们将自动解释为“Query”参数,也就是查询参数。
在URL链接中,比如:http://127.0.0.1:8080/user?id=124&name=tom,?后面就是跟的查询参数,查询参数一般都是以key=value键值对的形式存在,多个参数则使用&来分割。

查询参数基本设置

代码示例如下:

@app.get('/get_data')
def get_items(user_id: int, user_name: str):
    return {'id': user_id, 'name': user_name}

当你访问URL路径加入参数,比如:http://127.0.0.1:8080/get_data\?user_id=167\&user_name=zhangsan
测试访问返回内容:

$ curl -l http://127.0.0.1:8080/get_data\?user_id=167\&user_name=zhangsan
{"id":167,"name":"zhangsan"}

查询参数其他设置

设置查询参数为默认值
当没有查询参数的时候,参数则使用默认值。代码示例如下:

# 设置初始默认值
@app.get('/add')
def get_items(numa: int = 2, numb: int = 8):
    return f'numa + numb = {numa + numb}'

设置可选的查询参数
声明可选的Query参数,只需要将他们的默认值设置为None即可。代码示例如下:

@app.get('/exist_name')
def exist_name(name=None):
    if name is None:
        return 'name is None!'
    else:
        return f'name is {name}'

请求对象

请求对象(Request)就是一个封装了当前HTTP请求所有信息的对象,它是基于Starlette的Request类实现的。

可以理解为:服务器收到一个 HTTP 请求后,把这个请求的 URL、方法、头信息、Cookie、查询参数、请求体等内容,全部打包成一个Request实例,我们可以使用传给我们的Request实例获取这些信息

Request对象

Request 在 FastAPI 里是 Starlette 的 Request 对象,除了能拿到headers和cookies,也能直接获取query的参数变量以及读取请求体内容(body)。
下面我们直接用代码来查看Request对象里面的信息,代码示例如下:

from fastapi import FastAPI
from fastapi import Request
app = FastAPI()


@app.post("/info")
async def get_info(rq: Request):
    client_host = rq.client.host  # 获取host请求地址
    headers = dict(rq.headers)  # 获取header信息
    cookie = rq.cookies     # 获取cookie信息
    body_bytes = await rq.body()  # 获取原始请求体(bytes 类型)
    body_data = body_bytes.decode("utf-8")   # 将原始请求体转化成字符串
    user_id = rq.query_params.get('user_id')  # 获取query中查询参数的变量 user_id
    user_name = rq.query_params.get('user_name')  # 获取query中查询参数的变量 user_name
    return {'client_host': client_host, 'herders': headers, 'cookie': cookie, 'user_id': user_id, 'user_name': user_name, 'body': body_data}

我们通过测试请求访问来查看对象信息结果,如下所示:

$ curl -X POST http://127.0.0.1:8080/info\?user_id\=2784864\&user_name\=jason \
  -H 'Cookie: session_id=jsidhuhu38787826' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_name": "bob",
    "user_id": "2783786",
    "money": 869.26
}'
# 返回结果如下
{
  "client_host": "127.0.0.1",
  "herders": {
    "host": "127.0.0.1:8080",
    "user-agent": "curl/8.7.1",
    "accept": "*/*",
    "cookie": "session_id=jsidhuhu38787826",
    "content-type": "application/json",
    "content-length": "73"
  },
  "cookie": {
    "session_id": "jsidhuhu38787826"
  },
  "user_id": "2784864",
  "user_name": "jason",
  "body": "{\n    \"user_name\": \"bob\",\n    \"user_id\": \"2783786\",\n    \"money\": 869.26\n}"

根据上面请求Request对象的方式可以获取Cookie信息,但是FastAPI中有专门的方法获取Cookie信息,定义Cookie参数,就和你定义QueryPath参数一样的方式。代码示例如下:

from fastapi import Cookie  # 导入Cookie方法
app = FastAPI()


@app.get('/get_cookie')
def get_cookie(cookie: str = Cookie(None)):
    return {"cookie": cookie}

Header Parameters(Header参数)

Header在FastAPI中也有专门的方法获取Header信息,代码示例如下:

from fastapi import Header  # 导入Header方法
app = FastAPI()


@app.get('/get_headers')
def get_headers(headers: str = Header(None)):
    return {"headers": headers}

四、Route路由管理

在FastAPI中,APIRouter是一个路由组织器,用来将相关的API路由分组管理,跟Flask的蓝图(Blueprint)相似

基本用法

主要步骤就是导入APIRouter,然后初始化APIRouter()对象,绑定路由方法到对象上,在主应用对象上注册该路由,代码示例如下:

from fastapi import FastAPI, APIRouter
app = FastAPI()   # 创建主应用
router = APIRouter()   # 创建一个路由器

# 定义路由
@router.get("/")
def hello():
    return "welcome to click user."

# 注册到主应用
app.include_router(router, prefix="/users", tags=["users"], dependencies=[])

路由配置项目示例

项目目录结构:

$ tree demo
demo
├── app.py
└── routers
    ├── __init__.py
    ├── admin.py
    └── user.py

2 directories, 4 files

在上面的结构中app.py是主程序文件,routers/admin.pyrouters/user.py文件分别是admin模块和user模块的代码文件
app.py文件,代码如下:

from fastapi import FastAPI
from uvicorn import run
from routers import user, admin


def main():
    app = FastAPI()
    app.include_router(user.router, prefix="/user", tags=["user"])
    app.include_router(admin.router, prefix="/admin", tags=["admin"])
    run(app, host='0.0.0.0', port=8000)


if __name__ == '__main__':
    main()

routers/admin.py文件,代码如下:

from fastapi import APIRouter
router = APIRouter()


@router.get("/")
def admin():
    return "Welcome call admin !!!"


@router.get("/api")
def get_admin():
    return "Welcome call admin api !!!"

routers/user.py文件,代码如下:

from fastapi import APIRouter
router = APIRouter()


@router.get("/")
def user():
    return "Welcome call user !!!"


@router.get("/api")
def get_user_id():
    return "Welcome call user api !!!"

这样我们就可以在通过不同的模块文件来管理不同路由的功能,访问/admin路径调用admin模块功能,访问/user路径调用user模块功能。我们需要增加路由只需在routers目录下新增一个模块文件,在主应用中注册即可,新增模块功能只需要在对应模块下添加子路由以及方法即可。

五、参数条件验证

路径参数条件验证

参数必选验证

可以通过Path声明路径参数的相同类型的验证和元数据。因为Path参数是必须的(它是路径URL的一部分),因此你需要用...声明标志这是必需参数代码示例如下:

from fastapi import Path   # 导入Path方法
app = FastAPI()


@app.get('/set_path/{user_id}') # 设置路径参数条件验证  
    def set_path(user_id: int = Path(..., title='this is path set!')):  
    return user_id

如果要声明q查询参数而不使用Query或任何默认值,并且使用Path声明路径参数user_id并使用不同的顺序,则Python对此有一些特殊的语法。那就是:传递 * 作为函数的第一个参数。
Python不会对第一个星号做任何事情,但是它将知道*星号之后所有参数都应称为关键字参数(键-值对),也称为kwargs 。 即使它们没有默认值。
示例代码如下:

@app.get('/set_path/{user_id}')    # 设置路径参数条件验证
def set_path(*, user_id: int = Path(..., title='this is path set!'), data: str):
    string_data = {"user_id": user_id, "data": "Null"}
    if data:
        string_data = {"user_id": user_id, "data": data}
    return string_data

路径参数数字验证

我们可以对路径参数的数字大小进行设置验证,比如大于多少(gt)、小于多少(lt)或者大于等于(ge)、小于等于(le),以及取什么范围之间类似这样的设置。
比如设置user_id取值在1000~2000之间,示例代码如下:

@app.get('/set_path/{user_id}')    # 设置路径参数条件验证
def set_path(*, user_id: int = Path(..., title='this is path set!', ge=1000, le=2000), data: str):
    string_data = {"user_id": user_id, "data": "Null"}
    if data:
        string_data = {"user_id": user_id, "data": data}
    return string_data

我们使用curl命令进行测试,分别测试取值范围外和范围内的数字进行访问,如下所示:

# 访问数字小于1000
$ curl -l http://127.0.0.1:8080/set_path/999\?data\=hello
{
  "detail": [
    {
      "type": "greater_than_equal",
      "loc": [
        "path",
        "user_id"
      ],
      "msg": "Input should be greater than or equal to 1000",
      "input": "999",
      "ctx": {
        "ge": 1000
      }
    }
  ]
}
# 访问数字大于2000
$ curl -l http://127.0.0.1:8080/set_path/2001\?data\=hello
{
  "detail": [
    {
      "type": "less_than_equal",
      "loc": [
        "path",
        "user_id"
      ],
      "msg": "Input should be less than or equal to 2000",
      "input": "2001",
      "ctx": {
        "le": 2000
      }
    }
  ]
}
# 访问数字在取值范围内
$ curl -l http://127.0.0.1:8080/set_path/1045\?data\=hello
{"user_id":1045,"data":"hello"}

查询参数条件验证

字符串长度验证

我们可以使用fastapi库下的Query设置max_length来限制催化性能参数的长度,最小值跟最大值实现起来一样的只需要Query设置为min_length即可。代码示例如下:

from fastapi import Query   # 导入Query方法
app = FastAPI()


@app.get('/set_query')    # 设置参数条件验证
def set_items(data: str = Query(None, min_length=5, max_length=10)):
    string_data = {"data": "I am a query function!"}
    if data:
        string_data = {"data": data}
    return string_data

我们通过curl命令分别测试默认不要参数、设置限制内的参数以及设置超过限制的参数来验证效果,测试结果如下:

# 不加参数访问
$ curl -l http://127.0.0.1:8080/set_query
{"data":"I am a query function!"}
# 加上限定内的字符串参数访问
$ curl -l http://127.0.0.1:8080/set_query\?data\=Iamgoodboy
{"data":"Iamgoodboy"}
# 加上超过限定的字符串参数访问
$ curl -l http://127.0.0.1:8080/set_query\?data\=sdjidhjsaiuhduishaiudhasiudhsuahduasgduygayud
{
  "detail": [
    {
      "type": "string_too_long",
      "loc": [
        "query",
        "data"
      ],
      "msg": "String should have at most 10 characters",
      "input": "sdjidhjsaiuhduishaiudhasiudhsuahduasgduygayud",
      "ctx": {
        "max_length": 10
      }
    }
  ]
}
# 加上低于最小限制的字符串参数访问
$ curl -l http://127.0.0.1:8080/set_query\?data\=sd
{
  "detail": [
    {
      "type": "string_too_short",
      "loc": [
        "query",
        "data"
      ],
      "msg": "String should have at least 5 characters",
      "input": "sd",
      "ctx": {
        "min_length": 5
      }
    }
  ]
}

将默认值设置为...,那么表示这个参数为必选的。代码示例如下:

def set_items(data: str = Query(..., max_length=10)):

字符串正则表达式验证

我们可以使用fastapi库下的Query设置regex来进行参数正则匹配。代码示例如下:

@app.get('/set_query')
def set_items(data: str = Query(None, min_length=5, max_length=10, regex='^a[A-Za-z0-9]*$')):
    string_data = {"data": "I am a query function!"}
    if data:
        string_data = {"data": data}
    return string_data

以上代码,我们设置的规则是:以a字母开头,后面跟着任意数量的大小写字母或数字(包括0个或多个)的参数符合我们的验证要求。
正则解释:

  • ^:匹配字符串开始
  • a:字母 a(小写)
  • [A-Za-z0-9]*:零个或多个大小写字母或数字
  • $:匹配字符串结束
    我们通过curl命令进行测试正则是否生效,如下所示:
# 请求参数不以a开头
$ curl -l http://127.0.0.1:8080/set_query\?data\=dhuid3
{
  "detail": [
    {
      "type": "string_pattern_mismatch",
      "loc": [
        "query",
        "data"
      ],
      "msg": "String should match pattern '^a[A-Za-z0-9]*\$'",
      "input": "dhuid3",
      "ctx": {
        "pattern": "^a[A-Za-z0-9]*$"
      }
    }
  ]
}
# 请求参数符合正则,以a开头
$ curl -l http://127.0.0.1:8080/set_query\?data\=a3hu37
{"data":"a3hu37"}

其他常用参数信息

添加参数标题title,代码示例如下:

def set_items(data: str = Query(None, title="Query string"):

添加参数描述description,代码示例如下:

def set_items(data: str = Query(None, description="Query string for the items to search in the database that have a good match"):

添加参数的别名alias,代码示例如下:

def set_items(data: str = Query(None, alias="item-query"):

添加参数标记deprecated,标记是否弃用等,代码示例如下:

def set_items(data: str = Query(None, deprecated=True):

声明多次出现的查询参数

多次出现的查询参数也就是列表类型的参数;比如:

http://localhost:8080/items/?data=jack&data=tom

多个查询参数的定义,代码示例如下:

from typing import List  # 导入List类型
app = FastAPI()


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

使用curl命令进行测试,如下所示:

$ curl -l http://127.0.0.1:8080/items\?data\=jack\&data\=tom
# 返回结果如下
{"data":["jack","tom"]}

六、请求体参数应用

什么是请求体?

当你需要从客户端(如浏览器类)发送数据给服务端(如API)时,可以发送一个请求体(包含所有需要发送的数据打包成一个结构体)到服务端。例如:我现在想要查询用户叫zhangsan,订单编号为267376752的订单详情数据,那么我这里用户名和订单编号数据就是一个请求体的组成。
请求体是客户端发送到您的API的数据。 响应体是您的API发送给客户端的数据。
定义请求体,需要使用Pydantic模型。注意以下几点:

  • 不能通过GET请求发送请求体。
  • 发送请求体数据,必须使用以下几种方法之一:POST(最常见)、PUT、DELETE、PATCH。

实现请求体

请求体模型实现

先声明一个请求体数据模型,声明请求体数据模型为一个类,且该类继承 BaseModel。数据类型为JSON对象。

from pydantic import BaseModel

class Item(BaseModel):  
	user_name: str
	user_id: str
	description: str = None
	money: float

请求体数据结构:

// 传递全部参数
{
    "user_name": "jack",
    "user_id": "3894863902",
    "description": "A interesting people",
    "money": 106.28
}
// 不传递默认值或可选值
{
    "user_name": "jack",
    "user_id": "3894863902",
    "money": 106.28
}

请求体模型应用

将模型定义为参数,代码如下所示:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class Item(BaseModel):
    user_name: str
    user_id: str
    description: str = None
    money: float


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

测试如下:
然后我们使用post请求传入一个请求体参数,比如:{"user_name": "jack", "user_id": "2784369", "money": 263.78},就会得到类型类似为JSON的返回值。如下所示:

$ curl -X POST http://127.0.0.1:8080/save_money \
  -H "Content-Type: application/json" \
  -d '{"user_name": "jack", "user_id": "2784369", "money": 263.78}'
# 返回结果如下
{"user_name":"jack","user_id":"2784369","description":null,"money":263.78}

使用api接口工具进行请求测试,如下图所示:
fastapi
在函数内部,可以直接访问模型对象的所有属性,对象下的子项比如:item.money。代码示例如下:

@app.post("/save_money")
async def save_money(item: Item):
    return item.money

七、请求体进阶使用

1.混合参数使用

我们同时使用路径参数查询参数请求体参数,代码示例如下:

from pydantic import BaseModel
from fastapi import Query, Path
app = FastAPI()


class Item(BaseModel):
	user_name: str
	user_id: str
	description: str = None
	money: float


@app.post('/request/{request_id}')
def request_collection(*, request_id: int = Path(..., title='this is set request id!', ge=1000, le=2000),
                       request_data: str = Query(None, min_length=5, max_length=10), item: Item):
    string_data = {"request_id": request_id}
    if request_data:
        string_data.update({"request_data": request_data})
    if item:
        string_data.update({"item": item})
    return string_data

我们在上面的示例中加入了路径参数request_id,查询参数request_data以及请求体参数item,第一个参数*表示星号之后所有参数都应称为关键字参数(键-值对)。
我们下面进行测试传入三种不同的参数进行获取,如下所示:

$ curl -X POST http://127.0.0.1:8080/request/1562\?request_data\=hello \
  -H 'Content-Type: application/json' \
  -d '{"user_name": "bob", "user_id": "2783786", "money": 869.26}'
{
	"request_id":1562,
	"request_data":"hello",
	"item":
	{
		"user_name":"bob",
		"user_id":"2783786",
		"description":null,
		"money":869.26
	}
}

我们也可以使用api接口工具进行调试,分别编辑查询参数和请求体参数,路径参数只需在接口路径上添加即可,如下图所示:
fastapi
fastapi

2.多模型请求体参数

我们可以定义多个请求体模型,代码示例如下:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class Item(BaseModel):  # 创建第一个模型
    user_name: str
    user_id: str
    description: str = None
    money: float


class User(BaseModel):  # 创建第二个模型
    name: str
    sex: str
    full_name: str = None


@app.put('/update/{request_id}')  # 写一个路由绑定函数加入两个模型请求体参数
def update_user(*, request_id: int, item: Item, users: User):
    return {"request_id": request_id, "item": item, "users": users}

在这种情况下,我们请求体的格式为一个字典,请求体如下所示:

{
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    }
}

测试请求,如下所示:

$ curl -X PUT http://127.0.0.1:8080/update/1562 \
  -H 'Content-Type: application/json' \
  -d '{
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    }
}'
# 返回结果如下
{
    "request_id": 1562,
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "description": null,
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    }
}

3.Body方法定义单值的请求体参数

该方法主要应用于,除了定义的模型之外还需要在请求体中单独增加一个参数,比如上面的itemuser两个字段外再加一个code字段在请求体中。当你单独在变量中定义这个变量时,FastAPI会默认将其定义为query参数,所以我们可以用Body方法来定义。代码示例如下:

from fastapi import FastAPI
from fastapi import Body
from pydantic import BaseModel
app = FastAPI()


class Item(BaseModel):
    user_name: str
    user_id: str
    description: str = None
    money: float


class User(BaseModel):
    name: str
    sex: str
    full_name: str = None


@app.put('/update/{request_id}')  # 再之前的两个模型基础上增加一个单独变量code
def update_user(*, request_id: int, item: Item, users: User, 
                code: int = Body(..., min_length=10)):
    return {"request_id": request_id, "item": item, "users": users, "code": code}

这里的请求体在上面的基础上增加一个单值的请求体参数,如下所示:

{
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    },
    "code": 28783639464832
}

测试请求,如下所示:

$ curl -X PUT http://127.0.0.1:8080/update/1562 \
  -H 'Content-Type: application/json' \
  -d '{
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    },
    "code": 28783639464832
}'
# 返回结果如下
{
    "request_id": 1562,
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "description": null,
        "money": 869.26
    },
    "users": {
        "name": "Bob",
        "sex": "man",
        "full_name": "Jack Bob"
    },
    "code": 28783639464832
}

4.Body方法嵌入单个请求体参数

如果你想要传入的请求体带上key值,key中包含对应的模型内容,那么可以直接使用Body方法的特殊参数来实现,代码示例如下:

@app.post("/save_money")
def save_money(item: Item = Body(..., embed=True)):
    return item

跟之前没有key的请求体结构对比,如下所示:

// 现在要传入的请求体结构
{
    "item": {
        "user_name": "bob",
        "user_id": "2783786",
        "money": 869.26
    }
}
// 之前的请求体结构
{
	"user_name": "bob",
	"user_id": "2783786",
	"money": 869.26
}

5.添加请求体示例字段

使用Body方法中的example字段来设置示例字段。

@app.get("/select_items")
def select_items(item: Item = Body(..., example={
    "user_name": "jack",
    "user_id": "3894863902",
    "description": "A interesting people",
    "money": 106.28
})):
    return item

添加了之后我们可以在接口文档也就是http://127.0.0.1:8000/docs中查看详情,如下图所示:
fastapi

八、HTML模板渲染

FastAPI虽然主打API服务,但它也是基于Starlette的,所以同样可以像Django、Flask一样渲染HTML模板。
FastAPI主要使用Jinja2作为模板引擎(和 Flask、Django 模板很像),跟Flask一样,可以同时渲染HTML模板,也可以直接返回静态HTML页面。

渲染HTML模板

使用fastapi库下的templating.Jinja2Templates方法则可以渲染以Jinja2作为模板引擎的HTML文件,并指定模板路径位置。
项目结构如下:

$ tree demo
demo/
├─ app.py
└─ templates/
   └─ index.html

demo/app.py文件代码示例如下:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
app = FastAPI()
templates = Jinja2Templates(directory="templates")  # 设置模板存放目录

@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request, "name": "FastAPI"})

templates/index.html文件内容如下:

<!DOCTYPE html>
<html>
<head>
    <title>FastAPI Example</title>
</head>
<body>
    <h1>Hello, {{ name }}!</h1>
</body>
</html>

访问http://127.0.0.1:8000/时,FastAPI 会渲染模板并返回HTML页面。

注意事项:
1.Jinja2Templates 是从 fastapi.templating 导入的,本质用的还是 Starlette 的功能。
2.模板渲染时 必须 传入 request 对象(即 {“request”: request, …}),否则会报错。
3.对于复杂 UI,可以结合 Bootstrap / Tailwind CSS 来美化页面。

管理显示静态页面

1.字符串渲染HTML

使用HTMLResponse返回HTML内容,代码示例如下:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
# html内容
html_content = """
<!DOCTYPE html>
<html>
<head>
    <title>Static HTML</title>
</head>
<body>
    <h1>Hello, Static HTML!</h1>
</body>
</html>
"""

@app.get("/static", response_class=HTMLResponse)
async def static_html():
    return HTMLResponse(content=html_content)

2.返回单个HTML文件

使用FileResponse返回单个HTML文件内容,代码示例如下:

from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()

@app.get("/static/index")
async def get_static_index():
    return FileResponse("static/index.html")  # 指向你的 HTML 文件路径

访问/static/index就会直接返回static/index.html文件内容。适合少量单独HTML页面。

3.挂载静态目录(推荐)

如果你有很多静态资源(HTML、CSS、JS、图片),可以使用StaticFiles把整个目录挂载为静态文件目录,代码示例如下:

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()

# 挂载整个静态目录
app.mount("/static", StaticFiles(directory="static"), name="static")

访问方式如下:

http://localhost:8000/static/index.html
http://localhost:8000/static/style.css

九、Response Model(响应模型)

可以在任何路径操作中使用参数response_model声明用于响应的模型。

响应模型的定义和使用

定义一个模型,直接返回模型的格式,这种方式会返回请求体的全部内容,代码示例如下:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class UserInfo(BaseModel):
    username: str
    password: str
    email: str
    full_name: str = None


@app.post("/create_user", response_model=UserInfo)
def create_user(*, user_data: UserInfo):
    return user_data

response_modeldecorator方法(get,post等)的参数,该参数接受一个模型类。它接收的类型与您为Pydantic模型属性声明的类型相同,因此它可以是Pydantic模型,但也可以是例如一个Pydantic模型的清单,例如List [Item]
response_model功能: 将输出数据限制为模型的数据,可以将输出数据转换为其类型声明。

返回响应模型部分内容

从上面的情况来看我们在实际运用中,不可能返回全部的内容,比如不会希望密码部分也响应出来,那么我们可以再定义一个模型来定义我们希望返回的部分内容。代码示例如下:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class UserInfo(BaseModel):
    username: str
    password: str
    email: str
    full_name: str = None


class UserReturn(BaseModel):  # 定义模型,希望返回部分内容
    username: str
    email: str


@app.post("/select_user", response_model=UserReturn)  # 绑定返回响应模型
def select_user(*, user_data: UserInfo):
    return user_data

测试返回内容,如下所示:

$ curl -X POST http://127.0.0.1:8080/select_user \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "admin",
    "password": "dhuwhug28736dj",
    "email": "admin@163.com"
}'
# 返回结果如下
{"username":"admin","email":"admin@163.com"}

十、Body-Field模型(原Schema模型)

之前我们都是在绑定函数中使用Query方法去做属性验证,Field模型可以帮助我们在定义请求体模型的时候在模型中去设置默认值和验证属性。
我们需要先导入Field,最后在定义请求体模型中使用Field模型。示例代码如下:

from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()


class Item(BaseModel):
    user_name: str
    user_id: str = Field(..., min_length=6, max_length=10, description="The user_id 10 ≥ length ≥ 6")
    description: str = Field(None, title="The description of the item", max_length=100)
    money: float


@app.post("/items/info")
def items_info(item: Item):
    return item

补充: Field与 QueryPathBody的工作方式相同,具有相同的参数。

十一、Body-Nested Models(嵌套模型)

列表属性模型

将属性值的类型定义为列表,如下所示:

class Information(BaseModel):
    website: str
    white_list: list = []
    url: AnyUrl

如果没有list类型或者可以使用typing下的List来进一步定义列表类型,如下所示:

from typing import List


class Information(BaseModel):
    website: str
    white_list: List[str] = []
    url: AnyUrl

当然,我们还可以将列表的元素类型定义为int或者其它类型。比如别的请求体模型嵌套进列表中,示例代码如下:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class UrlName(BaseModel):
    name: str
    url: AnyUrl


class UrlInfo(BaseModel):
    website: str
    url_id: str
    url_num: int
    url: AnyUrl
    white_list: List[UrlName] = None  # 列表中内嵌UrlName模型


@app.post("/url_info/update")
def url_info(url_data: UrlInfo):
    return url_data

我们应该使用的请求体结构应该是这样,如下所示:

{
    "website": "apple_vlog",
    "url_id": "2iu2b3y3t2dhyut310846md",
    "url_num": 267,
    "url": "https://apple.vlog.cn",
    "white_list": [
        {"name": "baidu", "url": "https://www.baidu.com"},
        {"name": "google", "url": "https://www.google.com"}
    ]
}

深层嵌套模型

根据上面的示例,实际我们可以根据需求嵌套更多层的模型进行使用,示例代码如下:

from fastapi import FastAPI
from pydantic import BaseModel
from pydantic import AnyUrl
from uuid import UUID
from datetime import date
app = FastAPI()


class UrlName(BaseModel):  # 最底层模型
    name: str
    url: AnyUrl


class UrlInfo(BaseModel):  # 第二层模型
    website: str
    url_id: str
    url_num: int
    url: AnyUrl
    white_list: List[UrlName] = None


class Information(BaseModel):  # 第一层模型
    company_name: str
    company_code: UUID
    start_time: date
    status: bool
    url_info: UrlInfo


@app.put("/register")    # 定义接口绑定方法
def register_information(info: Information):
    return info

根据上面的示例代码,我们应该传入的请求体应该有三层,如下所示:

{
    "company_name": "helloworld",
    "company_code": "550e8400-e29b-41d4-a716-446655440000",
    "start_time": "2025-08-01",
    "status": true,
    "url_info": {
        "website": "apple_vlog",
        "url_id": "2iu2b3y3t2dhyut310846md",
        "url_num": 267,
        "url": "https://apple.vlog.cn",
        "white_list": [
            {"name": "baidu", "url": "https://www.baidu.com"},
            {"name": "google", "url": "https://www.google.com"}
        ]
    }
}

总结:所以,我们可以为请求体的属性值定义不同的类型,可以是列表,可以是元组,列表或者元组里面的元素可以是字符串或者是数值,还可以是请求体模型。使用深层嵌套的模型,能够使我们平时需求更加灵活和实用。

十二、Extra data types(额外数据类型)

根据十一点的深层嵌套模型的示例中,我们其实也使用了除了常用的intstrfloat等类型,还有其他很多额外的数据类型。

其他复杂的数据类型:

序号 类型
说明
1 UUID 一个标准的“通用唯一标识符”,在许多数据库和系统中通常作为 ID 使用。在请求和响应中将以 str 表示。
2 datetime.datetime Python 的日期时间类型:datetime.datetime。在请求和响应中,将以 ISO 8601 格式的 str 表示,比如:2008-09-15T15:53:00+05:00
3 datetime.date Python 的日期类型:datetime.date。在请求和响应中,将以 ISO 8601 格式的 str 表示,比如:2008-09-15
4 datetime.time Python 的时间类型:datetime.time。在请求和响应中,将以 ISO 8601 格式的 str 表示,比如:14:23:55.003
5 datetime.timedelta Python 的时间增量类型:datetime.timedelta。在请求和响应中,将以 float 表示总秒数。
6 frozenset 在请求和响应中,格式与 set 相同:在请求中,将读取列表,去重,并转换为集合;在响应中,集合将会被转化为列表。
7 bytes Python 标准类型:bytes。在请求和响应中将被视为 str,生成的 Schema 将指定它是带有 binary 格式的 str
8 Decimal Python 标准类型:Decimal。在请求和响应中,将以 float 格式表示。

其他类型使用示例,代码示例如下:

from fastapi import FastAPI, Body
from uuid import UUID
from datetime import datetime, time, timedelta, date
app = FastAPI()


@app.get("/get_data_type")
def get_data_type(*, user_uuid: UUID,
                  today: date = Body(None),
                  create_time: datetime = Body(None),
                  repeat_at: time = Body(None),
                  process_after: timedelta = Body(None)
                  ):
    return {"user_uuid": user_uuid, "today_date": today,
            "create_time": create_time, "repeat_at": repeat_at,
            "process_after": process_after}