本文最后更新于:2025年8月8日 晚上
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,专为在 Python 中构建 RESTful API 而设计。本文介绍相关内容。
简介
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.8+ 并基于标准的 Python 类型提示。
FastAPI 建立在 Starlette 和 Pydantic 之上,利用类型提示进行数据处理,并自动生成API文档。
FastAPI 于 2018 年 12 月 5 日发布第一版本,以其易用性、速度和稳健性在开发者中间迅速流行起来。
FastAPI 支持异步编程,可在生产环境中运行。
特点
高性能: 基于Starlette和Pydantic,利用异步(asynchronous)编程,提供可与 NodeJS 和 Go 并肩的出色性能。
自动文档生成: 自动生成交互式API文档,支持Swagger UI和ReDoc,让API的理解和测试更加直观。
类型注解支持: 利用Python的类型提示,提供更严格的输入验证和更好的代码提示。
高效编码 :提高功能开发速度约 200% 至 300%。
更少 bug :减少约 40% 的人为(开发者)导致错误。
异步支持: 支持异步请求处理,使得处理IO密集型任务更加高效。
健壮 :生产可用级别的代码。还有自动生成的交互式文档。
标准化 :基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。
适用场景
构建API后端: 用于构建RESTful API,支持前后端分离的Web应用。
微服务架构: 可以作为微服务的后端框架,支持快速开发和部署。
数据处理API: 适用于处理数据,接收和返回JSON数据。
实时通信: 支持WebSocket,适用于实时通信场景。
优势
Pythonic: 使用Python的自然语法和类型提示,降低学习曲线。
性能优越: 利用异步编程和底层的Starlette框架,提供卓越的性能。
文档友好: 自动生成交互式文档,减少文档维护的工作量。
生态系统: 基于Python生态系统,可以方便地集成各种库和工具。
环境准备
安装FastAPI
也可以使用以下命令直接安装 FastAPI 及所有可选依赖:
1 pip install "fastapi[all]"
这会安装:
fastapi
- FastAPI 框架
uvicorn[standard]
- ASGI 服务器
python-multipart
- 表单和文件上传支持
jinja2
- 模板引擎
python-jose
- JWT 令牌支持
passlib
- 密码哈希
bcrypt
- 密码加密
python-dotenv
- 环境变量支持
另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:
或
1 pip install "uvicorn[standard]"
这样我们就安装完成了。
运行第一个 FastAPI 应用
创建一个名为 main.py
的文件,添加以下代码:
1 2 3 4 5 6 from fastapi import FastAPI app = FastAPI()@app.get("/" ) def read_root (): return {"Hello" : "World" }
在命令行中运行以下命令以启动应用:
1 uvicorn main :app --reload
代码解析:
from fastapi import FastAPI
: 这行代码从 fastapi
模块中导入了 FastAPI
类。FastAPI
类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。
app = FastAPI()
:这行代码创建了一个 FastAPI 应用实例。与 Flask 不同,FastAPI 不需要传递 __name__
参数,因为它默认使用当前模块。
@app.get("/")
: 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。
def read_root():
: 这是定义了一个名为 read_root
的函数,它将被调用当用户使用 GET 方法访问根 URL 时。
return {"Hello": "World"}
: 这行代码是 read_root
函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。
VS Code 调试
创建 .vscode/launch.json
文件
添加配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "version" : "0.2.0" , "configurations" : [ { "name" : "Python: FastAPI" , "type" : "python" , "request" : "launch" , "module" : "uvicorn" , "args" : [ "main:app" , "--reload" ] , "jinja" : true , "justMyCode" : true } ] }
按 F5 启动调试器来运行代码。
在弹出的下拉菜单中,选择列表中的 FastAPI 调试配置选项:
交互式 API 文档
FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。
这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。
通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率
在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。
默认情况下,你可以通过访问 http://127.0.0.1:8000/docs 来打开 Swagger UI 风格的文档:
Swagger UI 提供了一个直观的用户界面,用于浏览 API 的各个端点、查看请求和响应的结构,并支持直接在文档中进行 API 请求测试。通过 Swagger UI,你可以轻松理解每个路由操作的输入参数、输出格式和请求示例。
或者通过 http://127.0.0.1:8000/redoc 来打开 ReDoc 风格的文档。
ReDoc 是另一种交互式文档界面,具有清晰简洁的外观。它使得开发者能够以可读性强的方式查看 API 的描述、请求和响应。与 Swagger UI 不同,ReDoc 的设计强调文档的可视化和用户体验。
交互式文档的优势
实时更新: 交互式文档会实时更新,反映出应用代码的最新更改。
自动验证: 输入参数的类型和格式会得到自动验证,降低了错误的可能性。
便于测试: 可以直接在文档中进行 API 请求测试,避免使用其他工具。
路由请求响应
路径参数
通过在路径中添加{}
,可以创建动态路径参数。例如:
1 2 3 @app.get("/items/{item_id}" ) def read_item (item_id: int ): return {"item_id" : item_id}
在这个例子中,{item_id}
是一个路径参数,可以接受整数类型的值。
查询参数
通过为函数参数设置默认值,可以将其作为查询参数。例如:
1 2 3 @app.get("/items/{item_id}" ) def read_item (item_id: int , q: str = None ): return {"item_id" : item_id, "q" : q}
在这个例子中,q
是一个查询参数,可以接受字符串类型的值。
1 2 3 4 5 6 7 8 9 10 11 from fastapi import FastAPI app = FastAPI()@app.get("/" ) def read_root (): return {"Hello" : "World" }@app.get("/items/{item_id}" ) def read_item (item_id: int , q: str = None ): return {"item_id" : item_id, "q" : q}
代码说明:
@app.get("/items/{item_id}")
:定义了一个路由路径,其中 {item_id}
是路径参数,对应于函数参数 item_id
。
def read_item(item_id: int, q: str = None)
:路由处理函数接受一个整数类型的路径参数 item_id
和一个可选的字符串类型查询参数 q
。
在路由操作中,可以使用函数参数声明查询参数。例如,q: str = None 表示 q 是一个可选的字符串类型查询参数,默认值为 None 。
路由处理函数返回一个字典,该字典将被 FastAPI 自动转换为 JSON 格式,并作为响应发送给客户端:
请求数据
在 FastAPI 中,请求(Request)和响应(Response)是与客户端交互的核心。
FastAPI 提供了强大的工具来解析请求数据,并根据需要生成规范的响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 from pydantic import BaseModelfrom fastapi import FastAPI app = FastAPI()class Item (BaseModel ): name: str description: str = None price: float tax: float = None @app.post("/items/" ) def create_item (item: Item ): return item
可以打开 http://127.0.0.1:8000/docs 来进行 POST 测试:
返回 Pydantic 模型
路由处理函数返回一个 Pydantic 模型实例,FastAPI 将自动将其转换为 JSON 格式,并作为响应发送给客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 from pydantic import BaseModelfrom fastapi import FastAPI app = FastAPI()class Item (BaseModel ): name: str description: str = None price: float tax: float = None @app.post("/items/" ) def create_item (item: Item ): return item
POST 请求,返回的数据格式如下所示:
1 2 3 4 5 6 { "name" : "runoob" , "description" : "菜鸟教程 POST 测试" , "price" : 12 , "tax" : 1 }
请求头和 Cookie
使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据。
1 2 3 4 5 6 7 8 from fastapi import Header, Cookiefrom fastapi import FastAPI app = FastAPI()@app.get("/items/" ) def read_item (user_agent: str = Header(None ), session_token: str = Cookie(None ) ): return {"User-Agent" : user_agent, "Session-Token" : session_token}
以上代码在浏览器访问 **http://127.0.0.1:8000/items/**,返回了 JSON 数据:
重定向和状态码
使用 RedirectResponse 实现重定向,将客户端重定向到 /items/ 路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 from fastapi import Header, Cookiefrom fastapi import FastAPIfrom fastapi.responses import RedirectResponse app = FastAPI()@app.get("/items/" ) def read_item (user_agent: str = Header(None ), session_token: str = Cookie(None ) ): return {"User-Agent" : user_agent, "Session-Token" : session_token}@app.get("/redirect" ) def redirect (): return RedirectResponse(url="/items/" )
以上代码在浏览器访问 http://127.0.0.1:8000/redirect/ 会自动跳转到 http://127.0.0.1:8000/items/ 页面:
使用 HTTPException 抛出异常,返回自定义的状态码和详细信息。
以下实例在 item_id 为 42 会返回 404 状态码:
1 2 3 4 5 6 7 8 9 from fastapi import HTTPException app = FastAPI()@app.get("/items/{item_id}" ) def read_item (item_id: int ): if item_id == 42 : raise HTTPException(status_code=404 , detail="Item not found" ) return {"item_id" : item_id}
以上代码在浏览器访问 http://127.0.0.1:8000/items/42/ 页面显示如下:
自定义响应头
使用 JSONResponse 自定义响应头:
1 2 3 4 5 6 7 8 9 10 from fastapi import FastAPIfrom fastapi.responses import JSONResponse app = FastAPI()@app.get("/items/{item_id}" ) def read_item (item_id: int ): content = {"item_id" : item_id} headers = {"X-Custom-Header" : "custom-header-value" } return JSONResponse(content=content, headers=headers)
以上代码在浏览器访问 http://127.0.0.1:8000/items/42/ 页面显示如下,可以看到我们自定义的响应头:
路由分组
通过使用APIRouter
,可以将路由分组到不同的模块。例如,我们可以创建一个items.py
文件,其中包含所有与items相关的路由:
1 2 3 4 5 6 7 from fastapi import APIRouter router = APIRouter()@router.get("/{item_id}" , response_model=Item ) def read_item (item_id: int ): return items[item_id]
然后,在main.py
中使用 include_router
导入并挂载这个路由:
1 2 3 4 5 6 from fastapi import FastAPIfrom . import items app = FastAPI() app.include_router(items.router, prefix="/items" )
这样,我们就可以更好地组织代码和路由。
CRUD API 示例
数据模型
首先,定义Item数据模型:
1 2 3 4 5 6 7 from pydantic import BaseModelclass Item (BaseModel ): name: str description: str = None price: float tax: float = None
在这个示例中,我们将使用一个简单的字典来存储数据:
创建(Create)
创建一个新的item:
1 2 3 4 5 @app.post("/items/" , response_model=Item ) def create_item (item: Item ): item_id = len (items) + 1 items[item_id] = item return item
读取(Read)
读取一个item:
1 2 3 @app.get("/items/{item_id}" , response_model=Item ) def read_item (item_id: int ): return items[item_id]
读取所有items:
1 2 3 @app.get("/items/" , response_model=List [Item] ) def read_items (): return list (items.values())
更新(Update)
更新一个item:
1 2 3 4 @app.put("/items/{item_id}" , response_model=Item ) def update_item (item_id: int , item: Item ): items[item_id] = item return item
删除(Delete)
删除一个item:
1 2 3 4 @app.delete("/items/{item_id}" , response_model=Item ) def delete_item (item_id: int ): item = items.pop(item_id) return item
通过这个例子,你可以了解到FastAPI如何简便地实现API的创建、读取、更新和删除操作。
Pydantic 模型
Pydantic 是一个用于数据验证和序列化的 Python 模型库。
它在 FastAPI 中广泛使用,用于定义请求体、响应体和其他数据模型,提供了强大的类型检查和自动文档生成功能。
以下是关于 Pydantic 模型的详细介绍:
定义 Pydantic 模型
使用 Pydantic 定义一个模型非常简单,只需创建一个继承自 pydantic.BaseModel 的类,并在其中定义字段。字段的类型可以是任何有效的 Python 类型,也可以是 Pydantic 内置的类型。
1 2 3 4 5 6 7 from pydantic import BaseModelclass Item (BaseModel ): name: str description: str = None price: float tax: float = None
以上代码中中,我们定义了一个名为 Item 的 Pydantic 模型,包含了四个字段:name、description、price 和 tax,name 和 price 是必需的字段,而 description 和 tax 是可选的字段,其默认值为 None。
使用 Pydantic 模型
请求体验证
在 FastAPI 中,可以将 Pydantic 模型用作请求体(Request Body),以自动验证和解析客户端发送的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from fastapi import FastAPIfrom pydantic import BaseModel app = FastAPI()class Item (BaseModel ): name: str description: str = None price: float tax: float = None @app.post("/items/" ) def create_item (item: Item ): return item
以上代码中中,create_item 路由处理函数接受一个名为 item 的参数,其类型是 Item 模型。FastAPI 将自动验证传入的 JSON 数据是否符合模型的定义,并将其转换为 Item 类型的实例。
查询参数验证
Pydantic 模型还可以用于验证查询参数、路径参数等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from fastapi import FastAPI, Queryfrom pydantic import BaseModel app = FastAPI()class Item (BaseModel ): name: str description: str = None price: float tax: float = None @app.get("/items/" ) def read_item (item: Item, q: str = Query(..., max_length=10 ) ): return {"item" : item, "q" : q}
以上代码中,read_item 路由处理函数接受一个 Item 模型的实例作为查询参数,以及一个名为 q 的字符串查询参数。通过使用 Query 函数,我们还可以为查询参数指定更多的验证规则,如最大长度限制。
自动文档生成
使用 Pydantic 模型的一个重要优势是,它能够自动为 FastAPI 生成交互式 API 文档。文档会包括模型的字段、类型、验证规则等信息,让开发者和 API 使用者能够清晰地了解如何正确使用 API。
打开 **http://127.0.0.1:8000/docs**,API 文档显示如下:
数据转换和序列化
Pydantic 模型不仅提供了验证功能,还可以用于将数据转换为特定类型(例如 JSON)或反向序列化。在 FastAPI 中,这种能力是自动的,你无需手动处理。
通过使用 Pydantic 模型,你可以更轻松地定义和验证数据,使得代码更清晰、更健壮,并通过自动生成的文档提供更好的 API 交互体验。
接下来我们可以打开 http://127.0.0.1:8000/docs 来进行 POST 测试:
填写请求参数:
FastAPI 路径操作依赖项
FastAPI 提供了简单易用,但功能强大的依赖注入系统,这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 FastAPI。
FastAPI 提供了路径操作依赖项(Path Operation Dependencies)的机制,允许你在路由处理函数执行之前或之后运行一些额外的逻辑。
依赖项就是一个函数,且可以使用与路径操作函数相同的参数。
路径操作依赖项提供了一种灵活的方式来组织代码、验证输入、进行身份验证等。
依赖项(Dependencies)
依赖项是在路由操作函数执行前或后运行的可复用的函数或对象。
它们被用于执行一些通用的逻辑,如验证、身份验证、数据库连接等。在 FastAPI 中,依赖项通常用于两个方面:
预处理(Before)依赖项: 在路由操作函数执行前运行,用于预处理输入数据,验证请求等。
后处理(After)依赖项: 在路由操作函数执行后运行,用于执行一些后处理逻辑,如日志记录、清理等。
依赖注入
依赖注入是将依赖项注入到路由操作函数中的过程。
在 FastAPI 中,通过在路由操作函数参数中声明依赖项来实现依赖注入。
FastAPI 将负责解析依赖项的参数,并确保在执行路由操作函数之前将其传递给函数。
例如,我们可以创建一个get_db
函数来获取数据库连接:
1 2 3 def get_db (): db = ... return db
然后,在路由中使用Depends
将其注入:
1 2 3 4 5 from fastapi import Depends@app.get("/items/{item_id}" , response_model=Item ) def read_item (item_id: int , db=Depends(get_db ) ): return db.get(item_id)
依赖项的使用
定义依赖项:
1 2 3 4 5 6 7 from fastapi import Depends, FastAPI app = FastAPI()def common_parameters (q: str = None , skip: int = 0 , limit: int = 100 ): return {"q" : q, "skip" : skip, "limit" : limit}
在这个例子中,common_parameters 是一个依赖项函数,用于预处理查询参数。
在路由中使用依赖项:
1 2 3 4 5 6 from fastapi import Depends@app.get("/items/" ) async def read_items (commons: dict = Depends(common_parameters ) ): return commons
在这个例子中,read_items 路由操作函数中的参数 commons 使用了 Depends(common_parameters),表示 common_parameters 是一个依赖项。FastAPI 将在执行路由操作函数之前运行 common_parameters 函数,并将其返回的结果传递给 read_items 函数。
路径操作依赖项的基本使用
预处理(Before)
以下实例中,common_parameters 是一个依赖项函数,它接受查询参数 q、skip 和 limit ,并返回一个包含这些参数的字典。
在路由操作函数 read_items 中,通过传入 Depends(common_parameters),我们使用了这个依赖项函数,实现了在路由执行前预处理输入数据的功能。
1 2 3 4 5 6 7 8 9 10 11 12 from fastapi import Depends, FastAPI, HTTPException app = FastAPI()def common_parameters (q: str = None , skip: int = 0 , limit: int = 100 ): return {"q" : q, "skip" : skip, "limit" : limit}@app.get("/items/" ) async def read_items (commons: dict = Depends(common_parameters ) ): return commons
后处理(After)
以下例子中,after_request 是一个后处理函数,用于在路由执行后执行一些逻辑。
在路由操作函数 read_items_after 中,通过传入 Depends(after_request),我们使用了这个后处理依赖项,实现了在路由执行后进行额外操作的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from fastapi import Depends, FastAPI, HTTPException app = FastAPI()def common_parameters (q: str = None , skip: int = 0 , limit: int = 100 ): return {"q" : q, "skip" : skip, "limit" : limit}@app.get("/items/" ) async def read_items (commons: dict = Depends(common_parameters ) ): return commonsasync def after_request (): pass @app.get("/items/" , response_model=dict ) async def read_items_after (request: dict = Depends(after_request ) ): return {"message" : "Items returned successfully" }
多个依赖项的组合
以下例子中,common_parameters 和 verify_token 是两个不同的依赖项函数,verify_token 依赖于 common_parameters ,这种组合依赖项的方式允许我们在路由执行前先验证一些参数,然后在进行身份验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from fastapi import Depends, FastAPI, HTTPException app = FastAPI()def common_parameters (q: str = None , skip: int = 0 , limit: int = 100 ): return {"q" : q, "skip" : skip, "limit" : limit}def verify_token (token: str = Depends(common_parameters ) ): if token is None : raise HTTPException(status_code=400 , detail="Token required" ) return token@app.get("/items/" ) async def read_items (token: dict = Depends(verify_token ) ): return token
异步依赖项
依赖项函数和后处理函数可以是异步的,允许在它们内部执行异步操作。
以下例子中,get_token 是一个异步的依赖项函数,模拟了一个异步操作。
在路由操作函数 read_items 中,我们使用了这个异步依赖项函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from fastapi import Depends, FastAPI, HTTPExceptionfrom typing import Optional import asyncio app = FastAPI()async def get_token (): await asyncio.sleep(2 ) return "fake-token" @app.get("/items/" ) async def read_items (token: Optional [str ] = Depends(get_token ) ): return {"token" : token}
通过使用路径操作依赖项,你可以在路由执行前或后执行额外的逻辑,从而实现更灵活、可组合的代码组织方式。
表单数据
在 FastAPI 中,接收表单数据是一种常见的操作,通常用于处理用户通过 HTML 表单提交的数据。
FastAPI 提供了 Form 类型,可以用于声明和验证表单数据。
声明表单数据模型
接下来我们设计一个接收一个登陆的表单数据,要使用表单,需预先安装 python-multipart:
1 pip install python-multipart
示例代码
1 2 3 4 5 6 7 8 from fastapi import FastAPI, Form app = FastAPI()@app.post("/login/" ) async def login (username: str = Form( ), password: str = Form( ) ): return {"username" : username}
接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:
使用 Pydantic 模型来声明表单数据模型。
在模型中,使用 Field 类型声明每个表单字段,并添加必要的验证规则。
1 2 3 4 5 6 from pydantic import BaseModel, Fieldclass Item (BaseModel ): name: str = Field(..., title="Item Name" , max_length=100 ) description: str = Field(None , title="Item Description" , max_length=255 ) price: float = Field(..., title="Item Price" , gt=0 )
以上例子中,Item 是一个 Pydantic 模型,用于表示表单数据。
模型中的字段 name、description 和 price 分别对应表单中的不同输入项,并设置了相应的验证规则。
除了可以在 API 文档中测验,另外我们也可以自己创建 html 来测试:
1 2 3 4 5 6 7 8 9 10 11 12 <form action ="http://localhost:8000/items/" method ="post" > <label for ="name" > Name:</label > <input type ="text" id ="name" name ="name" required > <br > <label for ="description" > Description:</label > <textarea id ="description" name ="description" > </textarea > <br > <label for ="price" > Price:</label > <input type ="number" id ="price" name ="price" required min ="0" > <br > <button type ="submit" > Submit</button > </form >
在路由中接收表单数据
在路由操作函数中,可以使用 Form 类型来接收表单数据。
Form 类型的参数可以与 Pydantic 模型的字段一一对应,以实现表单数据的验证和转换。
1 2 3 4 5 6 7 8 9 10 11 12 from fastapi import FastAPI, Form app = FastAPI()@app.post("/items/" ) async def create_item ( name: str = Form(... ), description: str = Form(None ), price: float = Form(..., gt=0 ), ): return {"name" : name, "description" : description, "price" : price}
以上例子中,create_item 路由操作函数接收了三个表单字段:name、description 和 price,这些字段与 Item 模型的相应字段一致,FastAPI 将自动根据验证规则验证表单数据。
接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:
表单数据的验证和文档生成
使用 Pydantic 模型和 Form 类型,表单数据的验证和文档生成都是自动的。
FastAPI 将根据模型中的字段信息生成交互式 API 文档,并根据验证规则进行数据验证。
API 文档地址 http://127.0.0.1:8000/docs 。
处理文件上传
如果表单包含文件上传,可以使用 UploadFile 类型处理。
以下是一个处理文件上传的实例:
1 2 3 4 5 6 7 8 from fastapi import FastAPI, File, UploadFile app = FastAPI()@app.post("/files/" ) async def create_file (file: UploadFile = File(... ) ): return {"filename" : file.filename}
在这个例子中,create_file 路由操作函数接收了一个 UploadFile 类型的文件参数。
FastAPI 将负责处理文件上传,并将文件的相关信息包装在 UploadFile 对象中,可以轻松地获取文件名、内容类型等信息。
通过上述方式,FastAPI 提供了一种简单而强大的方法来接收和处理表单数据,同时保持了代码的清晰性和可维护性。
核心概念
ASGI 与异步编程
ASGI(Asynchronous Server Gateway Interface)是 Python 异步 Web 服务器和应用程序之间的标准接口。
WSGI vs ASGI 对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def wsgi_app (environ, start_response ): status = '200 OK' response_headers = [('Content-type' , 'text/plain' )] start_response(status, response_headers) return [b'Hello World' ]async def asgi_app (scope, receive, send ): await send({ 'type' : 'http.response.start' , 'status' : 200 , 'headers' : [(b'content-type' , b'text/plain' )], }) await send({ 'type' : 'http.response.body' , 'body' : b'Hello World' , })
异步编程核心概念
同步 vs 异步执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import asyncioimport timeimport httpxdef sync_fetch_data (): start_time = time.time() time.sleep(1 ) time.sleep(1 ) time.sleep(1 ) print (f"同步执行耗时: {time.time() - start_time:.2 f} 秒" ) async def async_fetch_data (): start_time = time.time() await asyncio.gather( asyncio.sleep(1 ), asyncio.sleep(1 ), asyncio.sleep(1 ), ) print (f"异步执行耗时: {time.time() - start_time:.2 f} 秒" ) sync_fetch_data() asyncio.run(async_fetch_data())
FastAPI 中的异步路径操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from fastapi import FastAPIimport asyncio app = FastAPI()@app.get("/sync" ) def sync_endpoint (): time.sleep(2 ) return {"message" : "同步响应" }@app.get("/async" ) async def async_endpoint (): await asyncio.sleep(2 ) return {"message" : "异步响应" }@app.get("/users/{user_id}" ) async def get_user (user_id: int ): user = await database.fetch_one( "SELECT * FROM users WHERE id = :user_id" , {"user_id" : user_id} ) return user
适合异步的场景:
数据库操作
网络请求(API 调用)
文件 I/O 操作
长时间等待的操作
不适合异步的场景:
CPU 密集型计算
简单的数据处理
没有 I/O 等待的操作
1 2 3 4 5 6 7 8 9 10 11 12 @app.get("/weather" ) async def get_weather (): async with httpx.AsyncClient() as client: response = await client.get("https://api.weather.com/data" ) return response.json()@app.get("/calculate" ) def calculate_sync (): result = sum (range (1000000 )) return {"result" : result}
REST API 设计原则
REST(Representational State Transfer)是一种 Web API 设计风格,强调:
资源导向 :URL 表示资源
状态无关 :每个请求都是独立的
统一接口 :使用标准 HTTP 方法
分层系统 :支持缓存、负载均衡等
良好的 URL 设计示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from fastapi import FastAPI, HTTPException, statusfrom pydantic import BaseModelfrom typing import List , Optional app = FastAPI()@app.get("/users" ) async def get_users (): return users_db@app.get("/users/{user_id}" ) async def get_user (user_id: int ): return find_user(user_id)@app.post("/users" ) async def create_user (user: UserCreate ): return create_new_user(user)@app.put("/users/{user_id}" ) async def update_user (user_id: int , user: UserUpdate ): return update_existing_user(user_id, user)@app.patch("/users/{user_id}" ) async def patch_user (user_id: int , user: UserPatch ): return patch_existing_user(user_id, user)@app.delete("/users/{user_id}" ) async def delete_user (user_id: int ): return delete_existing_user(user_id)@app.get("/users/{user_id}/posts" ) async def get_user_posts (user_id: int ): return get_posts_by_user(user_id)@app.post("/users/{user_id}/posts" ) async def create_user_post (user_id: int , post: PostCreate ): return create_post_for_user(user_id, post)
1 2 3 4 5 6 7 8 9 10 11 GET /api/v1/users GET /api/v1/users/123 POST /api/v1/users GET /api/v1/users/123/orders GET /api/v1/getAllUsers GET /api/v1/user/123 POST /api/v1/createUser GET /api/v1/users-orders-123
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class User (BaseModel ): id : int name: str email: str class Post (BaseModel ): id : int title: str content: str author_id: int @app.get("/users" ) async def list_users () -> List [User]: return users@app.get("/posts" ) async def list_posts () -> List [Post]: return posts@app.get("/users/{user_id}/posts" ) async def get_user_posts (user_id: int ) -> List [Post]: """获取特定用户的所有文章""" return [post for post in posts if post.author_id == user_id]@app.get("/posts/{post_id}/author" ) async def get_post_author (post_id: int ) -> User: """获取文章的作者信息""" post = find_post(post_id) if not post: raise HTTPException(status_code=404 , detail="Post not found" ) return find_user(post.author_id)
HTTP 方法与状态码
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 from fastapi import status@app.post("/users" , status_code=status.HTTP_201_CREATED ) @app.get("/users" ) @app.delete("/users/{user_id}" , status_code=status.HTTP_204_NO_CONTENT ) @app.get("/users/{user_id}" ) async def get_user (user_id: int ): if user_id < 1 : raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="User ID must be positive" ) user = find_user(user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return user@app.post("/users/{user_id}/follow" ) async def follow_user (user_id: int , current_user_id: int ): if user_id == current_user_id: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Cannot follow yourself" ) if is_already_following(current_user_id, user_id): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Already following this user" )@app.exception_handler(Exception ) async def general_exception_handler (request, exc ): return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"detail" : "Internal server error" } )
常用状态码速查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 200 OK 201 Created 204 No Content 206 Partial Content 301 Moved Permanently 302 Found 304 Not Modified 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 405 Method Not Allowed 409 Conflict 422 Unprocessable Entity 429 Too Many Requests 500 Internal Server Error 502 Bad Gateway 503 Service Unavailable
SON 数据格式
JSON 基础与最佳实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from datetime import datetimefrom decimal import Decimalfrom typing import Optional , List from pydantic import BaseModel, Fieldimport jsonclass UserResponse (BaseModel ): id : int username: str email: str full_name: Optional [str ] = None is_active: bool = True created_at: datetime updated_at: Optional [datetime] = None class Config : allow_population_by_field_name = True json_encoders = { datetime: lambda v: v.isoformat(), Decimal: lambda v: float (v) }class APIResponse (BaseModel ): """标准 API 响应格式""" success: bool = True message: str = "操作成功" data: Optional [dict ] = None errors: Optional [List [str ]] = None timestamp: datetime = Field(default_factory=datetime.now)class PaginatedResponse (BaseModel ): items: List [dict ] total: int page: int size: int pages: int @app.get("/users" , response_model=PaginatedResponse ) async def get_users (page: int = 1 , size: int = 10 ): """返回分页的用户列表""" users = get_users_paginated(page, size) total = count_users() return PaginatedResponse( items=users, total=total, page=page, size=size, pages=(total + size - 1 ) // size )
JSON Schema 与数据验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 from pydantic import BaseModel, validator, Fieldfrom typing import Optional , List from enum import Enumclass UserRole (str , Enum): ADMIN = "admin" USER = "user" MODERATOR = "moderator" class UserCreate (BaseModel ): username: str = Field(..., min_length=3 , max_length=50 , description="用户名" ) email: str = Field(..., regex=r'^[\w\.-]+@[\w\.-]+\.\w+$' , description="邮箱地址" ) password: str = Field(..., min_length=8 , description="密码" ) full_name: Optional [str ] = Field(None , max_length=100 , description="全名" ) role: UserRole = Field(UserRole.USER, description="用户角色" ) age: Optional [int ] = Field(None , ge=0 , le=150 , description="年龄" ) @validator('username' ) def username_must_be_alphanumeric (cls, v ): if not v.isalnum(): raise ValueError('用户名只能包含字母和数字' ) return v @validator('password' ) def password_strength (cls, v ): if not any (c.isupper() for c in v): raise ValueError('密码必须包含至少一个大写字母' ) if not any (c.islower() for c in v): raise ValueError('密码必须包含至少一个小写字母' ) if not any (c.isdigit() for c in v): raise ValueError('密码必须包含至少一个数字' ) return v class Config : schema_extra = { "example" : { "username" : "johndoe" , "email" : "john@example.com" , "password" : "SecurePass123" , "full_name" : "John Doe" , "role" : "user" , "age" : 30 } }@app.post("/users" , response_model=UserResponse ) async def create_user (user: UserCreate ): """创建新用户,包含数据验证""" return await create_new_user(user)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class ErrorDetail (BaseModel ): field: str message: str code: str class ErrorResponse (BaseModel ): error: str message: str details: Optional [List [ErrorDetail]] = None timestamp: datetime = Field(default_factory=datetime.now)from fastapi import Requestfrom fastapi.exceptions import RequestValidationErrorfrom starlette.exceptions import HTTPException as StarletteHTTPException@app.exception_handler(RequestValidationError ) async def validation_exception_handler (request: Request, exc: RequestValidationError ): errors = [] for error in exc.errors(): errors.append(ErrorDetail( field='.' .join(str (x) for x in error['loc' ]), message=error['msg' ], code=error['type' ] )) return JSONResponse( status_code=422 , content=ErrorResponse( error="Validation Error" , message="请求数据验证失败" , details=errors ).dict () )@app.exception_handler(StarletteHTTPException ) async def http_exception_handler (request: Request, exc: StarletteHTTPException ): return JSONResponse( status_code=exc.status_code, content=ErrorResponse( error=f"HTTP {exc.status_code} " , message=exc.detail ).dict () )
特有概念
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 from fastapi import FastAPI, Query, Path, Bodyfrom fastapi.openapi.utils import get_openapi app = FastAPI( title="我的 API" , description="这是一个示例 API,展示 FastAPI 的功能" , version="1.0.0" , terms_of_service="http://example.com/terms/" , contact={ "name" : "开发者" , "url" : "http://example.com/contact/" , "email" : "developer@example.com" , }, license_info={ "name" : "MIT" , "url" : "https://opensource.org/licenses/MIT" , }, )@app.get( "/users/{user_id}" , summary="获取用户信息" , description="根据用户 ID 获取用户的详细信息" , response_description="用户信息对象" , tags=["用户管理" ] )async def get_user ( user_id: int = Path(..., title="用户ID" , description="要获取的用户ID" , ge=1 ), include_posts: bool = Query(False , title="包含文章" , description="是否包含用户的文章列表" ) ): """ 获取用户信息: - **user_id**: 用户的唯一标识符 - **include_posts**: 是否在响应中包含用户的文章列表 返回用户的基本信息,如果 include_posts 为 True,还会包含文章列表。 """ user = find_user(user_id) if include_posts: user.posts = get_user_posts(user_id) return userdef custom_openapi (): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title="自定义 API 文档" , version="2.5.0" , description="这是自定义的 OpenAPI schema" , routes=app.routes, ) openapi_schema["info" ]["x-logo" ] = { "url" : "https://example.com/logo.png" } app.openapi_schema = openapi_schema return app.openapi_schema app.openapi = custom_openapi
依赖注入系统预览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from fastapi import Dependsdef get_current_user_id () -> int : return 123 def get_database_session (): db = DatabaseSession() try : yield db finally : db.close()@app.get("/profile" ) async def get_profile ( user_id: int = Depends(get_current_user_id ), db = Depends(get_database_session ) ): return get_user_profile(db, user_id)def get_current_user ( user_id: int = Depends(get_current_user_id ), db = Depends(get_database_session ) ): return db.query(User).filter (User.id == user_id).first()@app.get("/dashboard" ) async def get_dashboard ( current_user: User = Depends(get_current_user ) ): return generate_dashboard(current_user)
类型提示的重要性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from typing import List , Optional , Union , Dict , Any from pydantic import BaseModel@app.get("/items/{item_id}" ) async def get_item ( item_id: int , q: Optional [str ] = None , limit: int = 10 ) -> Dict [str , Any ]: """类型提示告诉 FastAPI 如何处理参数和响应""" return {"item_id" : item_id, "q" : q, "limit" : limit}@app.post("/items/" ) async def create_items ( items: List [ItemCreate] ) -> List [ItemResponse]: return [create_item(item) for item in items]@app.get("/search" ) async def search ( q: str , result_type: str = "json" ) -> Union [List [Dict ], str ]: if result_type == "json" : return [{"title" : "结果1" }, {"title" : "结果2" }] else : return "结果1, 结果2"
综合示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 from fastapi import FastAPI, HTTPException, status, Depends, Queryfrom pydantic import BaseModel, Fieldfrom typing import List , Optional from datetime import datetimeimport asyncio app = FastAPI( title="博客 API" , description="展示 FastAPI 核心概念的博客系统" , version="1.0.0" )class PostBase (BaseModel ): title: str = Field(..., min_length=1 , max_length=200 ) content: str = Field(..., min_length=1 ) published: bool = Field(True )class PostCreate (PostBase ): pass class PostResponse (PostBase ): id : int author_id: int created_at: datetime updated_at: Optional [datetime] = None class Config : orm_mode = True posts_db = [] next_id = 1 async def get_current_user () -> int : return 1 @app.get("/posts" , response_model=List [PostResponse], tags=["文章" ] ) async def list_posts ( skip: int = Query(0 , ge=0 , description="跳过的文章数" ), limit: int = Query(10 , ge=1 , le=100 , description="返回的文章数" ), published_only: bool = Query(True , description="只返回已发布的文章" ) ): """ 获取文章列表 支持分页和筛选功能 """ await asyncio.sleep(0.1 ) filtered_posts = posts_db if published_only: filtered_posts = [p for p in posts_db if p["published" ]] return filtered_posts[skip:skip + limit]@app.post("/posts" , response_model=PostResponse, status_code=status.HTTP_201_CREATED, tags=["文章" ] ) async def create_post ( post: PostCreate, current_user_id: int = Depends(get_current_user ) ): """ 创建新文章 需要用户认证 """ global next_id await asyncio.sleep(0.1 ) new_post = { "id" : next_id, "title" : post.title, "content" : post.content, "published" : post.published, "author_id" : current_user_id, "created_at" : datetime.now(), "updated_at" : None } posts_db.append(new_post) next_id += 1 return new_post@app.get("/posts/{post_id}" , response_model=PostResponse, tags=["文章" ] ) async def get_post (post_id: int ): """ 获取特定文章 根据文章 ID 返回文章详情 """ await asyncio.sleep(0.1 ) post = next ((p for p in posts_db if p["id" ] == post_id), None ) if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Post with id {post_id} not found" ) return post@app.get("/health" , tags=["系统" ] ) async def health_check (): """系统健康检查""" return { "status" : "healthy" , "timestamp" : datetime.now(), "posts_count" : len (posts_db) }if __name__ == "__main__" : import uvicorn uvicorn.run(app, host="0.0.0.0" , port=8000 )
项目结构建议
对于大型项目,推荐以下结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 my_fastapi_project/ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── api/ │ │ ├── __init__.py │ │ ├── items.py │ │ └── users.py │ ├── models/ │ │ ├── __init__.py │ │ └── user .py │ ├── schemas/ │ │ ├── __init__.py │ │ └── user .py │ └── db/ │ ├── __init__.py │ └── session.py ├── tests/ │ ├── __init__.py │ └── test_api.py ├── requirements.txt └── .env
单元测试
使用 pytest
:
1 2 3 4 5 6 7 8 9 10 from fastapi.testclient import TestClientfrom main import app client = TestClient(app)def test_read_root (): response = client.get("/" ) assert response.status_code == 200 assert response.json() == {"message" : "Hello, FastAPI!" }
运行测试:
部署准备
生产服务器
推荐使用:
Uvicorn + Gunicorn
Hypercorn
Daphne
Docker 部署
创建 Dockerfile
:
1 2 3 4 5 6 7 8 9 10 FROM python:3.9 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn" , "app.main:app" , "--host" , "0.0.0.0" , "--port" , "80" ]
构建并运行:
1 2 docker build -t fastapi-app . docker run -d -p 80:80 fastapi-app
本地部署
命令:
1 uvicorn main :app --host 0.0 .0.0 --port 8000
main:启动服务的py文件名
app:服务对象名
–host:IP地址
–port:端口
本地部署后,可通过外部访问本地启动服务。
参考资料
文章链接:
https://www.zywvvd.com/notes/coding/python/python-fastapi/python-fastapi/