FastAPI 框架,高性能,易于学习,高效编码,生产可用
官方文档: https://fastapi.tiangolo.com
FastAPI 是一个用于构建 API 的现convert_underscores代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。
关键特性:
- 快速:可与 NodeJS 和 Go 比肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一。
- 高效编码:提高功能开发速度约 200% 至 300%。
- 更少 bug:减少约 40% 的人为(开发者)导致错误。
- 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
- 简单:设计的易于使用和学习,阅读文档的时间更短。
- 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
- 健壮:生产可用级别的代码。还有自动生成的交互式文档。
- 标准化:基于(并完全兼容)API 的相关开放标准。
安装FastApi:
pip3 install fastapi
pip3 install unicorn
开始第一个Demo
# 创建一个main.py文件
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello FastApi"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
运行服务器:
一、命令运行服务器:
uvicorn main:app --reload --port 8888 # 更改端口号
二、代码运行服务器调试:
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", reload=True, port=5555)
FastApi提供交互式Api文档一:这很方便我们管理自己的接口
现在访问 http://localhost:8000/docs 就会生成一个Swagger文档
FastApi提供交互式Api文档二:这很方便我们管理自己的接口
现在访问 http://127.0.0.1:8000/redoc 就会生成一个redoc文档
FastApi中何时用Path、Query、Header、Body
Query:查询参数用
Path:路径参数
Body:需要在请求数据传入countent_type为json的
Form:请求数据为表单的
如果你想即「get」又「post」同时请求,你可以这么做:
@app.api_route("/login", methods=("GET", "POST", "PUT"))
def login():
"""这是一个登陆接口"""
return {"msg": "login success", "code": 200}
何时用「Form」,何时用「Body」,何时用「Header」呢
如果你你以表单的形式传递数据,那你就应该用「Form」,看一下代码
@app.post("/login1")
def login_form(username=Form(None), password=Form(None)):
return {"username": username, "password": password}
如果你以JSON的形式传递数据,那你就应该用「Body」,看一下代码
@app.post("/login")
def login(data=Body(None)):
return {"data": data}
如果你你想传递「Header」数据,那你就应该用「Header」,看一下代码
@app.get("/user")
def user(id, num=Header(None)):
return {"id": id, "num": num}
如何定制一个返回信息,看代码
作用:就是将自己定义好的响应结果返回回来
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/user")
def user():
return JSONResponse(content={"msg": "get user"}, # 内容
status_code=202, # 状态码,默认为200
headers={"a": "b"})
if __name__ == '__main__':
import uvicorn
uvicorn.run("04_response:app", reload=True)
如何将自己写的html网站动态加载到fastapi(jinja2模板返回Html页面)
我们需要安装一些依赖库
1、jinja2pip install jinjia2
2、aiofilespip install aiofiles
废话不多说,看代码
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates # 需要进入的库
from starlette.staticfiles import StaticFiles # 引入静态文件库
app = FastAPI()
# 指定静态文件存放路径
app.mount("/page", StaticFiles(directory="page"), name="page")
# 指定html 存放目录
template = Jinja2Templates("page")
@app.get("/home")
def home (req: Request):
return template.TemplateResponse("index.html", context={"request": req})
if __name__ == '__main__':
import uvicorn
uvicorn.run("d05_templates:app", reload=True)
如果你的代码中有引入到css样式,你就可以向我这样,你会发现样式就被引入进来了
如果你想自定义传参进来,你可以试试这样:
如果你想实现这样的例子
# main.py
from fastapi import FastAPI, Request, Form
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
template = Jinja2Templates("pages")
todos = ["写博客", "看电影", "玩游戏"]
@app.get("/")
async def index(req: Request):
return template.TemplateResponse("index.html", context={"request": req, "todos": todos})
@app.post("/todo")
async def todo(todo=Form(None)):
"""处理用户发送过来的 todolist 数据"""
todos.insert(0, todo)
return RedirectResponse('/', status_code=302)
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", reload=True)
在这里我说一下为什么要将状态码设置为302,如果你不设置这个status_code,浏览器发送给后端的请求状态码为307,因为307的状态码是不能从post请求跳转到get请求,原因是post请求如果要跳转到get请求不通用,如果想进行跳转,需要将307更改为302。
# index.html <meta charset="UTF-8"> <title>Title</title><h1>待办事项</h1><div> <form action="/todo" method="post"> <input name="todo" type="text" placeholder="请添加待办事件..."> <input type="submit" value="添加"> </form></div> {% for todo in todos %} <p>{{ todo }}</p> {% endfor %}
关联数据库,将数据存储化
第一步:我们需要安装依赖库
pip install tortoise-orm
pip install aiomysq
第二步:电脑需要安装mysql,安装调试过程不在赘述
以我为例:先创建一个db为fastapi的库
create database fastapi;
第三步:配置数据库
from tortoise.contrib.fastapi import register_tortoise
# 数据库绑定
register_tortoise(app,
db_url="mysql://root:Ranyong_520@localhost:3306/fastapi",
modules={"models": []},
add_exception_handlers=True,
generate_schemas=True)
实例一:将数据存储到数据库并返回给前端
# d06_templates.py
from fastapi import FastAPI, Request, Form
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from tortoise.contrib.fastapi import register_tortoise
from dao.models import Todo
app = FastAPI()
template = Jinja2Templates("pages")
# 数据库绑定
register_tortoise(app,
db_url="mysql://root:Ranyong_520@localhost:3306/fastapi",
modules={"models": ['dao.models']}, # 设置模型类
add_exception_handlers=True,
generate_schemas=True)
@app.get("/")
async def index(req: Request):
# 从数据库获取 todos 的代码
# ORM,获取所有的 todos
todos = await Todo.all() # 获取所有的todos
print(todos)
return template.TemplateResponse("index.html", context={"request": req, "todos": todos})
@app.post("/todo")
async def todo(content=Form(None)):
"""处理用户发送过来的 todolist 数据"""
await Todo(content=content).save()
return RedirectResponse('/', status_code=302)
if __name__ == '__main__':
import uvicorn
uvicorn.run("d06_templates:app", reload=True)
然后创建一个dao的文件夹里面创建一个models的py文件
from tortoise import Model, fieldsclass Todo(Model): """数据库当中的表 todo""" id = fields.IntField(pk=True) # id为int类型的 pk:是将id作为主键 content = fields.CharField(max_length=500) # todo项里面的内容 例如:todos = ["写博客", "看电影", "玩游戏"] created_at = fields.DatetimeField(auto_now_add=True) # auto_now_add 当每次插入新数据的时候会引用本地时间 updated_at = fields.DatetimeField(auto_now=True) # auto_now 当每次修改数据后会更新本地时间
这时候我们来运行下代码:
可以发现返回了,并没有返回添加的数据,那我们再去数据库看,点击数据库更新按钮后,可以发现我们的数据已经存储到了表中表。
可以看到数据库已经存了我们提交的数据,现在我们只需要改一下index.html文件一个地方就可以解决
最后效果
枚举(Enum)
在python3.4以后才可使用
第一步:需要倒入Enum库
from enum import Enum
第二步:创建一个Enum的子类
class ModelName(str, Enum):
A = 'a'
B = 'b'
C = 'c'
# 预设值.py
from fastapi import FastAPI
from enum import Enum
# 预设值
# 如果你有一个接收路径参数的路径操作,但是你希望预先设定可能的有效参数值
# 创建一个继承str和Enum的子类
class ModelName(str, Enum):
A = 'a'
B = 'b'
C = 'c'
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name == ModelName.A:
"""你可以用类里面ModelName.key来进行比较"""
return {"model_name": model_name, "message": "You chose A!"}
if model_name.value == 'b':
"""你也可以用model_name.value来进行比较"""
return {"model_name": model_name, "message": "You chose B"}
return {"model_name": model_name, "message": "You chose C"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("预设值:app", reload=True)
查询参数
默认值:
顾名思义,默认值就是已经写好了,你不需要在写任何参数,不过你写也不妨碍
from fastapi import FastAPIapp = FastAPI()fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]@app.get("/items/")async def read_item(skip: int = 0, limit: int = 10): """ skip默认值为0 limit默认值为10 """ return fake_items_db[skip:skip + limit]# 传入 http://127.0.0.1:8000/items/ === http://127.0.0.1:8000/items/?skip=0&limit=10# 返回值:"""[{"item_name": "Foo"},{"item_name": "Bar" },{"item_name": "Baz"}]"""if __name__ == '__main__': import uvicorn uvicorn.run("查询参数:app", reload=True)
可选参数
第一步:导入依赖库
from typing import Optional
第二步:写方法引用
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
# 选传参数
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Optional[str] = None, short: bool = False):
"""
:param item_id:
:param q: 函数q是可选的,默认值为None
:param short:
:return:
"""
item = {"item_id": item_id}
if q:
return {"item_id": item_id, "q": q}
if not short:
item.update(
{"description": "这是一个令人惊异的项目,有一个很长的描述"}
)
return item
# 传入http://127.0.0.1:8000/items/1?short=false
# 返回值:
"""
{
"item_id": "1",
"description": "这是一个令人惊异的项目,有一个很长的描述"
}
"""
# 传入 http://127.0.0.1:8000/items/1?short=true
# 返回值:
"""
{
"item_id": "1"
}
"""
if __name__ == '__main__':
import uvicorn
uvicorn.run("查询参数:app", reload=True)
必传参数
from fastapi import FastAPIapp = FastAPI()# 必传参数@app.get("/items1/{item_id}")async def read_user_item(item_id: str, needy: str): """ :param item_id: 必传 :param needy: 必传 :return: """ item = {"item_id": item_id, "needy": needy} return item# 传入 http://127.0.0.1:8000/items1/1?needy=1# 返回值:"""{ "item_id": "1", "needy": "1"}"""if __name__ == '__main__': import uvicorn uvicorn.run("查询参数:app", reload=True)
例子:
from fastapi import FastAPIapp = FastAPI()@app.get("/item2/{item_id}")async def read_user_item( item_id: str, needy: str, skip: int = 0, limit: Optional[int] = None): item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} return item# 传入:http://127.0.0.1:8000/item2/1?needy=2&skip=0&limit=1# 返回值:"""{ "item_id": "1", "needy": "2", "skip": 0, "limit": 1}"""if __name__ == '__main__': import uvicorn uvicorn.run("查询参数:app", reload=True)
请求体
什么是请求体?就是将数据从客户端发送api数据,这部分就叫请求体
请求体是客户端发送api的数据,响应是api发送给客户端的数据
api几乎总是要发送响应体,但是客户端并不总是需要发送请求体
⚠️:发送请求体不能用GET
,发送数据我们通常使用POST
,PUT
,DELETE
或PATH
第一步:导入Pydantic中的BaseModel
from pydantic import BaseModel
第二步:创建数据模型,将你的数据模型声明为继承自BaseModel的类
class Itme(BaseModel): name: str description: Optional[str] = None # Optional 参数为可选的 price: float tax: Optional[float] = None
完整代码
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel # 第一步导包
# 创建数据模型
class Itme(BaseModel): # 第二步创建数据模型
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
# 使用模型
# 在函数内部,可以直接访问模型对象的所有属性
@app.post("/items/")
async def create_item(item: Itme):
item_dict = item.dict()
if item.tax:
price_with_tax = item.proice + item.tax
item_dict.updata({"Proice_with_tax": price_with_tax})
return item_dict
# 请求体+路径参数
@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Itme):
return {"item_id": item_id, **item.dict()}
# 请求体+路径参数+查询参数
@app.put("/items1/{item_id}")
async def create_item(item_id: int, item: Itme, q: Optional[str] = None):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
if __name__ == '__main__':
import uvicorn
uvicorn.run("请求体:app", reload=True)
- 如果在路径中也声明了该参数,它将被作用路径参数
- 如果参数属于单一参数 类似(int、float、str、bool等) 它将被解释为查询参数
- 如果参数的类型被声明为一个Pydantic 模型,它将被解释为请求体
查询参数和字符串校验
"""
在FastApi中提供了数据校验的功能
"""
举个例子:
from typing import Optionalfrom fastapi import FastAPIapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[str] = None): """ 查询参数q的类型为str,可以看到默认值为None,因为它是可选的(Optional) 但是我们需要添加约束条件,即使q是可选的,我还想限制它不能超过50个字符串,下面可以看《第一步》 """ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
第一步:
导入Query
from fastapi import Query
第二步:
将Query作为查询参数的默认值,并进行限制
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()# 默认值@app.get("/items/")async def read_items(q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^love$")): """ :param q: 可选参数 :None = 可填参数为空 如果将None 更换成一个字符串 比如 "test" 该q的值就会成默认值"test" :min_length = 最小字符串数量 :max_length = 最大字符串数量 :regex = 正则匹配 ^ 以该符号之后的字符开头; $ 到此结束 :return: """ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
默认值
"""
向Query 的第一个参数传入None的作用是默认值,同样你也可以传其他的默认值
假设你想要声明查询参数q,限制它的最短字符串为3,并默认值为 demo,你可以这么做
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: str = Query("demo", min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
必填值
"""
当我们要声明一个参数为必填值时,只需要将Query里面的「None」改成「...」就好了,不用管它是什么含义,你就简单的理解「...」是必传的就对了
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: str = Query(..., min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
参数传入多个值
"""
当你使用Query定义查询参数时,你还可以声明它去接收一组值,简单来说 接收多个值
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[List[str]] = Query(None)): query_items = {"q": q} return query_items# 传入:http://localhost:8000/items/?q=foo&q=bar# 返回:"""{ "q": [ "foo", "bar" ]}"""if __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
使用List
"""
你可以直接使用List代替List[str]
两者区别:
List[int] :将检查列表的内容必须为整数
List:将不会做任何检查
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[list] = Query(None)): query_items = {"q": q} return query_items# 传入:http://localhost:8000/items/?q=foo&q=bar# 返回:"""{ "q": [ "foo", "bar" ]}"""if __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
别名参数
"""
如果你想查询参数为 text
传入:http://localhost:8000/items/?text=demo
但是text 不是一个有效的Python变量名称
这时你可以用 alias 参数声明一个别名,该别名在URL中查到查询参数值
"""
import uvicorn
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, alias="text")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
# 传入:http://localhost:8000/items/?text=demo
# 返回:
"""
{
"items": [
{
"item_id": "Foo"
},
{
"item_id": "Bar"
}
],
"q": "demo"
}
"""
if __name__ == '__main__':
uvicorn.run("查询参数和字符串校验:app", reload=True)
弃用参数
"""
如果假设你不在喜欢这个参数了,但是你又要保留一段时间,你可以引用弃用参数,它可以在文档中清晰的展示为已弃用
我们只需要传入一个参数 deprecated=True 传入Query即可
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items( q: Optional[str] = Query( None, alias="item-query", title="Query string", description="Query string for the items to search in the database that have a good match", min_length=3, max_length=50, regex="^fixedquery$", deprecated=True, )): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) return results# 在swagger 里面就会有个红色的标识if __name__ == '__main__': uvicorn.run("查询参数和字符串校验:app", reload=True)
Swagger文档:
路径参数和数值校验
"""
Path为路径参数声明,路径参数是必需的
所以,声明的时候「...」将其标记为必填参数
"""
第一步:
导入Path
from fastapi import FastApi, Path, Query
第二步:
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()@app.get("/items/{item_id}")async def read_items( item_id: int = Path(..., title="获取项目ID"), q: Optional[str] = Query(None, alies="item-query"),): results = {"item_id": item_id} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("路径参数和数据校验:app", reload=True)
todo:需要弄懂这个*号含义
数值校验:大于等于
"""
如果我们想要一个值大于100,我们可以声明数值约束,
只需要加一个参数「ge=x」,限制一个值大于或等于x
"""
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 大于等于@app.get("/items/{item_id}")async def read_items( *, item_id: int = Path(..., title="The ID of the item to get", ge=100), q: str): results = {"item_id": item_id} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("路径参数和数据校验:app", reload=True)
数据校验:大于和小于等于
"""
gt: 大于
le:小于等于
"""
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 大于等于@app.get("/items/{item_id}")async def read_items( *, item_id: int = Path(..., title="The ID of the item to get", ge=100), q: str): results = {"item_id": item_id} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("路径参数和数据校验:app", reload=True)
数据校验:浮点数、大于和小于
"""
例如:要求一个值必须大于0,小于1,因此,0.5将是有效的,但是0.0或0不是。
lt:小于
ge:大于等于
"""
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 浮点数、大于和小于@app.get("/items6/{item_id}")async def read_items6( *, item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), # item_id: 大于0,小于等于1000 q: str, size: float = Query(..., gt=0, lt=10.5) # 范围: 大于0,小于10.5): results = {"item_id": item_id} if q: results.update({"q": q}) return resultsif __name__ == '__main__': uvicorn.run("路径参数和数据校验:app", reload=True)
总结:
gt
:大于(g
reatert
han)ge
:大于等于(g
reater than ore
qual)lt
:小于(l
esst
han)le
:小于等于(l
ess than ore
qual)
请求体-多个参数
"""
混合使用Path,Query和请求体参数
"""
import uvicornfrom fastapi import FastAPI, Pathfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None# 混合使用Path,Query和请求体参数@app.put("/items/{item_id}")async def updata_item( *, item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), q: Optional[str] = None, item: Optional[Item] = None): results = {"item_id": item_id} if q: results.update({"q": q}) if item: results.update({"item": item}) return resultsif __name__ == '__main__': uvicorn.run("请求体-多个参数:app", reload=True)
多个请求体参数
import uvicornfrom fastapi import FastAPI, Pathfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = Noneclass User(BaseModel): username: str full_name: Optional[str] = None# 多个请求体参数@app.put("/items/{item_id}")async def update_item(item_id: int, item: Item, user: User): results = {"item_id": item_id, "item": item, "user": user} return resultsif __name__ == '__main__': uvicorn.run("请求体-多个参数:app", reload=True)
添加请求体的单一值:
"""
除了请求item和user之外,还想在单独请求另一个键 age,你可以添加请求体的单一值
"""
import uvicornfrom fastapi import FastAPI, Path, Bodyfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = Noneclass User(BaseModel): username: str full_name: Optional[str] = None# 请求体的单一值@app.put("/items/{item_id}")async def updata_item( item_id: int, item: Item, user: User, age: int = Body(...)): results = {"item_id": item_id, "item": item, "user": user, "age": age} return results# 这样就可以单独的请求就加载了json里"""{ "item": { "name": "string", "description": "string", "price": 0, "tax": 0 }, "user": { "username": "string", "full_name": "string" }, "age": 0}"""if __name__ == '__main__': uvicorn.run("请求体-多个参数:app", reload=True)
多个请求体参数和查询参数
"""
任何时候都可以声明额外的查询参数
"""
import uvicorn
from fastapi import FastAPI, Path, Body
from typing import Optional
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
class User(BaseModel):
username: str
full_name: Optional[str] = None
# 多个请求体参数和查询参数
@app.put("/items/{item_id}")
async def updata_item(
*,
item_id: int,
item: Item,
user: User,
importance: int = Body(..., gt=0), # Body具有与Query、Path一样的相同的数值校验
q: Optional[str] = None # 等价于 q: Optional[str] = Query(None)
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
results.update({"q": q})
return results
if __name__ == '__main__':
uvicorn.run("请求体-多个参数:app", reload=True)
嵌入单个请求体参数
"""
希望在item键并在值中包含模型内容JSON
并需要利用Body方法的embed参数,方能解析出RequestBody 内容
item: Item = Body(..., embed=True)
"""
import uvicornfrom fastapi import FastAPI, Path, Bodyfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = Noneclass User(BaseModel): username: str full_name: Optional[str] = None# 嵌入单个请求体参数@app.put("/items/{item_id}")async def updata_item( item_id: int, item: Item = Body(..., embed=True)): results = {"item_id": item_id, "item": item} return results# 返回:"""{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }}"""# 而不是返回"""{ "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2}"""if __name__ == '__main__': uvicorn.run("请求体-多个参数:app", reload=True)
请求体-字段
"""
Field 字段的意思其实就是类似上面Query,Path,也同样给给Body内的字段的信息添加相关的校验,通俗来说就是通过Field来规范提交Body参数信息
"""
第一步:
导入Field
from pydantic import BaseModel, Field
第二步:
举个例子
import uvicornfrom fastapi import FastAPI, Bodyfrom typing import Optionalfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = Field( None, title="The description of the item", max_length=300 ) price: float = Field(..., gt=0, description="The price must be greater than zero") tax: Optional[float] = [email protected]("/items/{item_id}")async def update_item(item_id: int, item: Item = Body(..., embed=True)): results = {"item_id": item_id, "item": item} return resultsif __name__ == '__main__': uvicorn.run("请求体-字段:app", reload=True)
请求体-嵌套模型
"""
定义、校验、记录文档并使用任意深度嵌套的模型
"""
List字段
"""
你可以将一个属性定义为拥有子元素的类型
"""
第一步:
从typing 导入 List
from typing import List, Optional
import uvicornfrom fastapi import FastAPI, Bodyfrom typing import Optional, Listfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None tags: List = []@app.put("/items/{item_id}")async def update_item(item_id: int, item: Item): results = {"item_id": item_id, "item": item} return resultsif __name__ == '__main__': uvicorn.run("请求体-嵌套模型:app", reload=True)
定义子模型
首先,我们要定义一个Image模型:
import uvicorn
from fastapi import FastAPI, Body
from typing import Optional, List, Set
from pydantic import BaseModel, Field
app = FastAPI()
# 定义一个Image模型
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
image: Optional[Image] = None # 将其用作一个属性的类型
# 定义子模型
@app.put("/items2/{item_id}")
async def updata_item2(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
"""
这意味着FastApi类似于以下内容的请求体:
{
"item_id": 1,
"item": {
"name": "Foo",
"description": "The Pretender",
"price": 43,
"tax": 3.2,
"image": {
"url": "https://www.baidu.com",
"name": "百度一下,你就知道"
}
}
}
"""
if __name__ == '__main__':
uvicorn.run("请求体-嵌套模型:app", reload=True)
特殊的类型和校验
"""
这里的特殊指检测url是否合规
例如:在Image模型中我们又一个Url字段,我们可以把它声明为Pydantic的HttpUrl,而不是str
该字符串将被检查是否为有效的Url,并在JSON Schema/OpenApi文档中进行记录
"""
import uvicorn
from fastapi import FastAPI, Body
from typing import Optional, List, Set
from pydantic import BaseModel, Field, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl # HttpUrl 回检查是否为有效的Url
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
image: Optional[Image] = None
# 定义子模型
@app.put("/items2/{item_id}")
async def updata_item2(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
# 返回
"""
这意味着FastApi类似于以下内容的请求体:
{
"item_id": 1,
"item": {
"name": "Foo",
"description": "The Pretender",
"price": 43,
"tax": 3.2,
"image": {
"url": "https://www.baidu.com", # 正确的Url回返回正常的200
"name": "百度一下,你就知道"
}
}
}
"""
if __name__ == '__main__':
uvicorn.run("请求体-嵌套模型:app", reload=True)
深度嵌套模型
"""
可以自定义任意深度的嵌套模型
"""
import uvicorn
from typing import List, Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
images: Optional[List[Image]] = None
class Offer(BaseModel):
name: str
description: Optional[str] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
if __name__ == '__main__':
uvicorn.run("demo2:app", reload=True)
Swagger UI 上的效果
任意dict构成的请求体
"""
可以将请求声明为使用某类型的键和其他类型的值的dict
例如:接收任意键为int类型并且值为float类型的dict
"""
import uvicornfrom typing import List, Optional, Set, Dictfrom fastapi import FastAPIapp = FastAPI()@app.post("/index-weights/")async def create_index_weights(weights: Dict[int, float]): # 接收的名为weight的dict,key为int类型,value为float类型的JSon return weights# 返回一个类似这样的一个JSON格式:“”“{ "1": 1.0, "2": 2.0, "3": 3.0}”“”if __name__ == '__main__': uvicorn.run("demo2:app", reload=True)
总结:
JSON仅支持将str作为键
但是Pydantic具有自动转换数据的功能。
API客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic就会进行转换并校验
然后你接收的名为weights的dict实际上具有int类型的键和floa类型的值
模型的额外信息
from typing import Optionalimport uvicornfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None class Config: schema_extra = { "example": { "name": "ranyong", "description": "A very nice Item", "price": 35.4, "tax": 3.2 } }@app.put("/items/{item_id}")async def updata_item(item_id: int, item: Item): results = {"item_id": item_id, "item": item} return results# 返回:"""{ "item_id": 123, "item": { "name": "ranyong", "description": "A very nice Item", "price": 35.4, "tax": 3.2 }}"""if __name__ == '__main__': uvicorn.run("模式的额外信息-例子:app", reload=True)
使用Field参数给JSON声明额外信息
from typing import Optionalimport uvicornfrom fastapi import FastAPIfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel): name: str = Field(..., example="这是姓名") description: Optional[str] = Field(None, example="这是一个描述") price: float = Field(..., example=36.4) tax: Optional[float] = Field(None, exapmle=3.2) age: str = Field(None, example="这是年龄") @app.put("/items/{item_id}")async def updata_item(item_id: int, item: Item): results = {"item_id": item_id, "item": item} return resultsif __name__ == '__main__': uvicorn.run("模式的额外信息-例子:app", reload=True)
Swagger UI展示(注意传递的参数不会添加任何验证,只会添加注释,用于文档的目的)
Body 额外参数
"""
可以将请求体的一个example传递给Body
"""
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Bodyfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel): name: str = Field(..., example="这是姓名") description: Optional[str] = Field(None, example="这是一个描述") price: float = Field(..., example=36.4) tax: Optional[float] = Field(None, exapmle=3.2) age: int = Field(None, example="这是年龄")@app.put("/items/{item_id}")async def update_item( item_id: int, item: Item = Body( ..., example={ "name": "Foo", "description": "A very nice Item", "price": 35.4, "tax": 3.2, "age": 18 }, ),): results = {"item_id": item_id, "item": item} return resultsif __name__ == '__main__': uvicorn.run("模式的额外信息-例子:app", reload=True)
额外数据类型
"""
其他数据类型
- UUID
一种标准的“通用唯一标识符”,在请求和响应中将以str表示
- datetime.datetime
在请求和响应中将表示为ISO 8601格式 例如:2021-07-14T14:36:35.171Z
- datetime.date
在请求和响应中将表示为ISO 8601格式 例如:2021-07-15
- datetime.time
在请求和响应中将表示为ISO 8601格式 例如:14:36:55.003
- datetime.timedelta
在请求和响应中将表示为float代表总秒数
- frozenset
- bytes
- Decimal
"""
第一步:
导包:
from datetime import datetime, time, timedeltafrom uuid import UUID
第二步:
import uvicorn
from fastapi import FastAPI, Body
from datetime import datetime, time, timedelta
from typing import Optional
from uuid import UUID
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Optional[datetime] = Body(None),
end_datetime: Optional[datetime] = Body(None),
repeat_at: Optional[time] = Body(None),
process_after: Optional[timedelta] = Body(None)
):
start_datetime = start_datetime + process_after
duration = end_datetime - start_datetime
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"repeat_at": repeat_at,
"process_after": process_after,
"start_process": start_datetime,
"duration": duration,
}
if __name__ == '__main__':
uvicorn.run("额外数据类型:app", reload=True)
在Swagger UI中显示
Cookie参数
"""
声明Cookie参数的结构于声明Query参数和Path参数相同
"""
第一步:
导入cookie
from fastapi import Cookie, FastApi
第二步:
from typing import Optionalfrom fastapi import Cookie, FastAPIapp = FastAPI()@app.get("/items/")async def read_items(ads_id: Optional[str] = Cookie(None)): return {"ads_id": ads_id}
Header参数
第一步:
导入Header
from fastapi import FastApi, Header
第二步:
声明Header参数
import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Header # 导入Headerapp = FastAPI()@app.get("/items/")async def read_items(user_agent: Optional[str] = Header(None)): return {"User-Agent": user_agent}# 输入:http://127.0.0.1:8000/items/# 返回:"""{"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"}"""if __name__ == '__main__': uvicorn.run("Header参数:app", reload=True)
如果你需要禁用下划线到连字符的自动转换,设置Header参数convert_underscores=False
@app.get("/items/")async def read_items(user_agent: Optional[str] = Header(None, convert_underscores=False)): # 禁用下划线到连字符的自动转换 return {"User-Agent": user_agent}
没添加:
添加了:
注意:在设置convert_underscores 为 False 之前,请确保一些HTTP代理和服务器不允许使用带有下划线的headers
重复的headers
"""
收到重复的headers,这意味着header具有很多个值
可以在类型声明中使用一个list来定义这些情况
"""
import uvicornfrom typing import Optional, Listfrom fastapi import FastAPI, Headerapp = FastAPI()# 重复的[email protected]("/items1")async def read_items1(x_token: Optional[List[str]] = Header(None)): return {"X-Token Values": x_token}if __name__ == '__main__': uvicorn.run("Header参数:app", reload=True)
如果发送了两个headers,响应就会是这样
响应模型
"""
你可以任意的路径操作中使用response_model参数来声明用于响应的模型
"""
import uvicornfrom typing import Optional, Listfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None tags: List[str] = []@app.post("/items/", response_model=Item) # 声明响应模型async def create_item(item: Item): return itemif __name__ == '__main__': uvicorn.run("响应模型:app", reload=True)
这里有个坑,如果你按照pip执行不成功的话,你可以这样做
安装这个模块:
或者这样:
返回于输入相同的数据
"""
例如:要求输入账号密码邮箱名字等等,这边只需要return 返回 账号邮箱名字 密码不会返回回来
"""
import uvicorn
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
# 返回于输入相同的数据
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
if __name__ == '__main__':
uvicorn.run("响应模型:app", reload=True)
响应模型编码参数
"""
这个主要运用到你提前设定好数据,然后输入可以把提前设定好的数据自动填装
你需要设置路径操作装饰器的 response_model_exclude_unset=True 参数
"""
import uvicornfrom typing import Optional, Listfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = 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:8000/items/foo# 返回:"""{ "name": "Foo", "price": 50.2}"""# 输入:http://127.0.0.1:8000/items/bar# 返回:"""{ "name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}"""if __name__ == '__main__': uvicorn.run("响应模型:app", reload=True)
具有与默认值相同值的数据
"""
支持模型包含和模型排除
简单来说就是你想需要什么值,不想要什么值
response_model_include或response_model_exclude 来省略某些属性
"""
import uvicornfrom typing import Optionalfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str description: Optional[str] = None price: float tax: float = 10.5items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, "baz": { "name": "Baz", "description": "There goes my baz", "price": 50.2, "tax": 10.5 },}@app.get( "/items/{item_id}/name", response_model=Item, # response_model_include 需要保留的属性 response_model_include={"name", "price"})async def read_item(item_id: str): return items[item_id]@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"price"})# response_model_exclude 需要忽略的属性async def read_item_public_data(item_id: str): return items[item_id]if __name__ == '__main__': uvicorn.run("响应模型:app", reload=True)
response_model_include 需要保留的属性
response_model_exclude 需要忽略的属性
总结:
使用路径操作装饰器 response_model 参数来定义响应模型,特别是确保私有数据被过滤掉
使用response_model_exclude_unset来仅返回显示设定的值
额外的模型
"""
输入模型需要拥有密码属性
输出模型不应该包含密码
数据库模型很可能需要保存密码的哈希值
"""
多个模型
todo: 没学会!
响应状态码
"""
来声明用作与响应HTTP状态码
类似于:
@app.get()
@app.post()
@app.put()
@app.delete()
status_code 参数接收一个表示HTTP状态码的数字
"""
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
if __name__ == '__main__':
uvicorn.run("响应状态码:app", reload=True)
注意:status_code 是 「装饰器」方法(get,post)的一个参数,不属于路径操作函数
介绍一种名称快捷的方法
第一步:
导包:
from fastapi import FastApi, status
第二步:
import uvicornfrom fastapi import FastAPI, statusapp = FastAPI()@app.post("/items/", status_code=status.HTTP_201_CREATED) #有足够多的状态码供你选择async def create_item(name: str): return {"name": name}if __name__ == '__main__': uvicorn.run("响应状态码:app", reload=True)
表单数据
"""
接收的不是JSON,而是表单字段,要使用Form
如果使用到Form表单,需要安装库pip install python-multipart
"""
# 导入Formimport uvicornfrom fastapi import FastAPI, Formapp = FastAPI()@app.post("/login/")async def login(username: str = Form(...), password: str = Form(...)): return {"username": username}if __name__ == '__main__': uvicorn.run("表单数据:app", reload=True)
特别注意的是:
- username和password最好通过表单字段来传递,因为符合密码流,不能通过JSON
- 在一个路径操作中声明多个Form参数,但不能同时声明要接收JSON的Body字段,因为此时请求体的编码是application/x-www-form-urlencoded,而不是application/json
请求文件
"""
File 用于定义客户端的上传文件
因为上传文件是以「表单数据」形式发送
需要安装库
pip install python-multipart
"""
# 导入 Fileimport uvicornfrom fastapi import FastAPI, File, UploadFile # 导包app = FastAPI()@app.post("/files/")async def create_file(file: bytes = File(...)): return {"file_size": len(file)}# 更推荐这个方法@app.post("/uploadfile/")async def create_upload_file(file: UploadFile = File(...)): return {"filename": file.filename}if __name__ == '__main__': uvicorn.run("请求文件:app", reload=True)
特别注意的是:
- File是直接继承自Form的类
- 声明文件体必须使用File,否则,FastApi会把该参数当作查询参数或请求体(JSON)参数
- 更推荐使用UploadFile
- UploadFile 与 bytes 相比有更多优势:
- 使用spooled文件:
- 存储在内存的文件超出最大上限时,FastApi会把文件存储到磁盘
- 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存
- 可获取上传文件的元数据
- 自带async接口
- **UploadFile 的属性如下:
- filename:上传文件名字符串(str),例如:myimage.jpg
- content-type:内容类型字符串(str) 例如:image/jepg
- file:其实就是Python文件,可直接传递给其他预期file-like 对象的函数或支持库**
- **UploadFile 支持的语法
- write(data):把data写入文件
- read(size):按指定数量的字节或字符读取文件内容
- seek(offset):移动至文件offset 字节处的位置
例如:await myfile.seek(0) 移动到文件开头
执行 await mylife.read() 后,需要再次读取已读取内容时 - close():关闭文件**
以上方法都是async方法,要搭配「await」使用
例如,在async路径操作函数内,要用一下方式读取文件内容:
contens = myfile.file.read()
什么是「表单数据」
不包含文件时,表单数据一般用application/x-www-form-urlencoded
但是表单包含文件时,编码为multipart/form-data。使用了File,FastApi就知道从请求体的正确获取文件
多文件上传
"""
同时上传多个文件
可用同一个「表单字段」发送含多个文件的「表单数据」
上传多个文件时,要声明含bytes或UploadFile的列表
"""
import uvicornfrom fastapi import FastAPI, File, UploadFilefrom typing import Listfrom starlette.responses import HTMLResponseapp = FastAPI()# 多文件上传@app.post("/files")async def create_files(files: List[bytes] = File(...)): # 以byte形式 return {"file_sizes": [len(file) for file in files]}@app.post("/uploadfiles/")async def create_upload_files(files: List[UploadFile] = File(...)): # 以名字的形式 return {"filenames": [file.filename for file in files]}if __name__ == '__main__': uvicorn.run("请求文件:app", reload=True)
以byte形式展示:
以名字的形式展示:
import uvicornfrom fastapi import FastAPI, File, UploadFilefrom typing import Listfrom starlette.responses import HTMLResponseapp = FastAPI()@app.get("/")async def main(): content = """<form action="/files/" enctype="multipart/form-data" method="post"><input name="files" type="file" multiple=""><input type="submit"></form><form action="/uploadfiles/" enctype="multipart/form-data" method="post"><input name="files" type="file" multiple=""><input type="submit"></form> """ return HTMLResponse(content=content)if __name__ == '__main__': uvicorn.run("请求文件:app", reload=True)
请求表单与文件
"""
FastApi 支持同时使用File和Form定义文件和表单字段
需要安装库
pip install python-multipart
"""
第一步:
导入 File 与 Form
from fastapi import FastAPI, File, Form, UploadFile
第二步:
import uvicorn
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI()
@app.post("/files")
async def create_file(
# 定义File与Form参数
file: bytes = File(...),
fileb: UploadFile = File(...),
token: str = Form(...)
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
}
if __name__ == '__main__':
uvicorn.run("请求表单与文件:app", reload=True)
总结:
- 在同一个请求中接收数据和文件时,应同时使用File和Form
处理错误
"""
向客户端返回HTTP错误响应,可以使用HTTPException
"""
第一步:
导入:
from fastapi import FastApi, HTTPException
第二步:
# 导入HTTPException
import uvicorn
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
# 输入:http://127.0.0.1:8000/items/foo1
# 返回:
"""
{
"detail": "Item not found"
}
"""
if __name__ == '__main__':
uvicorn.run("处理错误:app", reload=True)
异常处理器
"""
FastApi 自带了一些默认异常处理器
当我们接收到无效数据时,FastApi内部会触发RequestValidationError
该异常内置了默认异常处理器
"""
第一步:
导入RequestValidationError
from fastapi.exceptions import RequestValidationError
并用@app.excption_handler(RequestValidationError)装饰器处理异常
第二步:
import uvicornfrom fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import PlainTextResponsefrom fastapi.exceptions import RequestValidationErrorfrom starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = FastAPI()@app.exception_handler(StarletteHTTPException)async def http_exception_handler(request, exc): return PlainTextResponse(str(exc.detail), status_code=exc.status_code)@app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc): return PlainTextResponse(str(exc), status_code=400)@app.get("/items/{item_id}")async def read_item(item_id: int): if item_id == 3: raise HTTPException(status_code=418, detail="Nope! I don't like 3.") return {"item_id": item_id}# 输入:http://127.0.0.1:8000/items/3# 返回:"""{"detail": "Nope! I don't like 3."}"""if __name__ == '__main__': uvicorn.run("处理错误:app", reload=True)
请求体-更新数据
"""
用PUT更新数据
"""
更新所有数据(也可更新部分数据)
import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
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)
async def read_item(item_id: str):
"""获取数据"""
return items[item_id]
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
"""更新数据"""
update_item_encoded = jsonable_encoder(item)
items[item_id] = update_item_encoded
return update_item_encoded
if __name__ == '__main__':
uvicorn.run("请求体-更新数据:app", reload=True)
先PUT,后GET
只发送要更新的数据,其余数据不变
import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
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": []},
}
"""
用PATCH进行部分更新
只发送要更新的数据,其余数据保持不变
"""
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
"""获取数据"""
return items[item_id]
@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
"""部分更新数据"""
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
if __name__ == '__main__':
uvicorn.run("请求体-更新数据:app", reload=True)
使用Pydantic的update参数
"""
.copy():为已有模型创建调用update参数的脚本,该参数为包含更新数据的dict
简而言之就是复制出一份后,以你这个为准,原先的被覆盖
"""
import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
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": []},
}
"""
用 .copy() 为已有模型创建调用update参数的副本,该参数为包含更新数据的dict
例如:stored_item_model.copy(update=update_data):
"""
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
return items[item_id]
@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
if __name__ == '__main__':
uvicorn.run("请求体-更新数据:app", reload=True)
依赖注入
"""
相同的逻辑判断处理
共享数据库连接
用户身份鉴权,角色管理
业务逻辑复用
提高代码的复用,减少代码重复
"""
第一步:
导入Depends:
from fastapi import Depends, FastApi
第二步:
创建一个依赖项,或「被依赖项」
import uvicorn
from fastapi import FastAPI, Depends
app = FastAPI()
# 创建依赖 仅需要2行
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
"""
common_parameters函数主要是负责接收函数,处理后返回一个字典
:param q: 可选查询参数q那是一个str
:param skip: 默认情况下是0
:param limit: 默认情况下是100
:return: 返回一个字典
"""
return {"q": q, "skip": skip, "limit": limit}
# 声明依赖
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)): # 声明了一个依赖关系,表示的是接口参数请求依赖与common_parameters的函数
commons.update({'小钟': '同学'})
return commons
# 声明依赖
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
if __name__ == '__main__':
uvicorn.run("依赖注入:app", reload=True)
注意:您只需要给Depends一个参数,此参数必须类似于函数
把类当作被依赖对象
import uvicorn
from fastapi import FastAPI, Depends
app = FastAPI()
# 把类当作被依赖对象
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str = None, name: str = None, skip: int = 0, limit: int = 100):
self.q = q
self.name = name
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
"""
:param commons: CommonQueryParams = Depends(CommonQueryParams)和 commons = Depends(CommonQueryParams)是等价的
:return:
"""
response = {}
# 如果q存在
if commons.q:
# 我们就把q加到一个新字典
response.update({"q": commons.q})
response.update({"小钟": '同学'})
elif commons.name:
response.update({"name": commons.name})
# 然后在我们的fake_items_db进行截取
items = fake_items_db[commons.skip: commons.skip + commons.limit]
response.update({"items": items})
return response
# 输入:http://127.0.0.1:8000/items/?q=1&name=admin
# 返回:
"""
{
"q": "q",
"小钟": "同学",
"name": "admin",
"items": [
{
"item_name": "Foo"
},
{
"item_name": "Bar"
},
{
"item_name": "Baz"
}
]
}
"""
if __name__ == '__main__':
uvicorn.run("依赖注入:app", reload=True)
有q,name参数:
没有q,name参数:
多层嵌套依赖
import uvicorn
from fastapi import FastAPI, Depends, Cookie
app = FastAPI()
# 多层嵌套依赖
def query_extractor(q: str = None):
return q
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: str = Cookie(None)
):
"""
query_or_cookie_extractor 依赖于 query_extractor
然后 query_or_cookie_extractor被注入到接口上也被依赖的对象
:param q:
:param last_query:
:return:
"""
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
return {"q_or_cookie": query_or_default}
# 对于同一个依赖,如果处理的结果是一样的,就是返回值是一样的话,我们可以进行多次调用依赖,这时候可以对被依赖的对象设置是否使用缓存机制:
@app.get("/items1")
async def needy_dependency(fresh_value: str = Depends(query_or_cookie_extractor, use_cache=False)):
"""
use_cache=False 不启动缓存机制
"""
return {"fresh_value": fresh_value}
if __name__ == '__main__':
uvicorn.run("依赖注入:app", reload=True)
官网给的例子:
list列表依赖
"""
list列表的依赖意思就是必须两条条件都成立才通过
"""
import uvicorn
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
# list列表依赖
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
if __name__ == '__main__':
uvicorn.run("依赖注入:app", reload=True)
多依赖对象注入
import uvicorn
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
# 多依赖对象注入
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
@app.get("/items2")
async def items2(xt: str = Depends(verify_token), xk: str = Depends(verify_key)):
return {"xt": xt, "xk": xk}
if __name__ == '__main__':
uvicorn.run("依赖注入:app", reload=True)
安全性
"""
/ todo 待补充
"""
第一步:
安装依赖库(因为OAuth2使用“form data”发送用户名和密码)
pip install python-multipart
第二步:
import uvicorn
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,
此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_item(token: str = Depends(oauth2_scheme)):
return {"token": token}
if __name__ == '__main__':
uvicorn.run("安全性:app", reload=True)
在Swagger UI 中:
点击它:
如果没有输入username和password,直接try it out 会直接响应401状态码(未经授权)
案例一:获取当前用户
import uvicorn
from typing import Optional
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
app = FastAPI()
"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# oauth2_scheme变量是OAuth2PasswordBearer的实例,但它也是一个"可调用的"
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
# 创建(伪)工具函数,该函数接收str类型的令牌并返回到User模型
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
# 创建一个get_current_user 依赖项
async def get_current_user(token: str = Depends(oauth2_scheme)):
"""
依赖项get_current_user将从子依赖项oauth2_scheme中接收一个str类型的token
:param token:
:return:
"""
user = fake_decode_token(token)
return user
@app.get("/users/me")
# 注入当前用户
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
if __name__ == '__main__':
uvicorn.run("安全性:app", reload=True)
案例二:使用密码和Bearer的简单OAuth2
导入OAuth2PasswordRequestForm,然后在token的路径操作中通过Depends将其作为依赖项使用
import uvicorn
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
# 获取username和password的代码
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
def fake_hash_password(password: str):
return "fakehashed" + password
"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# oauth2_scheme变量是OAuth2PasswordBearer的实例,但它也是一个"可调用的"
class User(BaseModel):
username: str
email: Optional[str] = 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):
user = get_user(fake_users_db, token)
return user
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_decode_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效身份验证",
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=400, detail="无效用户")
return current_user
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="账号密码不符")
user = UserInDB(**user_dict)
"""
UserInDB(**user_dict) ===
UserInDB(
username= user_dict["username"],
email = user_dict["email"],
full_name = user_dict["full_name"],
disabled = user_dict["disabled"],
hashed_password = user_dict["hashed_password"],
)
"""
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="用户名和密码不符")
return {"access_token": user.username, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
if __name__ == '__main__':
uvicorn.run("安全性:app", reload=True)
在Swagger UI 中
点击Authorize
点击登陆后
如果未启用的用户(disabled=True),发送get("/users/me")请求就会得到一个「未启用的用户」错误
使用(哈希)密码和JWT Bearer 令牌的OAuth2
"""
什么是JWT?
简单来说就是
会给你一个时效性为一周的令牌,你拿到了令牌你一周可以免登陆,一周过后,你需要重新发起请求获取令牌
需要安装依赖库:
pip install python-jose
**什么是哈希密码?
**简单来说就是
将内容转换成一些像乱码的字节序列
每次传入完全相同的密码时,你就会得到完全相同的乱码
你只能转换成乱码,不能将乱码转换成字符
需要安装依赖库:
pip install passlib
"""
建议回头重看
OAuth2 实现密码哈希与 Bearer JWT 令牌验证 - FastAPI
中间件
// todo 后续回看
异步、后台任务
"""
需要在request执行之后继续操作,但终端并不需要等待这些操作完成才能收到response
例如:
1、执行完request之后发送邮件通知
2、收到文件之后对文件进行二次处理
我们可以通过定义后台任务BackgroundTasks来实现这个功能
"""
# 使用BackgroundTasks
import uvicorn
from fastapi import BackgroundTasks, Depends, FastAPI
app = FastAPI()
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(message)
def get_query(background_tasks: BackgroundTasks, q: str = None):
if q:
message = f"发送内容: {q}\n"
background_tasks.add_task(write_log, message)
return q
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
):
message = f"邮箱来自: {email}\n"
background_tasks.add_task(write_log, message)
"""
.add_task()接收的参数
- 一个在后台运行的任务函数(write_notification)
- 按照顺序传递的一个系列参数(email)
- 任何的关键字参数(essage="notification..)
"""
return {"message": "Message sent", "q": q}
if __name__ == '__main__':
uvicorn.run("后台任务:app", reload=True)