官方文档:学习 - FastAPI
fastapi,一个用于构建 API 的现代、快速(高性能)的web框架。
fastapi是建立在Starlette和Pydantic基础上的,Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包,是构建高性能Asyncio服务的理性选择。
一、http请求协议格式
URL(Uniform Resource Locator,统一资源定位符) 是互联网上用于唯一标识和定位资源的地址。简单来说,URL 就是我们常说的“网址”,用于访问网页、文件、图片、视频等网络资源。
一个完整的URL包括:协议、ip、端口、路径、参数
例如: 百度安全验证https://www.baidu.com/s?wd=yuan 其中https是协议,www.baidu.com 是IP,端口默认80,/s是路径,参数是wd=yuan
请求方式: get与post请求
- GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456;提交的数据大小有限制(因为浏览器对URL的长度有限制)
- POST方法是把提交的数据放在HTTP包的请求体中;提交的数据没有限制
响应状态码:是当客户端向服务器端发送请求时, 返回的请求结果。状态码如200 OK,以3位数字和原因组成。
二、fastapi_quickstart
pip install fastapi uvicorn
uvicorn v0.21.0(高版本开发环境时重启有bug)
- FastAPI-Web 框架:用于编写 API 的代码逻辑,处理路由、参数校验、序列化、身份认证等。
- Uvicorn-ASGI 服务器:负责运行 FastAPI 应用,处理 HTTP 请求与响应,支持异步和高并发。
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/") # 路径操作装饰器
async def home(): # 路径操作函数
return {"user_id": 123}
# 路径操作装饰器参数可选,主要用于注解
@app.post("/shop",
tags=["shop接口"],
summary="this is summary",
description="this is description",
response_description="this is response_description",
deprecated=False, # 是否废弃
)
def shop():
return {"shop_id": "123"}
if __name__ == '__main__':
uvicorn.run("quickstart:app", host="127.0.0.1", port=8080, reload=True)
# 终端运行
# uvicorn quickstart:app --host 127.0.0.1 --port 8080 --reload
http://127.0.0.1:8080/docs 可查看 fastapi 自动生成的交互式 API 文档
三、路径操作
3.1 路径操作装饰器
fastapi支持各种请求方式:
3.2 路由分发 include_router
app01.utils.shop
app02.utils.user
main.py
http://127.0.0.1:8080/docs 查看fastapi接口文档
四、请求与相应
路径参数、查询参数、请求体参数
4.1 路径参数Path(⭐)
(1)基本用法
以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量":
@app.get("/user/{user_id}")
def get_user(user_id):
print(user_id, type(user_id))
return {"user_id": user_id}
路径参数 user_id
的值将作为参数 user_id
传递给你的函数
(2)有类型的路径参数
你可以使用标准的 Python 类型标注为函数中的路径参数声明类型。
@app.get("/user/{user_id}")
def get_user(user_id: int):
print(user_id, type(user_id))
return {"user_id": user_id}
在这个例子中,user_id
被声明为 int
类型。(这将为你的函数提供编辑器支持,包括错误检查、代码补全等等。)
(3)注意顺序
在创建路径操作时,你会发现有些情况下路径是固定的。
比如 /users/me,我们假设它用来获取关于当前用户的数据.
然后,你还可以使用路径 /user/{username} 来通过用户名 获取关于特定用户的数据。
由于路径操作是按顺序依次运行的,你需要确保路径 /user/me 声明在路径 user/{username}之前:
@app.get("/user/me")
async def read_user_me():
return {"username": "the current user"}
@app.get("/user/{username}")
async def read_user(username: str):
return {"username": username}
否则,/user/{username} 的路径还将与 /user/me 相匹配,"认为"自己正在接收一个值为 "me" 的 username 参数。
(4)枚举型路径参数
from enum import Enum
from fastapi import FastAPI
class cust_level(str, Enum):
level1 = '钻石'
level2 = '私人银行'
level3 = '金葵花'
level4 = '金卡'
level5 = '普卡'
app = FastAPI()
@app.get('/customer_level/{level}')
def read_enum(level: cust_level):
return {'level_res': level}
4.2 查询参数Query(⭐)
(1)简介
路径函数中声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数,就是 url? 问号之后用&分割的 key-value 键值对。
示例:
http://127.0.0.1:8000/items/?page=1&limit=10
该请求的查询参数有两个,page和limit,值分别为1,10。
@app.get("/jobs/{kd}")
async def search_jobs(kd: str, city: Union[str, None] = None, xl: Optional[str] = None): # 有默认值即可选,否则必选
if city or xl:
return {"kd": kd, "city": city, "xl": xl}
return {"kd": kd}
在这个例子中,函数参数 city
和xl
是可选的,并且默认值为 None
。
- Union 是当有多种可能的数据类型时使用,比如函数有可能根据不同情况有时返回str或返回list,那么就可以写成Union[list, str]
- Optional 是Union的一个简化, 当 数据类型中有可能是None时,比如有可能是str也有可能是None,则Optional[str], 相当于Union[str, None]
(2)设置参数默认值
@app.get("/list/query1")
def list_query1(page: int = 1, limit: int = 10):
return {"page": page, "limit": limit}
ip:port/list/query?page=1&limit=3
ip:port/list/query # 使用默认值 page=1,limit=10
ip:port/list/query?page=&limit= # 报错
(3)设置为可选参数
声明可选的查询参数,只需要将他们的默认值设置为None即可。
@app.get("/list/query2")
def list_query2(page:int = None):
return {"page": page}
ip:port/list/test
ip:port/list/test?page=3
(4)设置为枚举类型参数
from fastapi import APIRouter,Query
from enum import Enum
//自定义枚举类
class Cust_level(str, Enum):
Level1 = '钻石'
Level2 = '私人银行'
Level3 = '金葵花'
Level4 = '金卡'
Level5 = '普卡'
app03 = APIRouter()
@app03.get("/list/query4")
def list_query4(name:str = Query(...,alias='姓名'),
level :Cust_level = Cust_level.Level1 ):
return{"name":name,"level":level}
(5)Query简单介绍
Query库,可以提供对查询参数进行额外校验的功能。
def Query( # noqa: N802
default: Any = Undefined, //参数类型,传...表示为必传,传None表示为可选
*,
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, //正则表达式校验
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
**extra: Any,
) -> Any:
q: str = Query(None, max_length=50) # 可选
q: str = Query(... , max_length=50) # 必选
from fastapi import FastAPI, Query
app = FastAPI()
@app.get('/items/')
def read_items(
q: str = Query(None, min_length=1, max_length=20, regex='^str$') ):
results = {'items': [{'item_id': 'Foo'}, {'item_id': 'Bar'}]}
if q:
results.update({'q': q})
return results
这个特定的正则表达式检查接收到的参数值:
- ^: 表示字符串str前面没有字符
- str: 匹配 str字符串
- $: 表示字符串str后面不匹配任何字符
数值校验:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get('/items/{item_id}')
def read_items(
*, item_id: int = Path(..., ge=1), q: str ):
results = {'item_id': item_id}
if q:
results.update({'q': q})
return results
from fastapi import FastAPI, Path
app = FastAPI()
@app.get('/items/{item_id}')
async def read_items(*,
*,item_id: int = Path(..., gt=0, le=1000),q: str ):
results = {'item_id': item_id}
if q:
results.update({'q': q})
return results
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get('/items/{item_id}')
def read_items(
*, item_id: int = Path(..., ge=0, le=1000),
item_id: int = Path(..., ge=0, le=1000),
q: str,
size: float = Query(..., gt=0, lt=10.5) ):
results = {'item_id': item_id}
if q:
results.update({'q': q})
return results
4.3 请求体数据Body(⭐)
FastAPI 基于 Pydantic ,Pydantic 主要用来做类型强制检查(校验数据)。不符合类型要求就会抛出异常。
定义请求体,一般需要使用Pydantic模型,发送请求体数据,常用以下几种方法:POST(最常见)、PUT、DELETE、PATCH。
对于 API 服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度,因为开发者再也不用自己写一行一行的做类型检查。
安装上手pip install pydantic
(1)使用Pydantic模型
from typing import Union, List, Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field, ValidationError, validator
import uvicorn
from datetime import date
class Addr(BaseModel):
province: str
city: str
class User(BaseModel):
name = 'root'
age: int = Field(default=0, lt=100, gt=0)
birth: Optional[date] = None
friends: List[int] = []
description: Union[str, None] = None
# addr: Union[Addr, None] = None # 类型嵌套
@validator('name') # 定义自定义数据验证逻辑的装饰器
def name_must_alpha(cls, v):
assert v.isalpha(), 'name must be alpha'
return v
class Data(BaseModel): # 类型嵌套
users: List[User]
app = FastAPI()
@app.post("/data/")
async def create_data(data: Data):
# 添加数据库
return data
和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 None 可使其成为可选属性。
FastAPI 会自动将定义的模型类转化为JSON Schema,Schema 成为 OpenAPI 生成模式的一部分,并显示在 API 交互文档中,查看 API 交互文档如下,该接口将接收application/json类型的参数。
FastAPI 支持同时定义 Path 参数、Query 参数和请求体参数,FastAPI 将会正确识别并获取数据。
- 参数在 url 中也声明了,它将被解释为 path 参数
- 参数是单一类型(例如int、float、str、bool等),它将被解释为 query 参数
- 参数类型为继承 Pydantic 模块的
BaseModel
类的数据模型类,则它将被解释为请求体参数
(2)Body简单介绍
- 可以将单类型的参数成为Request Body的一部分,即查询参数变成请求体参数。
- 和 Query提供的额外校验基本一致。
def Body( # noqa: N802
default: Any = Undefined,
*,
embed: bool = False,
media_type: str = "application/json",
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,
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
**extra: Any,
) -> Any:
from fastapi import FastAPI,Body
app = FastAPI()
class Item2(BaseModel):
name:str = Body(default="张三",alias="姓名")
description:str=Body(default="",alias="描述")
price:float = Body(default='',alias="分数",gt=5.0,lt=10.5)
tax:float=None
@app04.post('
def create_item3(item:Item2 ):
return item
4.4 form表单数据
在 OAuth2 规范的一种使用方式(密码流)中,需要将用户名、密码作为表单字段发送,而不是 JSON。
FastAPI 可以使用Form组件来接收表单数据,需要先使用pip install python-multipart命令进行安装。
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/regin")
def regin(username: str = Form(..., max_length=16, min_length=8, regex='[a-zA-Z]'),
password: str = Form(..., max_length=16, min_length=8, regex='[0-9]')):
print(f"username:{username},password:{password}")
return {"username": username}
4.5 文件上传
from fastapi import FastAPI, File, UploadFile
from typing import List
app = FastAPI()
# file: bytes = File():适合小文件上传
# 使用 File() 接收文件内容,直接以 bytes 类型存储在内存中。
@app.post("/files/")
async def create_file(file: bytes = File()):
print("file:", file)
return {"file_size": len(file)}
# 通过 List[bytes] 接收多个文件,每个文件内容以字节形式存储
@app.post("/multiFiles/")
async def create_files(files: List[bytes] = File()):
return {"file_sizes": [len(file) for file in files]}
# file: UploadFile:适合大文件上传
# 使用 UploadFile 类型,支持流式读写,避免一次性加载大文件到内存。
# 分块读取文件(每次 1024 字节),写入本地磁盘
@app.post("/uploadFile/")
async def create_upload_file(file: UploadFile):
with open(f"{file.filename}", 'wb') as f:
for chunk in iter(lambda: file.file.read(1024), b''):
f.write(chunk)
return {"filename": file.filename}
@app.post("/multiUploadFiles/")
async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
4.6 Request对象
有些情况下我们希望能直接访问Request对象。例如我们在路径操作函数中想获取客户端的IP地址,需要在函数中声明Request类型的参数,FastAPI 就会自动传递 Request 对象给这个参数,我们就可以获取到 Request 对象及其属性信息,例如 header、url、cookie、session 等。
from fastapi import Request
@app.get("/items")
async def items(request: Request):
return {
"请求URL:": request.url,
"请求ip:": request.client.host,
"请求宿主:": request.headers.get("user-agent"),
"cookies": request.cookies,
}
4.7 请求静态文件
在 Web 开发中,需要请求很多静态资源文件(不是由服务器生成的文件),如 css/js 和图片文件等。
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static",StaticFiles(directory="static"))
4.8 响应模型相关参数(⭐)
(1)response_model
前面写的这么多路径函数最终 return 的都是自定义结构的字典,FastAPI 提供了 response_model 参数,声明 return 响应体的模型
# 路径操作
@app.post("/items/", response_model=Item)
# 路径函数
async def create_item(item):
...
response_model 是路径操作的参数,并不是路径函数的参数
FastAPI将使用response_model进行以下操作:
- 将输出数据转换为response_model中声明的数据类型。
- 验证数据结构和类型
- 将输出数据限制为该model定义的
- 添加到OpenAPI中
- 在自动文档系统中使用。
- 你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型
案例:
- 注册功能
- 输入账号、密码、昵称、邮箱,注册成功后返回个人信息
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
(2)response_model_exclude_unset
通过上面的例子,我们学到了如何用response_model控制响应体结构,但是如果它们实际上没有存储,则可能要从结果中忽略它们。例如,如果model在NoSQL数据库中具有很多可选属性,但是不想发送很长的JSON响应,其中包含默认值。
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[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},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
请求:http://127.0.0.1:8080/items/foo
不设置unset参数:
{
"name": "Foo",
"description": null,
"price": 50.2,
"tax": 10.5,
"tags": []
}
设置unset参数:
{
"name": "Foo",
"price": 50.2
}
使用路径操作装饰器的 response_model 参数来定义响应模型,特别是确保私有数据被过滤掉。使用 response_model_exclude_unset 来仅返回显式设定的值。
除了response_model_exclude_unset以外,还有response_model_exclude_defaults和response_model_exclude_none,我们可以很直观的了解到他们的意思,不返回是默认值的字段和不返回是None的字段。
(3)INCLUDE和EXCLUDE
response_model_exclude 和 response_model_include 是用于控制响应数据中字段的包含或排除的参数。它们与 response_model 配合使用,允许你精细调整返回给客户端的数据结构。
response_model_exclude
和 response_model_include
不能同时使用,否则 FastAPI 会抛出错误。
# response_model_exclude
@app.get("/items/{item_id}", response_model=Item, response_model_exclude={"description"}, )
async def read_item(item_id: str):
return items[item_id]
# response_model_include
@app.get("/items/{item_id}", response_model=Item, response_model_include={"name", "price"}, )
async def read_item(item_id: str):
return items[item_id]
五、中间件与CORS
5.1 中间件
你可以向 FastAPI 应用添加中间件.
"中间件"是一个函数,它在每个请求被特定的路径操作处理之前,以及在每个响应之后工作.
如果你使用了 yield
关键字依赖, 依赖中的退出代码将在执行中间件后执行.
如果有任何后台任务(稍后记录), 它们将在执行中间件后运行.
要创建中间件你可以在函数的顶部使用装饰器 @app.middleware("http")
.
中间件参数接收如下参数:
request
.- 一个函数
call_next
,它将接收request,作为参数.
-
- 这个函数将
request
传递给相应的 路径操作. - 然后它将返回由相应的路径操作生成的
response
.
- 这个函数将
- 然后你可以在返回
response
前进一步修改它.
import uvicorn
from fastapi import FastAPI
from fastapi import Request
from fastapi.responses import Response
import time
app = FastAPI()
@app.middleware("http")
async def m2(request: Request, call_next):
# 请求代码块
print("m2 request")
response = await call_next(request)
# 响应代码块
response.headers["author"] = "yuan"
print("m2 response")
return response
@app.middleware("http")
async def m1(request: Request, call_next):
# 请求代码块
print("m1 request")
# if request.client.host in ["127.0.0.1", ]: # 黑名单
# return Response(content="visit forbidden")
# if request.url.path in ["/user"]:
# return Response(content="visit forbidden")
start = time.time()
response = await call_next(request)
# 响应代码块
print("m1 response")
end = time.time()
response.headers["ProcessTimer"] = str(end - start)
return response
@app.get("/user")
def get_user():
time.sleep(3) # 模拟耗时操作
print("get_user函数执行")
return {
"user": "current user"
}
@app.get("/item/{item_id}")
def get_item(item_id: int):
time.sleep(2) # 模拟耗时操作
print("get_item函数执行")
return {
"item_id": item_id
}
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=8030, reload=True,
debug=True, workers=1)
FastAPI 的中间件按照注册顺序依次执行请求代码块,但响应代码块的执行顺序是相反的(后注册的中间件先处理响应)。
访问 /user
时:
m2 request
m1 request
get_user函数执行 # 休眠 3 秒
m1 response
m2 response
访问 /user
时,响应头如下:
HTTP/1.1 200 OK
content-type: application/json
author: yuan
ProcessTimer: 3.002345
5.2 CORS 跨源资源共享
CORS是跨源资源共享,用来允许不同源的前端应用访问后端API。
CORS(Cross-Origin Resource Sharing,跨源资源共享) 是一种浏览器安全机制,用于控制不同源(协议、域名、端口)之间的资源访问权限。它的核心目的是保护用户隐私和数据安全,防止恶意网站通过脚本窃取用户数据。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost:63342"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 允许的源列表。
# allow_origins=["*"], # 代表允许所有源。
# 当 allow_credentials=True 时,allow_origins 不能为 *,否则浏览器会拒绝请求。
allow_credentials=True, # 允许携带凭证(如 Cookies)
allow_methods=["GET"], # 允许的 HTTP 方法
allow_headers=["*"], # 允许的请求头
)
@app.get("/")
def main():
return {"message": "Hello World"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8080, debug=True, reload=True)