FastAPI
https://fastapi.tiangolo.com/zh/ 中文文档
https://pydantic-docs.helpmanual.io/ pydantic 文档
https://tortoise-orm.readthedocs.io/en/latest/getting_started.htmltortoise-orm异步sqlalchemy
1. 前期准备
1. 安装
首先需要安装两个库
fastapi 和 uvicorn
pip install fastapi uvicorn -i [https://pypi.tuna.tsinghua.edu.cn/simple/](https://pypi.tuna.tsinghua.edu.cn/simple/)
一定要加 -i 不然 安装有可能失败 所以一定要换源
2. 运行
第一种 直接运行
from fastapi import FastAPI
import uvicorn
app = FastAPI()
if __name__ == '__main__':
uvicorn.run(app)
但此时运行 会报错 因为没有找到资源
第二种 终端运行
uvicorn [文件名]:app
uvicorn参数解释:
- main: 指定主程序文件main.py文件, 如果main.py文件改成test.py 则命令也需要改为uvicorn test:app
- app:在main.py中使用app = FastAPI()创建的对象
- --host:远程主机ip,如果是本地则可以不要这个参数
- --host:端口号
- --reload:在修改源代码后程序会自动重新加载不用退出重新启动
3. 请求方式
1. get
这个是最常见的请求形式
(1) url参数
修改main.py
from fastapi import FastAPI
app = FastAPI()
# url参数定义在这个修饰器里面
@app.get("/{url}")
def read_root():
return {"Hello": "World"}
我们在@app.get中定义了url参数,那么我们下次get请求时就不能直接输入http://127.0.0.1:8000而是http://127.0.0.1:8000/xxxx(xxxx表示任意字符串)
(2) param参数
修改main.py
from fastapi import FastAPI
app = FastAPI()
# url参数可以和param重合
@app.get("/{url}")
def read_root(url: str, parms_1: str, parms_2: str=None):
return {'url地址是: ': url, "parms_1参数是 ": parms_1, "parms_2参数是 ": parms_2}
这是输入http://192.168.30.17:8000/anywords?parms_1=abc&parms_2=def就可以了,这里我使用的是远程服务器,因此我指定了ip和端口
parm参数
说明:
1.如果有parm参数则在地址后面加 ?,多个parm参数使用 & 连接
2.return中单引号和双引号没有区别
3.从上面可以看出url参数可以和parm参数合一,这是第一个url参数就不需要?url=,而是直接跟在端口80000/后面
2. post
post方法参数不是直接接在url后面,安全性会好一些
使用post时需要继承BaseModel这个类
(1)body参数
from fastapi import FastAPI
from pydantic import BaseModel #fastapi的一个依赖,需要从pydantic中引入
app = FastAPI()
class Args(BaseModel): #继承BaseModel
data_str: str #定义一个字符串型参数
data_int: int #定义一个整形参数
data_list: list #定义一个列表
@app.post("/test_post")
async def postEchoApi(args:Args): #设置刚才定义的参数
dict_args = args.dict() # 也可以转化为字典
return {"str data":args.data_str,
'int data': args.data_int,
'list data':args.data_list,
'args 数据类型': str(type(args))}
postman
测试post的时候就用postman就可以了,注意是在Body选择Raw在选择Json形式传入参数
2. 在服务器部署fastapi
第一种使用方式
uvicorn main:app --host '0.0.0.0' --port 8000 --reload --workers 1
第二种使用方式(推荐)
Gunicorn是一个成熟的、功能齐全的服务器和流程管理器。
Uvicorn包含一个Gunicorn worker类,允许您运行ASGI应用程序,具有Uvicorn的所有性能优势,同时还为您提供了Gunicorn的全功能流程管理。
这允许您动态地增加或减少工作进程的数量,正常地重新启动工作进程,或者在不停机的情况下执行服务器升级。
对于生产部署,我们建议将gunicorn与uvicorn worker类一起使用。
gunicorn main:app -b 0.0.0.0:8000 -w 1 -k uvicorn.workers.UvicornWorker
3. 请求参数和验证
1. helllo world 接口给后端COVID-19感染数据
首先运行一个简单的fastAPI程序,实现返回 “hello,world”的接口,进一步 继承Pydantic的Basemodel规范请求体数据格式和和类型。通过查询参数和请求体传递城市,所在国家,是否有感染病例的信息,讲解FastAPI框架的脚本开发方法,同步和异步函数的编写 装饰器,url路由,HTTP方法。
2. FastAPI的交互文档 - SwaggerUi 和ReDoc
API分档分为 SwaggerUi 和ReDoc
SwaggerUI : 0.0.0.0:8080/docs
ReDoc: http://0.0.0.0:8080/redoc
这个文档只能看不能用 不太好用
3. 路径参数的数据解析 验证
路径参数的类型,错误检查 自动填充没数据转换,解析验证(包括数字大小写范围的验证),参数别名,API交互文档的传参演示
from typing import Optional, List
from fastapi import APIRouter, Path, Query
from enum import Enum
from pydantic import BaseModel, Field
app01 = APIRouter()
"""
Path Parameters and Number validation 路径参数与数字验证验证
"""
@app01.get("/path/parameters")
async def path_params01():
return {"message": "This is a message"}
@app01.get("/path/{parameters}") # 函数的顺序 就是路由的顺序
async def path_params02(parameters):
return {"message": f"This is a {parameters}"}
class CityName(str, Enum):
BeiJing = "beijing china"
ShangHai = "shanghai china"
# 还可以定义枚举类型
@app01.get("/enum/{city}")
async def latest(city: CityName):
if city == CityName.BeiJing:
return {"cityName": city, "count": 1111, "death": 10}
elif city == CityName.ShangHai:
return {"cityName": city, "count": 2222, "death": 22}
# 通过path path 传递路径 :path 代表 这个是个路径传值
@app01.get("path/{file_path:path}")
async def file_path(file_path: str):
return {"filePath": f"{file_path}"}
# 验证参数
@app01.get("path_/{num}")
def path_params_verify(num: int = Path(..., title="Your number", description="不可描述", ge=1, le=10)):
return num
4. 查询参数和字符串的验证 Query Parameters and String Validation
讲解查询参数的传参方式,类型转换,多路径参数和查询参数的使用。必填查询参数 骂你一个能选择的数据源,查询今日全球covid-19感染数据的接口
# 给了参数值就是必填参数 没给就可填可不填
@app01.get("query")
def page_limit(page: int = 1, limit: Optional[int] = None):
if limit:
return {"page": page, "limit": limit}
return {"page": page}
# 布尔值的转换
@app01.get("query/bool/conversion")
def bool_conversion(params: bool = False):
return f"{params}"
# 查询多个参数的列表
@app01.get("query/string/conversion")
def string_conversion(
value: str = Query(..., min_length=8, max_length=16, regex="^a"),
values: List[str] = Query(default=["v1", "v2"], alias="alias_name")
):
return {"value": f"{value}", "values": f"{values}"}
5. 请求体以及混合参数
用Pydantic的BaseModel和Field类定义请求体数据格式和类型。如何定义多个请求体,请求体和路径参数,查询参数的混合使用。
class CityInfo(BaseModel):
# example 是注解 的功能
name: str = Field(..., example="BeiJing")
country: str
country_code: str = None
country_population: int = Field(default=800, title="人口数量", description="国家的人口数量", ge=800)
class Config:
schema_extra = {
"example": {
"name": "shanghai",
"country": "China",
"country_code": "CN",
"country_population": 1400000,
}
}
@app01.post("/request_body/city")
def city_info(city: CityInfo):
print(city.name, city.country)
return city.json()
@app01.put("/request_body/city/{name}")
def mix_city_info(
name: str,
city01: CityInfo,
city02: CityInfo,
confirmed: int = Query(ge=0, description="确诊人数", default=0),
death: int = Query(ge=0, description="死亡人数", default=0),
):
if name == "Shanghai":
return {"Shanghai": {"confirmed": confirmed, "death": death}}
return city01.dict(), city02.dict()
6. 如何定义数据格式嵌套的请求体Request Body - Nested Models
通过对Python类的继承,结合Typing 和Pydantic 的Field类,用嵌套的模型类定义数据格式嵌套的请求体
class Data(BaseModel):
city: List[CityInfo] = None # 这里就是定义 数据格式嵌套的请求体
date: date # 额外的数据类型 可以在官网查找到
confirmed: int = Field(ge=0, default=0, description="确诊人数")
death: int = Field(ge=0, default=0, description="死亡人数")
recovered: int = Field(ge=0, default=0, description="治愈人数")
@app01.put("/request_body/nest")
def nested(data: Data):
return data.json()
7.额外的参数和数据类型都有哪些?
schema.extra 属性定义额外的参数,其余数据类型一览。如
UUID
datetime
forzenset
bytes
decimal等
8. cookie 和 headers 参数
用fastiapi的ccokie类实现后端定义cookie的参数
用fastapi的header类定义后端定义请求头的参数。请求头参数自动转换功能介绍,请求头擦拭农户中重复key 如何处理
@app01.get("/cookie")
def cookie(cookie_id: Optional[str] = Cookie(None)):
return {"cookie_id": cookie_id}
@app01.get("/header")
def header(user_agent: Optional[str] = Header(None), x_token: List[str] = None):
return {"user_agent": user_agent, "x_token": x_token}
4. 响应数据和FastAPI配置
1. 响应模型 response model
响应模型的声明和调用
response_model 指定默认值或者响应模型。
response_model_exclude_unset
response_model_include_model_exclude
参数含义
class UserInfo(BaseModel):
user_name: str
password: str
email: EmailStr
mobile: str = "10086"
address: str = None
full_name: Optional[str] = None
class UserOut(BaseModel):
user_name: str
email: EmailStr
mobile: str = "10086"
address: str = None
full_name: Optional[str] = None
users = {
"user01": {"user_name": "zic1", "password": "1234555", "email": "hybpjx1@163.com"},
"user02": {"user_name": "zic2", "password": "12345", "email": "hybpjx2@163.com", "mobile": "110"}
}
# response_model_exclude_unset=True 只封装 填入的值
@app02.post("/response_model", response_model=UserInfo, response_model_exclude_unset=False)
async def response_model(user: UserInfo):
print(user.password)
return users["user02"]
@app02.post(
"/response_model/attributes",
# response_model=UserInfo,
# response_model=Union[UserInfo, UserOut]
response_model = List[UserInfo],
# response_model_include=["user_name", "email"],
# response_model_exclude=["password"]
)
async def response_model_attr(user: UserInfo):
# 可通过关键字删除密码 即使删除密码 依然可以成功
# del user.password
# 在使用 response_model = List[UserInfo] 时 需要返回一个列表
return [user,user]
# return user.dict()
2. 响应状态码 response status code
status_code 参数定义,fastapi。status快捷调用响应状态码的属性
@app02.post("status_code", status_code=200)
def status_code():
return {"status_code": 200}
# 实际上 status.HTTP_200_OK 就是200
@app02.post("status_attribute", status_code=status.HTTP_200_OK)
def status_attribute():
print(status.HTTP_200_OK)
print(type(status.HTTP_200_OK)) # int类型
return {"status_attribute_status_code": status.HTTP_200_OK}
3. 数据处理 Form Data
导入FastAPI的form类,代码演示如何定义表单参数。
@app02.post("form_login")
def form_login(user_name: str = Form(...), password: str = Form(...)):
# 用Form类需要 安装 pip install python-multipart Form 类的方法 类似于body query
return {"user_name": user_name, "password": password}
4. 单文件 多文件上传 以及参数详解 request File
file的UploadFile 类。以及其参数的定义,多文件的上传的实现。以解析COVID-19.csv文件数据为例
使用 upload_file的优势:
- 文件储存在内从中 当文件大于一个阀值 就写入磁盘总
- 适用于图片 视频 等
- 可以获取上传文件多元数据 内存 ,大小 上传时间 等
- 有文件对象多异步接口
- 上传多是python对象 由 read write 等方法
# @app02.post("/file")
# async def fileobj(file: bytes = File(...)):
@app02.post("/file") # 上传多个文件
async def fileobj(file: List[bytes] = File(...)):
""" 由于文件读写 是由bytes 读取 所以适合小内存多文件"""
return {"file_size": len(file)}
@app02.post("/upload_file")
async def upload_file(files: List[UploadFile] = File(...)):
"""
使用 upload_file的优势:
1. 文件储存在内从中 当文件大于一个阀值 就写入磁盘总
2. 适用于图片 视频 等
3. 可以获取上传文件多元数据 内存 ,大小 上传时间 等
4. 有文件对象多异步接口
5. 上传多是python对象 由 read write 等方法
:param file:
:return:
"""
for file in files:
content = await file.read()
print(content)
return {"file_name": files[1].filename, "file_content_type": files[0].content_type}
5. FastAPI项目的静态文件配置
CSS/JS/Images/静态文件的配置 项目如何找到static文件
在目录工程下新建static
在main.py下新加如下代码
from fastapi.staticfiles import StaticFiles
# mount 表示 将某个目录下一个完全独立的应用挂载过来,这个不会在API交互文档中显示
app.mount(
path="/static",
app=StaticFiles(directory="static"),
name="static")
6.路径操作配置(path,operation configuration)
Path Operation Configureation的概念,包括Response status Code,Tags,Summary and description, 文档描述符,相应描述,Deprecate参数
@app02.post(
"/path_operation_configuration",
response_model=UserOut,
# tags=['path','operation','configuration'],# 这个Tags 和主程序中的一样 会显示在API文档中
summary="this is a summary",
description="this is a description",
response_description="this is a response description",
# deprecated=True,# 表示这个接口已经废弃
status_code=status.HTTP_200_OK,
)
async def path_operation_configuration(user: UserIn):
"""
Path Operation Configuration 路径操作配置
:param user: 用户信息
:return: 返回结果
"""
return user.dict()
7. FastAPI应用的常见配置型
配置FasAPI 应用标题,描述,版本,tags的元数据,自定义openAPI和文档的url
app = FastAPI(
title="FastAPI Tutorial and Coronavirus Tracker API Docs ",
description="新管病毒 疫情跟踪器 API接口文档",
version="1.0",
docs_url="/docs",
redoc_url="/redocs",
)
8. FastAPI框架的错误处理
HTTPException的使用,如何自定义异常处理器,给第六小结开发的COVID-19数据查询接口定义
"""
Handing Error 错误处理
"""
@app02.get("/http_exception", )
async def http_exception(city: str):
if city != "Bei Jing":
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="City Not Found",
headers={"X-Error": "error"})
return {"city": city}
重写异常处理
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from fastapi.exceptions import HTTPException
from starlette.exceptions import HTTPException as star_HTTPException
@app.exception_handler(star_HTTPException) # 重写异常处理
async def http_exception_handler(request, exc):
"""
:param request: 这个参数不能省
:param exc:
:return: 只是把原本是Json的response 转变成文本的response
"""
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.exception_handler(RequestValidationError) # 重写请求验证的处理器
async def validation_exception_handler(request, exc):
"""
:param request: 这个参数不能省
:param exc:
:return:
"""
return PlainTextResponse(str(exc), status_code=400)
5. FastApi的依赖注入系统
“依赖注入”是指在编程中,为了保证代码成功运行,先导入或者声明器所需要的“依赖”,如子函数,数据库链接等等。
- 提高代码复用率
- 共享数据库链接
- 增强安全,认证和角色管理。(权限控制方面)
FastAPI的兼容性
- 所有的关系型数据库 支撑nosql数据库
- 第三方的包和API
- 认证和授权系统
- 响应数据注入系统
1. Dependencies 函数作为依赖项 创建和导入和声明依赖
async def common_parameters(q: Optional[str] = None, page: int = 1, limit: int = 10):
return {"q": q, "page": page, "limit": limit}
@app03.get("/dependency01") # 依赖的导入和声明
async def dependency01(commons: dict = Depends(common_parameters)):
return commons
# 不区分 async 和 def 都可以调用
@app03.get("/dependency02")
def dependency02(commons: dict = Depends(common_parameters)):
return commons
2. classes as dependencies 类作为依赖性
fake_items_db = [
{
"item_name": "Foo",
},
{
"item_name": "Bar",
},
{
"item_name": "Nav",
},
]
class CommonQueryParams:
def __init__(self, q: Optional[str] = None, page: int = 1, limit: int = 10):
self.q = q
self.page = page
self.limit = limit
@app03.get("/classes_as_dependencies")
# 三种写法
# async def classes_as_dependencies(common: CommonQueryParams = Depends(CommonQueryParams)):
# async def classes_as_dependencies(common: CommonQueryParams = Depends()):
async def classes_as_dependencies(common=Depends(CommonQueryParams)):
response = {}
if common.q:
response.update({"q": common.q})
items = fake_items_db[common.page:common.page+common.limit]
response.update({"items": items})
return response
3. Sub-dependencies 子依赖
def query(q: Optional[str] = None):
return q
def sub_query(q: str = Depends(query), last_query: Optional[str] = None):
if q:
return q
else:
return last_query
@app03.get("/sub_dependency")
async def sub_dependency(final_query: str = Depends(sub_query, use_cache=True)):
# use_ache表示 当有多个依赖有一个共同的子依赖时,每次request请求只会调用子依赖一次,多次调用将从缓存中调用。
return {"sub_dependency":final_query}
4. Dependencies in path operation decorators 路径操作装饰器中的多依赖
async def verify_token(x_token: str = Header(...)):
"""没有返回值的子依赖"""
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="X-Token header invalid")
return x_token
async def verify_key(x_key: str = Header(...)):
"""有返回值的子依赖 但是返回值不会被调用"""
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="X-Key header invalid")
return x_key
@app03.get("/dependency_in_path_operations", dependencies=[Depends(verify_token), Depends(verify_key)])
async def dependency_in_path_operations():
return [
{"user": "user01"},
{"user": "user02"},
{"user": "user03"},
]
5. Global Dependencies 全局依赖
-
- 通过某个单个路由 实现单个路由全局
# 使得全局都要 经过上述定义的两个以来 key和token
app03 = APIRouter(dependencies=[Depends(verify_token), Depends(verify_key)])
-
- 全局 整个项目
# 使得全局都要 经过上述定义的两个以来 key和token
app = FastAPI(
dependencies=[Depends(verify_token), Depends(verify_key)]
)
6. Dependency with yield 带yield的依赖
需要python 3.7 版本才支持,不然的话 需要安装很多的依赖,async-exit-stack async-generator
以下是伪代码
async def get_db():
db = "db_connection"
try:
yield db
finally:
db.endswith("db_close")
async def dependency_a():
dep_a = "generate_dep_a()"
try:
yield dep_a
finally:
dep_a.endswith("dep_a_close")
async def dependency_b(dep_a: Depends(dependency_a)):
dep_b = "generate_dep_b()"
try:
yield dep_b
finally:
dep_b.endswith(dep_a)
async def dependency_c(dep_b: Depends(dependency_b)):
dep_c = "generate_dep_c()"
try:
yield dep_c
finally:
dep_c.endswith(dep_b)
6. OAuth2.0的授权模式
- 授权码授权模式(Authorization Code Grant)
- 隐式授权模式(Implicit Grant)
- 密码授权模式(Resource Owner Password Credentials Grant)
在主程序中设置
app.include_router(app04, prefix="/test4", tags=["安全 认证与授权"])
"""
OAuth2PasswordBearer 是接受Url作为参数的一个类: 客户会向该URL发送UserName和Password 参数,然后得到一个Token值
OAuth2PasswordBearer 不会创建相应的URL路径操作,只是指明客户端用来请求Token的URL地址
"""
oauth2_schema = OAuth2PasswordBearer(tokenUrl="/test4/token") # 请求token的Url地址
@app04.get("oauth2_password_bearer")
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
return {"token": token}
1. 实现 oauth2_password_bearer认证
fake_users_db = {
"john snow": {
"username": "john snow",
"full_name": "John Snow",
"email": "johnsnow@example.com",
"hashed_password": "fakehashedsecret", # 哈希加密
"disabled": False, # 是否活跃
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice@example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
def fake_hash_password(password: str):
return "fakehashed" + password
class User(BaseModel):
username: str
email: Optional[EmailStr] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def fake_decode_token(token: str):
user = get_user(fake_users_db, token)
return user
async def get_current_user(token: str = Depends(oauth2_schema)):
user = fake_decode_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
# 规范,如果认证失败 请求头返回下述信息
headers={"WWW-Authenticate": "Bearer"},
)
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive User")
return current_user
@app04.post("/token")
async def login(form_data:OAuth2PasswordRequestForm= Depends()):
user_dict = fake_users_db.get(form_data.username)
if user_dict:
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,detail="Incorrect Username or password ")
return {"access_token":user.username,"token_type":"bearer"}
else:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,detail="Incorrect Username or password ")
@app04.get("users/me")
async def read_user_self(current_user:User = Depends(get_current_active_user)):
return current_user
2. 开发基于JWT的认证(Json Web Token)
内容太多 见GitHub
7. FastAPI中配置SQLALCHEMY
首先一个项目中需要包含如下文件
- static 项目中的静态文件
- templates 项目中被渲染的HTML
- database 配置fastapi中的数据库
- models 建立 模型类
- schemas 响应的数据体规范 继承BaseModel
- crud 对数据库的操作 如何获取信息等等
- main.py 主应用 接口入口
"""
附上三个SQLAlchemy教程
SQLAlchemy的基本操作大全
http://www.taodudu.cc/news/show-175725.html
Python3+SQLAlchemy+Sqlite3实现ORM教程
https://www.cnblogs.com/jiangxiaobo/p/12350561.html
SQLAlchemy基础知识 Autoflush和Autocommit
https://zhuanlan.zhihu.com/p/48994990
"""
8. 中间件
- 中间件
- 跨域资源共享(CORS)
- 后台任务
- 测试用例
中间件
request 经过中间件 路过应用 返回响应
中间件开发——给所有的响应加上响应时间
@app.middleware('http')
async def add_process_time_header(request: Request, call_next): # call_next将接收request请求做为参数
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers['X-Process-Time'] = str(process_time) # 添加自定义的以“X-”开头的请求头
return response
注:带yield的依赖的退出部分的代码 和 后台任务 会在中间件之后运行
2. 跨域资源共享
代码实现
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://127.0.0.1",
"http://127.0.0.1:8080"
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
3. 实现celery的后台任务
def bg_task(framework: str):
with open("README.md", mode="a") as f:
f.write(f"## {framework} 框架精讲")
@app06.post("/background_tasks")
async def run_bg_task(framework: str, background_tasks: BackgroundTasks):
"""
:param framework: 被调用的后台任务函数的参数
:param background_tasks: FastAPI.BackgroundTasks
:return:
"""
background_tasks.add_task(bg_task, framework)
return {"message": "任务已在后台运行"}
def continue_write_readme(background_tasks: BackgroundTasks, q: Optional[str] = None):
if q:
background_tasks.add_task(bg_task, "\n> 整体的介绍 FastAPI,快速上手开发,结合 API 交互文档逐个讲解核心模块的使用\n")
return q
@app06.post("/dependency/background_tasks")
async def dependency_run_bg_task(q: str = Depends(continue_write_readme)):
if q:
return {"message": "README.md更新成功"}
4. https://coronavirus-tracker-api.herokuapp.com/#/
5. 编写 测试用例
"""Testing 测试用例"""
from fastapi.testclient import TestClient
from main import app
client = TestClient(app) # 先pip install pytest
def test_run_bg_task(): # 函数名用“test_”开头是 pytest 的规范。注意不是async def
response = client.post(url="/test6/background_tasks?framework=FastAPI")
assert response.status_code == 200
assert response.json() == {"message": "任务已在后台运行"}
def test_dependency_run_bg_task():
response = client.post(url="/test6/dependency/background_tasks")
assert response.status_code == 200
assert response.json() is None
def test_dependency_run_bg_task_q():
response = client.post(url="/test6/dependency/background_tasks?q=1")
assert response.status_code == 200
assert response.json() == {"message": "README.md更新成功"}
直接在终端输入 pytest
如果通过就