FastAPI 文檔
- FastAPI 文檔
- 1 前置基礎介紹
- 2 FastAPI介紹
- 3 FastAPI 基礎內容
- 3.1 啟動項目 main.py
- 3.2 路徑參數
- 3.3 查詢參數
- 3.4 FastAPI請求體
- 3.5 查詢參數和字符串驗證
- 3.6 路徑參數和數值驗證
- 3.7 聲明主體多參數
- 3.8 body 字段
- 3.9 body嵌套模型
- 3.10 額外的數據類型
- 3.11 cookie參數和header參數
- 3.12 響應模型 Response Model
- 3.13 擴展模型
- 3.14 響應狀態碼
- 3.15 表單數據
- 3.16 請求文件
- 3.17 錯誤處理
- 3.18 路徑操作配置
- 3.20 JSON兼容編碼
- 3.21 body更新
- 3.22 FastAPI 依賴部分
- 3.23 FastAPI 中間件
- 3.24 FastAPI Cors 跨越請求
- 3.25 FastAPI 大應用-多個文件
- 4 FastAPI進階內容
- 5 FastAPI 認證
- 6 數據庫
- 7 FastAPI 日志管理
- 8 FastAPI 緩存
- 9 FastAPI Users 用戶管理
- 10 FastAPI 定期任務
- 11 FastAPI后台接口文檔
- 9 docker + Nginx 部署FastAPI
1 前置基礎介紹
1.1 Web開發模式
FastAPI 適用於前后端分離模式
注:
1 后端僅返回前端所需的數據
2 API: 前后端分離模式中,每一個函數或試圖稱之為接口或API
1.2 基於RESTful的FastAPI的設計風格
在進行API接口設計時,應規定統一的接口設計方式采用的RESTful API設計風格
請求方式
不同的URL 采用不同的請求方式,代表不同的執行操作
常用的HTTP請求方式有下面四個:
請求方式 | 說明 |
---|---|
GET | 獲取資源數據(單個或多個) |
POST | 新增資源數據 |
PUT | 修改資源數據 |
DELETE | 刪除資源數據 |
響應數據格式
服務器返回的響應數據格式,應該盡量使用JSON
響應的狀態碼
服務器向客戶端返回的狀態碼和提示信息
200 OK - [GET]:服務器成功返回用戶請求的數據
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功
202 Accepted - [*]:表示一個請求已經進入后台排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作
401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作
406 Not Acceptable - [GET]:用戶請求的格式不可得
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的
422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤
500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功
1.3 REST接口開發的核心任務
將請求的數據(如JSON格式)轉換為模型類對象
操作數據庫
將模型類對象轉換為響應的數據(如JSON格式)
序列化 :將數據庫數據序列化為前端所需要的格式 如(字典、JSON、XML等)
反序列化: 將前端發送的數據反序列化保存到模型類對象,並保存到數據庫中
2 FastAPI介紹
2.0 概述
為什么選擇 FastAPI ?
FastAPI 是Python領域(3.6+)用來構建 API 服務的一個高性能框架。
一、快速
性能極高,可與 NodeJS, Go 媲美。(得益於Starlette和Pydantic)。
Starlette 是一個輕量級 ASGI 框架/工具包。它非常適合用來構建高性能的 asyncio 服務,並支持 HTTP 和 WebSockets。
官方網址:https://www.starlette.io/
Pydantic 是一個使用Python類型提示來進行數據驗證和設置管理的庫。Pydantic定義數據應該如何使用純Python規范用並進行驗證。
官方網址:https://pydantic-docs.helpmanual.io/
二、簡單易懂,易於上手
1、設計的初衷就是易於學習和使用。不需要閱讀復雜的文檔。
2、良好的編輯器支持。支持自動完成,可以花更少的時間用於調試。
3、代碼復用性強。
4、方便的 API 調試,生成 API 文檔。
5、能提高開發人員兩到三倍的開發速度。減少40%的人為出錯幾率。
三、健壯性強
企業級代碼質量。
四、標准統一
遵循以下API解決方案的開放標准:OpenAPI (也叫Swagger) and JSON Schema。
性能測試參考:
2.1 環境安裝與使用
FastAPI是基於python3.6+和標准python類型的一個現代化的,快速的(高性能) 的異步框架,有自動式交互文檔,非常適合構建RESTful API。
查看官方文檔: https://fastapi.tiangolo.com
2.2 FastAPI特性
詳情查看官方文檔:
https://fastapi.tiangolo.com/features/
FastAPI的特點:
基於開放的標准
- OpenAPI的創建,包含了路徑操作,參數,請求體和安全等的聲明。
- 通過JSON Schema自動的數據模型文檔。
- 圍繞着這些標准設計,通過嚴謹的學習,而不是在上面加一層后記。
- 也允許在很多種語言中使用自動的客戶端代碼生成器
3.1 使用虛擬環境
使用虛擬環境安裝fastapi
引用:https://www.cnblogs.com/chjxbt/p/10517952.html
3 FastAPI 基礎內容
本章案例在 test_fastapi/FastAPI基礎內容目錄下運行
3.1 啟動項目 main.py
from fastapi import FastAPI
import uvicorn
# 創建app應用
app = FastAPI()
# 路徑裝飾器
@app.get("/")
async def root():
return {"message": "Hello World"}
# 為路徑添加參數
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
# 現測試環境采用調式模式運行
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
進入交互式API文檔
現在進入 http://127.0.0.1:8000/docs.
你會看到自動生成的交互式api文檔,可以進行操作
3.2 路徑參數
我們可以聲明路徑“參數”或“變量”與Python格式字符串使用相同的語法:
# 路徑參數
from fastapi import FastAPI
app = FastAPI()
@app.get("/me/xx")
async def read_item_me():
return {"me": 'me'}
@app.get("/me/{item_id}") # item_id 為路徑參數
async def read_item(item_id: str):
return {"item_id": item_id}
@app.get("/")
async def main():
return {"message": "Hello,FastAPI"}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
路徑參數item_id的值將作為參數itemid傳遞給函數。
運行這個示例並轉到http://127.0.0.1:8000/items/foo,您將看到一個響應:
{"item_id" : "foo"}
聲明帶有類型的路徑參數
注意:聲明類型從參數為必填項
@app.get("/items/{item_id}")
async def read_item(item_id: int): # item_id 為int類型
return {"item_id": item_id}
進入API交互式文檔中校驗
3.3 查詢參數
當您聲明不屬於路徑參數一部分的其他函數參數時,它們將被自動解釋為“查詢”參數。
from fastapi import FastAPI
app = 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就是查詢參數
return fake_items_db[skip : skip + limit]
查詢是在?
之后的鍵-值對集合。在URL中,用&
字符分隔(查詢字符串)
例如,在url中: 或者進入交互式文檔中
http://127.0.0.1:8000/items/?skip=0&limit=10
查詢參數為: skip:值為0; limit:值為10
同樣我們可以設置為可選參數 為None , 以及 參數類型裝換
還有多路徑參數 @app.get("/users/{user_id}/items/{item_id}")
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None, short: bool = False):
item = {"item_id": item_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item
3.4 FastAPI請求體
當你需要從一個客戶端(簡單來說是瀏覽器)發送數據給你的API,你發送的內容叫做請求體
。
一個請求體數據通過客戶端發送到你的API。你的API返回一個響應體給客戶端。
你的API總是發送一個響應體,但是客戶端不必一直發送請求體。
為了聲明一個請求體,使用Pydatic
模型支持。
注意: 使用Pydatic 發送請求體不能是GET 請求,必須使用POST,PUT,DELETE等發送數據
from fastapi import FastAPI
from pydantic import BaseModel
# 這個是創建數據模型 繼承與BaseModel
class Item(BaseModel):
name: str
description: str = None # 不必要的默認值可以用Noen
price: float
tax: float = None
app = FastAPI()
@app.post("/items/")
async def create_item(item_id:int, item: Item, q:str=None): # 聲明一個item參數指向Item數據模型
print(item.name)
print(item.dict())
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
查看交互式文檔
返回的為json數據
3.5 查詢參數和字符串驗證
FastAPI允許聲明額外的信息並且驗證你的參數。
從fastapi中導入Query
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str = Query(None, min_length=3, max_length=50)): # 限制長度
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
3.6 路徑參數和數值驗證
可以使用Query聲明查詢參數的更多驗證和元數據的方式相同,也可以使用Path聲明相同類型的驗證和路徑參數的元數據。
從fastapi中導入Path
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: int = Path(..., title="The ID of the item to get",ge=1),
q: str = Query(None, alias="item-query"),
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
始終需要一個路徑參數,因為它必須是路徑的一部分。
.. 作為改標記的必填項 gt:大於,le:小於等於,lt:小於
3.7 聲明主體多參數
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel): # 項目數據模型
name: str
description: str = None
price: float
tax: float = None
class User(BaseModel): # 用戶數據模型
username: str
full_name: str = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body(...)):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
在這種情況下,FastAPI將注意到該函數中有多個主體參數(兩個參數是Pydantic模型)。
因此,它將使用參數名稱作為正文中的鍵(字段名稱),並期望一個類似於以下內容的正文:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
3.8 body 字段
您可以使用Query
,Path
和Body
在路徑操作函數參數中聲明其他驗證和元數據的方式相同,也可以使用Pydantic的Field在Pydantic模型內部聲明驗證和元數據。
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
# 請求體參數驗證,使用 Field 字段
class Item(BaseModel):
name: str
description: 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: float = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
demo2
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float = Field(..., gt=0)
tax: float = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item = Body(...,
example={ # example是Body里沒有的字段;不會添加任何驗證,而只會添加注釋;不是example也不行
"name": "Foo",
"description": "A very nice Item", # 默認值
"price": 0,
"toooo": 3.2,
},
)
):
results = {"item_id": item_id, "item": item}
return results
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Field
的工作方式與Query
,Path
和Body
相同,它具有所有相同的參數,等等。
3.9 body嵌套模型
使用FastAPI,可以定義,驗證,記錄和使用任意深度嵌套的模型(這要歸功於Pydantic)
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# 定義一個數據模型,子模型
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[str] = [] # 列表類型
tags2: Set[str] = set() # 集合類型
# 嵌套另一個子模型
image: Optional[Image] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
這意味着FastAPI將期望類似於以下內容的主體:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"tags2": [
"aaa",
"aaaa"
],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
3.10 額外的數據類型
目前為止我們看到的常用的數據類型有int,str,float,bool
除此之外還以更多的混合數據類型
UUID
* 一個標准的“通用唯一標識符”,在許多數據庫和系統中通常作為ID使用。
*在請求和響應中將以str
表示。
datetime.datetime 例如:2008-09-15T15:53:00 + 05:00
datetime.date: 例如:2008-09-15
datetime.time: 例如: 14:23:55.003
datetime.timedelta: 例如: Pydantic還允許將其表示為“ ISO 8601時間差異編碼
# 額外的數據類型
from datetime import datetime, time, timedelta
from uuid import UUID
from uuid import uuid1
from fastapi import Body, FastAPI
app = FastAPI()
# 額外數據類型
# https://fastapi.tiangolo.com/tutorial/extra-data-types/
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: datetime = Body(None),
end_datetime: datetime = Body(None),
repeat_at: time = Body(None),
process_after: timedelta = Body(None),
):
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"repeat_at": repeat_at,
"process_after": process_after,
"start_process": start_process,
"duration": duration,
}
if __name__ == '__main__':
import uvicorn
print('uuid:', uuid1())
uvicorn.run(app, host="127.0.0.1", port=8000)


本章具體解釋請看官方文檔 # https://fastapi.tiangolo.com/tutorial/extra-data-types/
3.11 cookie參數和header參數
cookie demo
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(*, ads_id: str = Cookie(None)):
return {"ads_id": ads_id}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
header demo
from fastapi import FastAPI, Header
from typing import List
app = FastAPI()
# 要聲明標頭,您需要使用Header,否則參數將被解釋為查詢參數。
@app.get("/items/")
async def read_items(*, user_agent: str = Header(None), users_agent: str =Header(None)):
return {"User-Agent": user_agent}, {"AAAAA": user_agent}, {'ABCD': users_agent}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
3.12 響應模型 Response Model
可以在任何路徑操作中使用參數response_model
聲明用於響應的模型
@app.get(), @app.post, @app.put @app.delete 等
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
tags: List[str] = []
@app.post("/items/", response_model=Item) # 響應輸出pydantic模型
async def create_item(item: Item):
return item
注意,response_model是“decorator”方法的參數(get,post等)。不像所有參數和主體一樣,具有路徑操作功能。
它接收的類型與您為Pydantic模型屬性聲明的類型相同,因此它可以是Pydantic模型,但也可以是例如一個list
的Pydantic模型,例如List[Item]
FastAPI將使用此response_model進行以下操作:
- 把輸出數據轉化為類型聲明的格式
- 驗證數據
- 給響應添加一個json 模式, 在OpenAPI的路徑操作下。
- 將由自動文檔系統使用
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str = None
# Don't do this in production!
@app.post("/user/", response_model=UserIn) # 聲明響應模型
async def create_user(*, user: UserIn):
return user # 同一模型聲明輸出
現在,每當瀏覽器使用密碼創建用戶時,API都會在響應中返回相同的密碼。
在這種情況下,這可能不是問題,因為用戶自己正在發送密碼。
但是,如果我們對另一個路徑操作使用相同的模型,則可能會將用戶的密碼發送給每個客戶端。
應該添加一個輸出模型
我們可以改用純文本密碼創建輸入模型,而沒有明文密碼則創建輸出模型:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel): # 輸入模型,在交互式文檔中輸入的
username: str
password: str
email: EmailStr
full_name: str = None
class UserOut(BaseModel): # 輸出模型 執行后所輸出的模型數據
username:str
email: EmailStr
fill_name: str = None
# Don't do this in production!
@app.post("/user/", response_mode:UserOut) # 聲明響應模型
async def create_user(*, user: UserIn):
return user # 同一模型聲明輸出
因此,FastAPI將負責過濾掉未在輸出模型中聲明的所有數據(使用Pydantic)。
在文檔中查看
當您看到自動文檔時,可以檢查輸入模型和輸出模型是否都具有自己的JSON模式:
輸入的模型數據
輸出的數據
概括
使用路徑操作修飾器的參數response_model
定義響應模型,尤其是確保私有數據被過濾掉。
3.13 擴展模型
繼續前面的示例,通常會有多個相關模型
用戶模型尤其如此,因為:
input model 需要有一個密碼
Output model 不需要密碼
databases model 可能需要一個哈希密碼
減少代碼重復是FastAPI的核心思想之一。
隨着代碼重復的增加,錯誤,安全問題,代碼不同步問題(當您在一個地方進行更新,而不是在另一個地方進行更新)等問題的可能性也增加了。
這些模型都共享大量數據,並且復制屬性名稱和類型。我們可以聲明一個UserBase模型作為其他模型的基礎。
然后,我們可以使該模型的子類繼承其屬性(類型聲明,驗證等)。
這樣,我們可以只聲明模型之間的差異(使用純文本密碼,使用hashed_password和不使用密碼):
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str = None
# UserBase 作為模型的基礎,其繼承,相同的數據不要寫 減少代碼重復是FastAPI的核心思想之一
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
class UserIn(UserBase):
password: str
# 進行哈希密碼
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
# 保存用戶
def fake_save_user(user_in: UserIn):
hash_password = fake_password_hasher(user_in.password)
# 保存用戶
# **user_id.dict() 表示一個完整的模型數據dict **稱之為一個關鍵字參數
user_in_db = UserInDB(**user_in.dict(), hashed_password=hash_password) # 偽哈希加密
print("user saved! ... not really")
print(user_in_db)
return user_in_db
@app.post("/user/", response_model=UserOut) # 響應輸入模型
async def create_user(*, user_in: UserIn): # 響應輸出模型
user_saved = fake_save_user(user_in)
return user_saved
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
可以進行交互式文檔查看
響應模型列表
同樣,您可以聲明對象列表的響應。
為此,請使用標准的Python typing.List
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
得到的響應結果
200 ok
{
"name": "Foo",
"description": "There comes my hero"
},
{
"name": "Red",
"description": "It's my aeroplane"
}
]
同樣你可以帶dict中的響應
概括
使用多個Pydantic模型,並針對每種情況自由繼承。
如果每個實體必須能夠具有不同的“狀態”,則無需為每個實體擁有單個數據模型。以用戶“實體”為例,其狀態包括password,password_hash和無密碼。
3.14 響應狀態碼
響應狀態碼我們可以在任何路徑操作中使用status_code 聲明用於響應的HTTP狀態代碼
例:
from fastapi import FastAPI
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
每一個狀態表示着不同操作
我們還可以用 fastapi.status的便捷變量
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}
3.15 表單數據
安裝 pip install python-multipart
導入
from fastapi import FastAPI, Form
使用正向或查詢的方式創建表單參數
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(*, username: str = Form(...), password: str = Form(...)):
return {"username": username}
在登錄注冊項 或許需要發送username
和password
作為表單字段
規范要求這些字段必須准確命名為username
和password
,並作為表單字段(而不是JSON)發送。
3.16 請求文件
我們可以使用文件定義客戶端要上傳的文件
安裝:pip install python-multipart
導入 File
from fastapi import FastAPI, File, UploadFile
定義File參數
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}
這些文件將作為“表單數據”上傳。
如果將路徑操作函數參數的類型聲明為字節(bytes),FastAPI將為您讀取文件,並且您將接收內容(字節)。
請記住,這意味着全部內容將存儲在內存中。這對於小文件將很好地工作。
但是在某些情況下,您可以從使用UploadFile中受益。
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}
該返回文件名
3.17 錯誤處理
在許多情況下,您需要將錯誤通知給正在使用API的客戶端。
該客戶端可以是帶有前端的瀏覽器,其他人的代碼,IoT設備等。
我們需要告知客戶端: 客戶端沒有足夠的權限進行該操作。客戶端無權訪問該資源。客戶端嘗試訪問的項目不存在。
在這些情況下,您通常會返回400(從400到499)范圍內的HTTP狀態代碼。
使用 HTTPException
返回有錯誤的http響應給客戶端使用 HTTPException
導入 from fastapi import FastAPI, HTTPException
HTTPException
是普通的Python異常,其中包含與API相關的其他數據。
因為它是Python異常,所以您不返回它,而是raise
它。
在此示例中,當客戶端通過不存在的ID請求商品時,引發狀態代碼為404的異常:
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@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"
headers={"X-Error": "There goes my error"}) # 自定義請求頭
# X-Error 自定義。例如,對於某些類型的安全性。OAuth 2.0和某些安全實用程序在內部需要/使用此功能。
return {"item": items[item_id]}
結果響應
如果客戶端請求http://example.com/items/foo
(item_id
為“bar”),則他將收到HTTP狀態代碼200和JSON響應:
{
"item": "The Foo Wrestlers"
}
但是,如果客戶端請求http://example.com/items/bar
(不存在的item_id
“ bar”),則他將收到HTTP狀態碼404(“找不到”錯誤)和JSON響應:
{
"detail": "Item not found"
}
3.18 路徑操作配置
您可以將幾個參數傳遞給路徑操作裝飾器以對其進行配置。
注意,這些參數直接傳遞給路徑操作裝飾器,而不是傳遞給路徑操作函數
官方文檔: https://fastapi.tiangolo.com/tutorial/path-operation-configuration/
3.20 JSON兼容編碼
在某些情況下,您可能需要將數據類型(例如Pydantic模型)轉換為與JSON兼容的數據(例如dict,list等)。
例如,如果您需要將其存儲在數據庫中。
為此,FastAPI提供了jsonable_encoder()
函數。
假設您有一個僅接收JSON兼容數據的數據庫fake_db
例如,它不接收日期時間對象,因為它們與JSON不兼容。
因此,datetime對象必須轉換為包含ISO格式數據的str。
同樣,該數據庫將不會接收Pydantic模型(具有屬性的對象),而只會接收dict
。
您可以為此使用jsonable_encoder
。
它接收一個對象,如Pydantic模型,並返回JSON兼容版本:
from datetime import datetime
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
fake_db = {}
class Item(BaseModel):
title: str
timestamp: datetime
description: str = None
app = FastAPI()
@app.put("/items/{id}")
def update_item(id: str, item: Item):
json_compatible_item_data = jsonable_encoder(item)
fake_db[id] = json_compatible_item_data
# jsonable_encoder實際上由FastAPI在內部用於轉換數據。但這在許多其他情況下很有用。
print(json_compatible_item_data)
print(type(json_compatible_item_data))
print(fake_db)
print(type(fake_db))
return fake_db
# 在此示例中,它將Pydantic模型轉換為dict,並將日期時間轉換為str。
#調用它的結果是可以用Python標准json.dumps()進行編碼的東西、。
3.21 body更新
官方文檔:https://fastapi.tiangolo.com/tutorial/body-updates/
3.22 FastAPI 依賴部分
FastAPI有一個非常強大但直觀的依賴注入
系統。
依賴是在函數的執行前調用
當進行需要執行依賴操作時,這將非常有用,前提是:
- 具有共享邏輯(一次又一次地使用相同的代碼邏輯)。
- 共享數據庫連接
- 強制執行安全性,身份驗證,角色要求等
所有這些,同時最大限度的減少了代碼的重復
創建一個依賴項或“ dependable”
讓我們首先關注依賴關系
它只是一個函數,可以采用路徑操作函數可以采用的所有相同參數:
from fastapi import Depends, FastAPI
app = FastAPI()
# 被依賴的函數
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# Depends 接收的參數必須是類似於函數
# 這里的 Depends 當每次請求前會調用 Depends()中的依賴項,
# 從這個依賴項函數中獲取結果,最終將該結果分配給路徑操作函數中的參數,也就是 common參數
# 這樣,我們只需編寫一次共享代碼,FastAPI便會為路徑操作調用共享代碼。
@app.get("/items/")
async def read_items(common: dict = Depends(common_parameters)):
common["test"] = "test"
return common
@app.get("/users/")
async def read_users():
return {"code": 200, "message": "ok"}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
向路徑操作裝飾器添加依賴項
from fastapi import Depends, FastAPI, 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")
return x_key
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
這些依賴將會在安全會中起到很大用處
3.23 FastAPI 中間件
“中間件”是一種功能,該功能可在任何請求通過任何特定路徑操作處理之前與每個請求一起使用。以及返回之前的每個響應。
要創建中間件,請在函數頂部使用修飾符@app.middleware("http")
。
import time
from fastapi import FastAPI, Request
app = FastAPI()
# 中間件,該功能在任何路徑操作之前,先執行該中間件
# 計算http請求和生成響應所花費的時間
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def main():
return {"message": "Hello World"}
例如添加CORSMiddleware
中間件
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
# app.add_middleware()`接收中間件類作為第一個參數,並接收任何其他要傳遞給中間件的參數。
app.add_middleware( # 添加中間件
CORSMiddleware, # CORS中間件類
allow_origins=origins, # 允許起源
allow_credentials=True, # 允許憑據
allow_methods=["*"], # 允許方法
allow_headers=["*"], # 允許頭部
)
@app.get("/")
async def main():
return {"message": "Hello World"}
有關CORS的更多信息,請查看Mozilla CORS文檔。
3.24 FastAPI Cors 跨越請求
CORS或“跨源資源共享”是指瀏覽器中運行的前端具有與后端進行通信的JavaScript代碼,並且后端與前端具有不同的“來源”的情況。
Origin是協議(http
,https
),域(myapp.com
,localhost
,localhost.tiangolo.com
)和端口(80
、443
、8080
)的組合。
使用 CORSMiddleware
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware( # 添加中間件
CORSMiddleware, # CORS中間件類
allow_origins=origins, # allow_origins=['*'], # 允許起源所有
allow_credentials=True, # 允許憑據
allow_methods=["*"], # 允許方法
allow_headers=["*"], # 允許頭部
)
@app.get("/")
async def main():
return {"message": "Hello World"}
3.25 FastAPI 大應用-多個文件
如果要構建應用程序或Web API,則很少將所有內容都放在一個文件中。 FastAPI提供了一種方便的工具,可在保持所有靈活性的同時構建應用程序。
相當與flask中的藍圖
文件結構
fastapi-nano
├── app # primary application folder
│ ├── apis # this houses all the API packages
│ │ ├── api_a # api_a package
│ │ │ ├── __init__.py # empty init file to make the api_a folder a package
│ │ │ ├── mainmod.py # main module of api_a package
│ │ │ └── submod.py # submodule of api_a package
│ │ └── api_b # api_b package
│ │ ├── __init__.py # empty init file to make the api_b folder a package
│ │ ├── mainmod.py # main module of api_b package
│ │ └── submod.py # submodule of api_b package
│ ├── core # this is where the configs live
│ │ ├── auth.py # authentication with OAuth2
│ │ ├── config.py # sample config file
│ │ └── __init__.py # empty init file to make the config folder a package
│ ├── __init__.py # empty init file to make the app folder a package
│ ├── main.py # main file where the fastAPI() class is called
│ ├── routes # this is where all the routes live
│ │ └── views.py # file containing the endpoints of api_a and api_b
│ └── tests # test package
│ ├── __init__.py # empty init file to make the tests folder a package
│ └── test_api.py # test files
├── docker-compose.yml # docker-compose file
├── Dockerfile # dockerfile
├── LICENSE # MIT license
├── Makefile # Makefile to apply Python linters
├── mypy.ini # type checking configs
├── requirements.txt # app dependencies
└── requirements-dev.txt # development dependencies
這樣做的好處就是能夠將獨立功能的代碼分開來進行管理編寫
參照文檔: https://fastapi.tiangolo.com/tutorial/bigger-applications/
4 FastAPI進階內容
本節案例,請訪問 test_fastapi/FastAPI進階內容目錄
4.1 自定義響應
默認情況下FastAPI中將使用JSONResponse 返回響應
但是除了JSONResponse 響應之外,還有 Response,HTML Response,RedirectResponse(重定向)響應等
HTML Response 響應
要直接從FastAPI返回帶有HTML的響應,請使用HTMLResponse。
- 導入HTMLResponse。
- 將HTMLResponse作為路徑操作的參數content_type傳遞。
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
參數response_class
也將用於定義響應的“媒體類型”。
HTTP標頭Content-Type將設置為text/html。
RedirectResponse 重定向響應
# 重定向
# 返回HTTP重定向,默認情況下使用307狀態代碼(臨時重定向)
@app.get("/typer")
async def read_typer():
return RedirectResponse("https://typer.tiangolo.com")
當我們訪問url時,默認情況會跳轉到我們指定的url
Response響應
Response 返回的是文本或字節
它接受以下參數:
- content
一個
str或
bytes`。 status_code
- 一個int
HTTP狀態碼headers
- 一個字符串dict
media_type
- 一個給定媒體類型的str
, 例如 "text/html
"
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
4.2自定義響應cookie
當我們在響應頭中響應cookie時,使用 response 參數
我們可以在路徑操作函數中聲明類型為Response的參數。然后,您可以在該時間響應對象中設置cookie
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/cookie-and-object/")
def create_cookie(response: Response):
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return {"message": "Come to the dark side, we have cookies"}
或者直接返回一個Response 響應
當我們直接在代碼中返回Response
時,也可以創建cookie。
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/cookie/")
def create_cookie():
content = {"message": "Come to the dark side, we have cookies"}
response = JSONResponse(content=content)
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return response
4.3 自定義設置響應頭
# 使用一個Response參數
# 您可以在路徑操作函數中聲明類型為Response的參數(就像對cookie一樣)。然后,您可以在該時間響應對象中設置標題。
import uvicorn
from fastapi import FastAPI, Response
from starlette.responses import JSONResponse
app = FastAPI()
@app.get("/headers-and-object/")
def get_headers(response: Response):
response.headers["X-Cat-Dog"] = "alone in the world"
return {"message": "Hello World"}
# 或者直接返回一個響應頭 -- 通常情況下會這樣使用
@app.get("/headers/")
def get_headers():
content = {"message": "Hello World"}
headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"}
return JSONResponse(content=content, headers=headers)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
# 自定義 Headers
# 請記住,可以使用“X-”前綴添加自定義專有標題。
#
# 但是,如果您希望瀏覽器中的客戶端看到自定義標頭,則需要使用Starlette's中記錄的參數Exposure_headers將它們添加到CORS配置中(在CORS(跨源資源共享)中了解更多信息)。 CORS文件。
4.4 HTTP的基本驗證
為了安全起見,我們可以使用最簡單的情況,可以使用HTTP基本身份驗證
使用簡單的 Http Basic Auth 來進行驗證,應該程序需要包含用戶名和密碼的標記來進行驗證,如果未驗證成功,則會返回HTTP 401 未經授權的錯誤。
- 導入
HTTPBasic
和HTTPBasicCredentials
. - 使用
HTTPBasic
創建一個"security
模式` - 在路徑操作中將此安全性與依賴項結合使用。
- 它返回HTTPBasicCredentials類型的對象:
- 它包含發送的用戶名和密碼。
import secrets
import uvicorn
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
# http驗證
security = HTTPBasic()
# 被依賴項函數,用於檢查用戶名和密碼是否正確
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
# 驗證用戶名和密碼
correct_username = secrets.compare_digest(credentials.username, "admin")
correct_password = secrets.compare_digest(credentials.password, "admin")
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
4.5 Request 對象
在FastAPI中 提供了Request 對象,在某些情況下,我們可能需要直接訪問Request請求對象做一些額外的事情,例如 獲取前端傳遞過來的參數。
假設您要在路徑操作功能中獲取客戶端的IP地址/主機。
為此,您需要直接訪問請求。
import uvicorn
from fastapi import FastAPI, Request, Form
app = FastAPI()
@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
client_host = request.client.host # 獲取客戶端的主機ip
client_path = request.url.path # 獲取當前url路徑
return {"client_host": client_host, "item_id": item_id, "path": client_path, "form": client_username}
# 通過聲明類型為Request的路徑操作函數參數,FastAPI將知道在該參數中傳遞Request。
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
具體詳情功能請訪問Request文檔
https://www.starlette.io/requests/
4.6 高級中間件
由於FastAPI基於Starlette並實現了ASGI規范,因此可以使用任何ASGI中間件。
Fastapi是基於Starlette的一個ASGI框架
ASGI,為了規范支持異步的Python Web服務器、框架和應用之間的通信而定制的一個協議
ASGI中間件定義
from unicorn import UnicornMiddleware
app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
FastAPI 集成了幾種常用的中間件
1 HTTPSRedirectMiddleware
強制所有傳入請求必須為https或wss。
app.add_middleware()
接收中間件類作為第一個參數,並接收任何其他要傳遞給中間件的參數。
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
# 強制所有傳入請求必須為https或wss。
app.add_middleware(HTTPSRedirectMiddleware)
@app.get("/")
async def main():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
FastAPI在fastapi.middleware中提供了幾種中間件,以方便開發人員。但是大多數可用的中間件直接來自Starlette。
為了查看其他的中間件,查看Starlette's Middleware docs和ASGI Awesome List
4.7 事件啟動與結束
您可以定義在應用程序啟動之前或在應用程序關閉時需要執行的事件處理程序(函數)。
startup事件 與 shutdown結束事件
from fastapi import FastAPI
app = FastAPI()
items = {}
# 程序啟動前要做的事情
@app.on_event("startup")
async def startup_event():
print("程序啟動之前執行")
items["foo"] = {"name": "Fighers"}
items["bar"] = {"name": "Tenders"}
@app.get("/items/{item_id}")
async def read_items(item_id: str):
return items[item_id]
# 程序結束后要做的事情
# shutdown
@app.on_event("shutdown")
def shutdown_event():
# 程序結束之后執行
with open("log.txt", mode="a") as log:
log.write("Application shutdown")
4.8 WebSocket
HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行及時通訊。
簡單示例 單個客戶端連接
查看文檔 :https://www.starlette.io/websockets/
main.py
from fastapi import FastAPI
from starlette.responses import HTMLResponse
from starlette.websockets import WebSocket, WebSocketDisconnect
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:5000/ws");
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
# @app.get("/")告訴FastAPI如何去處理請求
# 路徑 /
# 使用get操作
@app.get("/")
async def get():
# 返回表單信息
return HTMLResponse(html)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
# 接收連接數
await websocket.accept()
try:
while True:
# 接收文本
data = await websocket.receive_text()
# 發送文本數據
await websocket.send_text(f"Message text was: {data}")
# 當WebSocket連接關閉時,await websocket.receive_text()會引發WebSocketDisconnect異常,
except WebSocketDisconnect:
# 斷開連接
await websocket.close()
print("客戶端關閉成功")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=5000)
多個客戶端連接收發消息
from fastapi import FastAPI
from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocketDisconnect
info = {}
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off" placeholder="" />
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
document.getElementById("messageText").placeholder="第一次輸入內容為昵稱";
var ws = new WebSocket("ws://localhost:5000/ws");
// 接收
ws.onmessage = function(event) {
// 獲取id為messages的ul標簽內
var messages = document.getElementById('messages')
// 創建li標簽
var message = document.createElement('li')
// 創建內容
var content = document.createTextNode(event.data)
// 內容添加到li標簽內
message.appendChild(content)
// li標簽添加到ul標簽內
messages.appendChild(message)
};
var name = 0;
// 發送
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
if (name == 0){
document.getElementById("messageText").placeholder="";
name = 1;
}
}
</script>
</body>
</html>
"""
# HTTPEndpoint HTTP端點 提供了基於類的視圖模式來處理HTTP方法
class Homepage(HTTPEndpoint):
async def get(self, request):
# 接受一些文本或字節並返回HTML響應。
return HTMLResponse(html)
# WebSocketEndpoint WebSocketEndpoint
# 該WebSocketEndpoint 是一個ASGI應用呈現,封裝了WebSocket實例。
class Echo(WebSocketEndpoint):
encoding = "text"
# 修改socket
async def alter_socket(self, websocket):
socket_str = str(websocket)[1:-1]
socket_list = socket_str.split(' ')
socket_only = socket_list[3]
return socket_only
# 連接 存儲
async def on_connect(self, websocket):
# 接受連接
await websocket.accept()
# 用戶輸入名稱 receive_text() 接收資料
name = await websocket.receive_text()
socket_only = await self.alter_socket(websocket)
# 添加連接池 保存用戶名
info[socket_only] = [f'{name}', websocket] # 存儲到字典中
# 先循環 告訴之前的用戶有新用戶加入了
for wbs in info:
await info[wbs][1].send_text(f"{info[socket_only][0]}-加入了聊天室")
# 收發
async def on_receive(self, websocket, data):
socket_only = await self.alter_socket(websocket)
for wbs in info:
await info[wbs][1].send_text(f"{info[socket_only][0]}:{data}")
print(wbs)
# 斷開 刪除
async def on_disconnect(self, websocket, close_code):
try:
socket_only = await self.alter_socket(websocket)
# 刪除連接池
info.pop(socket_only)
print(info)
except WebSocketDisconnect:
await websocket.close()
routes = [
Route("/", Homepage),
WebSocketRoute("/ws", Echo)
]
app = FastAPI(routes=routes)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=5000)
5 FastAPI 認證
5.1 安全介紹
官方文檔:https://fastapi.tiangolo.com/tutorial/security/
5.2 Jwt 介紹
JSON Web Token(JWT) 是目前前后端分離開發中用戶身份認證的一種解決方案
這是將JSON對象編碼為長而密集的字符串且沒有空格的標准。看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ
如果要使用JWT令牌並查看其工作原理,請查看https://jwt.io。
JWT 認證流程:
- 用戶使用用戶名和密碼來請求服務器進行登錄
- 服務器驗證用戶的登錄信息
- 服務器通過驗證,生成一個token並返回給用戶
- 客戶端存儲token,並在每次驗證請求攜帶上這個token值
- 服務端驗證token值,並返回數據
5.3 使用密碼和JTW憑證進行認證
1 需要安裝依賴的包
安裝pyjwt PyJWT來生成和驗證Python中的JWT令牌
pip install pyjwt
密碼 passlib
PassLib是一個很棒的Python程序包,用於處理密碼哈希。
它支持許多安全的哈希算法和實用程序來使用它們。推薦的算法是“ Bcrypt”。
pip install passlib[bcrypt]
demo
from fastapi import Depends, FastAPI, HTTPException # , status
from starlette import status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
# 用戶數據(模擬)
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice@example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True, # 關閉此用戶
},
}
app = FastAPI()
# 哈希密碼(模擬)
def fake_hash_password(password: str):
return "fakehashed" + password
# oauth2_scheme是令牌對象,token: str = Depends(oauth2_scheme)后就是之前加密的令牌
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") # Bearer 持票人(承載、數據載體)
# 用戶信息模型
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None
# 用戶輸入模型
class UserInDB(User):
hashed_password: str # 哈希密碼
username: str # 此行多余,為了vscode不報錯而已。
# 獲取用戶
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
# 解碼令牌(模擬)
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
# 獲取當前用戶
# 如果用戶不存在或處於非活動狀態,則這兩個依賴項都將僅返回HTTP錯誤。
async def get_current_user(token: str = Depends(oauth2_scheme)):
print('oauth2_scheme', oauth2_scheme)
print('token', token)
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
# 獲取當前活躍用戶,get(read_users_me)專屬
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
@app.post("/token") # name = johndoe,alice password = secret,secret2
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# 使用username來自表單字段的從(假)數據庫獲取用戶數據。
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
# 先將這些數據放到Pydantic UserInDB模型中
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
# 如果正確返回
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__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
具體認證篇請看
6 數據庫
6.1 SQLAlchemy 數據庫
**1 ORM **
ORM 稱之為對象關系映射。
ORM 將數據庫中的表與面向對象中的類建立了一種對應關系。
當我們去操作數據庫,數據庫中的表或者表中的一條記錄就可以直接通過操作類或者類實例來完成
SQLAlchemy 對象
SQLAlchemy 是ORM 知名的工具之一,為高效和高性能的數據庫訪問設計。
SQLAlchemy 實際上就是利用Python來構建關系型數據庫結構與表達式的系統
SQLAlchemy 是對數據庫操作的封裝,讓開發者不用直接和sql打交道,而是通過python對象
詳情請看官方文檔 https://www.sqlalchemy.org/
6.2 alembic 數據庫遷移篇
alembic是用來做ORM模型與數據庫的遷移與映射。alembic使用方式跟git有點類似,表現在兩個方面,第一個,alemibi的所有命令都是以alembic開頭;
整體目錄
.
├── alembic.ini # alembic 配置文件
├── db # 數據庫鏈接配置
│ ├── __init__.py
│ └── base.py
├── alembic # 初始化倉庫
│ ├── README
│ ├── __pycache__
│ │ └── env.cpython-37.pyc
│ ├── env.py
│ ├── script.py.mako
│ └── versions
│ └── __pycache__
└── models # 數據庫模型
├── __init__.py
└── user_models.py
base.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
DB_URI = "mysql+pymysql://root:python12@127.0.0.1:3306/test6?charset=utf8"
engine = create_engine(DB_URI)
# 創建基類
Base = declarative_base(engine)
1 定義數據庫模型 以SQLAlchemy 為例子 假設為user用戶模型
user_models
# 數據庫模型所對應的SQLAlchemy
from sqlalchemy import Column,Integer,String,create_engine
from db.base import Base
class User(Base):
# 對應MySQL中數據表的名字
__tablename__ = 'users'
# 創建字段
id = Column(Integer, primary_key=True) # users表中的id字段(主鍵)
username = Column(String(64), nullable=False, index=True) # users表中的username字段
2 在終端中初始化,創建一個倉庫
alembic init alembic
初始化完成后,會生成一個alembic.ini 配置文件以及一個alembic目錄
3 修改配置文件
3.1 修改alembic.ini 配置文件,只修改數據庫連接部分即可
將
sqlalchemy.url = driver://user:pass@localhost:port/dbname
修改為
sqlalchemy.url = mysql+pymysql://root:python12@127.0.0.1:3306/test6
3.2 修改alembic/env.py
將
target_metadata = None
修改為
import sys
import os
# 1.__file__:當前文件(env.py)
# 2.os.path.dirname(__file__):獲取當前文件的目錄
# 3.os.path.dirname(os.path.dirname(__file__)):獲取當前文件目錄的上一級目錄
# 4.sys.path: python尋找導入的包的所有路徑
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from db.base import Base
from models import user_models # 這里要引入orm模型,不然遷移文件里面沒有表字段
target_metadata = Base.metadata # 添加模型的元數據對象
4 生成遷移腳本
# 由於提供了模型類,所以可以用–autogenerate參數自動生成遷移腳本。運行
alembic revision --autogenerate -m "alembic table init"
生成成功后會得到一個遷移文件 在 alembic/versions 目錄下
5 將生成的遷移腳本映射到數據庫中
alembic upgrade head
如果后續要添加模型,修改模型字段,重復4、5步驟即可,
降級數據庫的話
alembic downgrade 版本號
具體詳情請查看附件
https://alembic.sqlalchemy.org/en/latest/
6.3 SQLAlchemy 案例
具體詳情案例請訪問 test_fastapi/ databases 目錄下文件運行
from fastapi import FastAPI, Depends, HTTPException
from passlib.context import CryptContext
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from starlette import status
# ===================== 數據庫操作初始化 ====================
# 鏈接是需要指定要用到的MySQL數據庫
engine = create_engine('mysql+pymysql://root:python12@localhost:3306/test6?charset=utf8', echo=True)
Base = declarative_base() # 生成SQLORM基類
# SessionLocal類的每個實例將是一個數據庫會話。該類本身還不是數據庫會話。
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 創建session對象
session = SessionLocal() # 生成鏈接數據庫的實例
class User_My(Base): # 聲明數據庫某表的屬性與結構
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True) # 主鍵
email = Column(String(64), unique=True, index=True)
hashed_password = Column(String(128))
is_active = Column(Boolean, default=True)
# 執行下面的代碼,會創建userinfo表(所有表結構)
# Base.metadata.create_all(engine)
# 創建 pydantic 數據模型
class UserBase(BaseModel):
email: str
class UserCreate(UserBase): # 繼承子模型 UserBase
password: str
class User(UserBase):
id: int
is_active: bool
class Config:
orm_mode = True # Pydantic的orm_mode將告訴Pydantic模型讀取數據,即使它不是dict而是ORM模型
# 創建app應用
app = FastAPI()
# ==================== 數據庫操作 使用依賴項 ======================
def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
print("數據庫關閉")
# Context是上下文 CryptContext是密碼上下文 schemes是計划 deprecated是不贊成(強烈反對)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# 驗證哈希密碼
# verify_password驗證密碼 plain_password普通密碼 hashed_password哈希密碼
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# 獲取密碼哈希
def get_password_hash(password):
return pwd_context.hash(password)
# =================== 數據庫操作方法 =======================
# 通過id查詢用戶信息 查詢前先添加一個數據
def get_user(db: session, user_id: int):
users = db.query(User_My).filter(User_My.id == user_id).first()
print("users_id", users)
return users
# 新建用戶到數據庫中
def db_create_user(db: session, user: UserCreate):
# if user.email:
# raise HTTPException(status_code=400,
# detail="user exist")
# 1 對密碼進行加密
hashed_password = get_password_hash(user.password)
# 添加到數據庫中
db_user = User_My(email=user.email, hashed_password=hashed_password)
# 添加到session中
db.add(db_user)
# 添加到數據庫中
db.commit()
db.refresh(db_user) # 刷新
return db_user
# ============== post和get請求 ===============
# 新增用戶請求
@app.post("/users/", response_model=User)
def db_create_users(user: UserCreate, db: session = Depends(get_db)):
# Depends(get_db)使用依賴關系可防止不同請求意外共享同一鏈接
return db_create_user(db=db, user=user)
# 查詢用戶請求
@app.get("/users/{user_id}", response_model=User)
def read_user(user_id: int, db: session = Depends(get_db)):
db_user = get_user(db=db, user_id=user_id)
print(db_user)
if db_user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="User not found")
return db_user
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
6.4 tortoise-orm (數據庫)
1 tortoise-orm 介紹
Tortoise ORM是受Django啟發的易於使用的 異步 ORM (對象關系映射器)
**2 安裝 tortoise-orm **
pip install tortoise-orm
以及MySQL 或其他db驅動程序
pip install tortoise-orm[aiomysql]
pip install tortoise-orm[asyncpg]
3 tortoise-orm 案例
運行此案例在 test_fastapi/databases 目錄下運行
具體數據庫使用及功能請查看下方
tortoise-orm遷移: https://tortoise-orm.readthedocs.io/en/latest/migration.html
tortoise-orm文檔:https://tortoise-orm.readthedocs.io/en/latest/
7 FastAPI 日志管理
Loguru是一個旨在以Python帶來令人愉悅的日志記錄的庫。
Loguru的主要概念是只有一個日志記錄器。
pip install loguru
基本輸出日志
from loguru import logger
logger.debug("這是一條debug日志")
集成到FastAPI 新建了一個文件包extensions/
專門存放擴展文件
然后在文件目錄下創建了extensions/logger.py
文件, 簡單配置
logger.py
import os
import sys
import time
import traceback
from loguru import logger
# 項目所在目錄
# abspath 返回絕對路徑
# dirname 返回文件路徑
basedir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 定位到log日志文件中
log_path = os.path.join(basedir, 'logs')
# print(log_path)
# 如果文件夾不存在就新建
if not os.path.exists(log_path):
os.mkdir(log_path)
# 定義log文件名
log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log')
# 日志簡單配置
# 具體其他配置 可自行參考 https://github.com/Delgan/loguru
# retention Cleanup after some time
# rotation New file is created each day at noon
# enqueue 確保日志完整的輸出保存 異步寫入
logger.add(log_path_error, rotation="12:00", retention="5 days", enqueue=True)
_ init _.py
from extensions import logger
使用
# 得先在 extensions/__init__.py導入logger 才可以這樣導入logger
from extensions.logger import logger
logger.debug(f"日志記錄")
logger.info(f"日志記錄")
logger.error(f"xxx")
最后輸出在 logs文件中
具體詳情請看附件(文檔)
https://pypi.org/project/loguru/
8 FastAPI 緩存
本案例在 test_fastapi/fastapi-cache目錄下運行
1 安裝第三方緩存
pip install fastapi-cache
2 demo
from fastapi import FastAPI, Depends, HTTPException
import json
from fastapi_cache import caches, close_caches
from fastapi_cache.backends.redis import CACHE_KEY, RedisCacheBackend
app = FastAPI()
def redis_cache():
return caches.get(CACHE_KEY)
@app.get('/')
async def hello(
cache: RedisCacheBackend = Depends(redis_cache)
):
print(cache)
# 獲取redis對象
in_cache = await cache.get('some_cached_key')
if not in_cache:
cache_data = {
"name": "xiaoming",
"age": 20,
"address": "Shanghai"
}
# 設置redis對象
await cache.set('some_cached_key', json.dumps(cache_data))
return {'response': in_cache or 'default'}
# 清除緩存
@app.delete("/delete_redis_cache")
async def test_del_redis(cache: RedisCacheBackend = Depends(redis_cache)):
# 1 獲取redis對象
in_cache_redis = await cache.get("some_cached_key")
if in_cache_redis:
print(in_cache_redis)
# await cache.delete("some_cached_key")
else:
raise HTTPException(status_code=200, detail="未找到reids中的key")
return {"resposne": "ok"}
# 事件處理函數
# 程序啟動前要做的事情
@app.on_event('startup')
async def on_startup() -> None:
print("啟動")
rc = RedisCacheBackend('redis://127.0.0.1:6379')
caches.set(CACHE_KEY, rc)
# 程序結束后要執行的事情
@app.on_event('shutdown')
async def on_shutdown() -> None:
print("退出")
await close_caches()
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=5000)
9 FastAPI Users 用戶管理
本章案例在test_fastapi/FastAPI_User目錄下運行
fastapi-user 能夠快速向我們的FastAPI 項目添加注冊和身份驗證系統,FastAPI用戶旨在盡可能地自定義和適應。
2.1 特征
-
可擴展的基本用戶模型
-
即用型注冊,登錄,忘記和重置密碼路徑
-
即用型OAuth2流程
-
依賴可調用以將當前用戶注入路由
-
可定制的數據庫后端
- 得益於databases 數據庫包括 SQLALchemy異步后端
- 支持mongodb 異步后端
- Tortoise ORM backend included
-
多個可定制的身份驗證碼后端
- 包括JWT身份驗證后端
- 包括Cookie身份驗證后端
-
全面的OpenAPI架構支持,即使有多個身份驗證后端
2.2 安裝
具有SQLAlchemy支持
pip install fastapi-users[sqlalchemy]
包括Tortoise-orm支持
pip install fastapi-users[mongodb]
這里我們使用的tortoise-orm 數據庫來作為此案例
本章案例在 test_fastapi/fastapi-users 運行,結合文檔看詳情說明。
tortoise-orm遷移: https://tortoise-orm.readthedocs.io/en/latest/migration.html
fastapi-users文檔 https://frankie567.github.io/fastapi-users/
2.3 說明
混合提供了四種Pydantic模型變體
BaseUser
提供基礎領域和驗證;BaseCreateUser
,專門用於用戶注冊,由必填字段email
和password
必填字段組成;BaseUpdateUser
,專門用於用戶個人資料更新,其中添加了一個可選password
字段;BaseUserDB
,這是數據庫中用戶的表示形式,並添加了一個hashed_password
字段。
10 FastAPI 定期任務
本案例在 test_fastapi/fastapi-utils-tasks 目錄下運行
1 安裝 第三方模塊
pip install fastapi-utils
2 定時任務
啟動和關閉事件是觸發與服務器生命周期相關的操作的好方法。
但是,有時您希望任務不僅在服務器啟動時觸發,而且要定期觸發。例如,可能想定期重置內部緩存,或從數據庫中刪除過期的令牌。
可以通過 該fastapi_utils.tasks.repeat_every
裝飾實現定時任務
調用用@repeat_every(...)
修飾器修飾的函數時,將啟動循環,並以seconds
提供給修飾器的參數確定的延遲定期調用該函數。
例子:
from fastapi import FastAPI
from sqlalchemy.orm import Session
from fastapi_utils.session import FastAPISessionMaker
from fastapi_utils.tasks import repeat_every
# 創建app應用
app = FastAPI()
ret = 0
async def tasks_message():
global ret
ret = ret + 1
print(ret)
# 程序啟動后調用該函數
@app.on_event("startup")
@repeat_every(seconds=60 * 1) # 1 min 每一分鍾調用一次
async def remove_expired_tokens_task() -> None:
print("啟動")
# 調用函數
await tasks_message()
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
關鍵字參數
以下是針對以下各項的各種關鍵字參數的詳細說明repeat_every
:
seconds: float
:連續兩次呼叫之間等待的秒數wait_first: bool = False
:如果False
(默認值),則在第一次調用修飾函數時立即調用包裝函數。如果為True
,則裝飾函數將等待一個時間段,然后再調用包裝函數logger: Optional[logging.Logger] = None
:如果您通過記錄器,則在重復執行循環中引發的任何異常都將記錄(帶有回溯)到提供的記錄器中。
具體詳情文檔請點擊:https://fastapi-utils.davidmontague.xyz/user-guide/repeated-tasks/
11 FastAPI后台接口文檔
博客類型項目實現了基本的增刪改查
數據庫為SQLAlchemy ORM
前台目錄:
- 在blog-crud/frontend目錄下
- 安裝依賴: npm install
- 啟動開發: npm run dev
- 打包命令: npm run build
后台項目:
- 在blog-crud/fastapi-blog目錄下
- 數據庫為 SQLAlchemy ORM
1 接口文檔說明:
前端:http://127.0.0.1:8000/
后端API交互文檔地址:http://127.0.0.1:5000/redoc
2 登錄
2.1 登錄驗證接口
請求路徑: /api/v1/login/access-token
請求方法: post
請求參數
username 用戶名
password 密碼
響應參數
access_token 基於jwt的令牌
token_type 令牌類型
3 用戶管理
3.1 添加用戶
- 請求路徑: /api/v1/users
- 請求方法: POST
- 請求參數
- email 郵箱
- Username 用戶姓名
- Password 哈希密碼
- 響應參數
- email: 郵箱
- Username: 用戶姓名
- id:用戶id
- Is_active 激活狀態
- is_admin 管理員
4 首頁
4.1 首頁文章
請求路徑:GET api/v1/posts?page=1&limit=10 顯示所有文章
4.2 首頁分類
請求路徑:GET api/v1/categories 顯示所有分類
4.3 管理員
管理員(前提登錄並且是管理員) 在User模型上中is_admin字段改為True
請求路徑: /manage/posts 文章管理
請求路徑: /manage/post? 文章編輯/創建
請求路徑: /manage/categories 分類管理
請求路徑: /manage/category? 分類編輯/創建
4 后台管理
4.1 文章管理
-
請求路徑: GET /api/v1/posts/admin 后台顯示所有文章
-
請求路徑: POST /api/v1/posts/ 后台創建文章
-
請求參數:
-
title: 標題 content: 內容 cover_image 圖片 is_publish 是否發布 can_comment 是否評論 category_id 分類id(外鍵)
-
響應參數
content: 創建文章成功 201
4.2 分類管理
-
請求路徑: GET /api/v1/categories/ 顯示所有分類
-
請求路徑: POST /api/v1/categories/ 添加分類
-
請求參數: name: 分類姓名 img: 圖片
-
請求路徑: /api/v1/categories/<int: categories>
-
請求方法:DELETE
-
請求路徑參數: category_id 分類id
9 docker + Nginx 部署FastAPI
1 使用Pycharm 將本地項目上傳到遠程服務器
注意只有Pycharm專業版才具有部署的功能,社區版(無需破解)是沒有此功能
添加配置,起個名字
以上都配置好了,然后我們將我們本地的項目上傳到遠程服務器中
最后進入到我們的服務器查看上傳的項目。
2 docker部署項目
-
轉到您的項目目錄。
都需要在同一路徑中
-
創建一個
Dockerfile
:FROM python:3.7 # 復制文件到容器 # 將本地項目和依賴包添加到容器 /home 路徑 ADD requirements.txt /home ADD ./app /home # 跳轉到指定目錄 WORKDIR /home # 運行安裝 requirements.txt RUN pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com # 暴露端口 EXPOSE 5000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]
-
創建一個docker-compose
Compose項目是 Docker 官方的開源項目,負責實現對 Docker 容器集群的快速編排,同時也是對dockerfile進行管理
創建 docker-compose.yml 配置文件
version: '3' services: fastapi2: build: . # 這個是查找當前dockerfile文件進行構建 ports: - "5000:5000" mysql: image: mysql:5.7 expose: - 3306 ports: - "3307:3306" environment: MYSQL_ROOT_PASSWORD: python MYSQL_USER: root
-
運行構建鏡像命令
-
docker-compose up -d --build
用於部署一個 Compose 應用
默認情況下該命令會讀取名為 docker-compose.yml 或 docker-compose.yaml 的文件當然用戶也可以使用 -f 指定其他文件名。通常情況下,會使用 -d 參數令應用在后台啟動
-
docker-compose stop
停止 Compose 應用相關的所有容器,但不會刪除它們
被停止的應用可以很容易地通過 docker-compose restart 命令重新啟動
-
docker-compose rm
用於刪除已停止的 Compose 應用
它會刪除容器和網絡,但是不會刪除卷和鏡像
-
docker-compose restart
重啟已停止的 Compose 應用
如果用戶在停止該應用后對其進行了變更,那么變更的內容不會反映在重啟后的應用中,這時需要重新部署應用使變更生效
-
docker-compose ps
用於列出 Compose 應用中的各個容器
輸出內容包括當前狀態、容器運行的命令以及網絡端口
-
docker-compose down
停止並刪除運行中的 Compose 應用它會刪除容器和網絡,但是不會刪除卷和鏡像
最后當我們構建成功之后,會看到我們運行的程序,同時也可以訪問服務器ip和端口
-
4 nginx 轉發
nginx 是一個高性能HTTP服務器和反向代理服務器,最終的目的是實現網站的負載均衡;
nginx 作為web服務器,對處理索引文件和靜態文件效率非常高,例如我們項目中的靜態文件(HTML、CSS、JS)就交給nginx進行處理。
強大的反向代理和負載均衡功能,平衡集群中各個服務器的負載壓力
安裝nginx
sudo apt-get install nginx
2 運行(啟動)nginx
nginx
3 服務器查看運行狀態 或瀏覽器打開
3 停止/重啟 nginx
nginx -s stop/reload
4 查詢nginx路徑
nginx -t # 騰訊雲的同學要加sudo
# 第一個文件路徑是等會我們要去添加配置的文件
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
修改nginx.conf 文件 /etc/nginx/nginx.conf
使用sudo vim
注意重要的一點,這里部分截圖的最后有兩行文件
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
先說下原因: 因為nginx中有個默認配置文件default, 里面的默認監聽端口是80端口,那么我們需要在阿里雲配置的端口也是80端口,此時就會出現上我們自身配置的80端口是起沖突的,也就是說最終我們配置好了nginx信息,訪問的時候還是nginx最初的頁面 welcome to nginx
。 所以我們需要刪除掉nginx默認的配置文件,讓nginx訪問我們配置的端口信息。
步驟:
cd 進入 /etc/nginx/sites-enabled/
路徑下能夠看到有個default文件, 通過cat default
能夠看到默認配置信息, 我們需要做的就是 刪除 或者 對文件進行移動到上一層, mv deault ../
此時nginx就不會加載默認的配置了,通過curl 122.51.67.247:80
是不會訪問最初的配置了,而是顯示的拒絕連接的了。
添加需要配置的文件到 vim /etc/nginx/nginx.conf
配置信息
upstream app {
server 139.129.99.129:5000; # docker 配置
}
server {
listen 80;
location / {
proxy_pass http://app; # 添加到這里
}
配置好nginx信息后 通過 nginx -t 檢測配置是否正確
# 測試nginx配置文件是否正確
sudo nginx -t
# 如打印以下內容,表示配置沒有問題
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# 這時我們需要重新加載下nginx配置
sudo nginx -s reload
最后訪問我們服務器ip就可以了