簡介
FastAPI 是一個用於構建 API 的現代、快速(高性能)的 web 框架,使用 Python 3.6+ 並基於標准的 Python 類型提示。
關鍵特性:
- 快速:可與 NodeJS 和 Go 比肩的極高性能(歸功於 Starlette 和 Pydantic)。最快的 Python web 框架之一。
- 高效編碼:提高功能開發速度約 200% 至 300%。*
- 更少 bug:減少約 40% 的人為(開發者)導致錯誤。*
- 智能:極佳的編輯器支持。處處皆可自動補全,減少調試時間。
- 簡單:設計的易於使用和學習,閱讀文檔的時間更短。
- 簡短:使代碼重復最小化。通過不同的參數聲明實現豐富功能。bug 更少。
- 健壯:生產可用級別的代碼。還有自動生成的交互式文檔。
- 標准化:基於(並完全兼容)API 的相關開放標准:OpenAPI (以前被稱為 Swagger) 和 JSON Schema。
安裝
uvicorn為ASGI服務器
pip install fastapi
pip install uvicorn
HelloWord
編寫腳本
創建一個名為hello_world.py的文件
from fastapi import FastAPI
# 創建fastapi的應用, 自定義名為app
app = FastAPI()
# url+http方法+執行函數
@app.get('/')
async def hello_world():
return {'hello': 'world'}
啟動服務
通過uvicorn啟動服務, 其中--reload是可選參數, 代碼修改后會自動重啟服務
uvicorn hello_world:app --reload
啟動結果如下則說明啟動成功, 默認是在8000端口啟動的, 地址為http://127.0.0.1:8000
$ uvicorn hello_world:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [189] using statreload
INFO: Started server process [191]
INFO: Waiting for application startup.
INFO: Application startup complete.
瀏覽器訪問接口
通過瀏覽器訪問http://127.0.0.1:8000, 可以看到返回結果

查看交互式API文檔
FastAPI會自動生成交互式API文檔, 通過網址http://127.0.0.1:8000/docs訪問, 這個可以方便開發人員更好地調試api, 也可以在文檔上寫入相關的注釋, 方便前后端開發的對接

查看可選的api文檔
除了上面的交互式api文檔外, fastapi還提供了另一個文檔, 通過http://127.0.0.1:8000/redoc訪問, 這個文檔只能查看, 不能交互

url路徑參數
基本用法
這里的路徑參數指的是url訪問資源的路徑中的參數, 比如根據restful規范, 定義訪問id為1的商品, 對應的url應該是/goods/1, 那么如果想訪問id為n的商品, url應該是/goods/n, 這個n就是路徑參數.
在fastapi中定義路徑參數的方法是:
@app.get('/items/{item_id}')
async def get_item_by_id(item_id: int):
return {'item_id': item_id}
-
item_id即為路徑參數名, 路徑參數需要用大括號括起來, 且在執行函數get_item_by_id中需要定義與路徑參數同名的參數item_id -
在定義函數參數
item_id時, 用到了python3.6+引入的"類型提示", 這樣能夠讓代碼和讀維護起來更加方便, 編輯器也能夠根據類型提供更多的支持, 推薦使用 -
有了類型提示后, 如果url傳入的參數不能轉換為int類型, 則fastapi會自動檢測並返回錯誤信息, 如:

枚舉類型參數
# 導入枚舉模塊
from enum import Enum
# 定義枚舉類
class BookType(str, Enum):
java = "Java"
python = "Python"
c_plus = "C++"
# 定義函數
@app.get('/books/{book_type}')
async def get_book_type(book_type: BookType):
result = {'book_type': book_type, 'message': ''}
if book_type == BookType.java:
result['message'] = "This is java"
elif book_type == BookType.python:
result['message'] = 'This is python'
elif book_type == BookType.c_plus:
result['message'] = 'This is c++'
return result
- 定義枚舉類
BookType時需要繼承str和Enum類, 表明枚舉值都是字符串類型, 下面的類變量即為枚舉變量和枚舉值 - 參數
book_type的類型注解為定義的枚舉類BookType
包含路徑的路徑參數
@app.get('/files/{file_path:path}')
async def get_file_path(file_path):
return {'file_path': file_path}
- 在url中定義參數路徑參數時, 如果該參數值本身也是一個路徑, 為了不會混淆, 可以在url參數后面添加
:path, 用來表示這個參數是一個路徑格式
查詢參數
查詢參數指的是在url后面添加的以?開頭並以&分隔的一系列鍵值對參數
在fastapi的函數中, 如果聲明的參數不屬於路徑參數, 那么它就會被自動解釋為查詢參數
from typing import Optional
@app.get('/items/')
async def get_query_param(page: int, count: Optional[int] = 10):
return {'page': page, 'count': count}
- 在函數
get_query_param后定義了兩個參數page和count, 這兩個參數並沒有對應的url路徑參數, 因此fastapi會認為這兩個參數是查詢參數 - 其中
page參數沒有指定Optional和默認值, 因此該參數被認為是必輸的 count參數指定了為Optional, 可選的, 並給了默認值, 那么說明這個參數不是必填的, 如果沒填, 就默認這個參數值為10
請求體
使用pydantic聲明請求體
# 導入pydantic基本模型
from pydantic import BaseModel
# 創建請求體的pydantic模型類
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.post('/items/')
async def create_item(item: Item):
# return item 可以直接返回pydantic類型的數據
# 也可以將pydantic轉為字典, 然后做后續的邏輯處理
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict['price_with_tax'] = price_with_tax
return item_dict
- 在定義pydantic模型類時, 和聲明查詢參數一樣, 可以設置Optional屬性指定其是否為 必輸字段
- 聲明執行函數的參數
item類型為上面定義的pydantic模型類 - fastapi會把傳入的請求體作為json讀取, 然后轉為定義的pydantic模型類, 並校驗數據的類型和是否必輸
在fastapi的交互式文檔的schemas中, 可以看到定義的pydantic模型類, 上面指明了類型和是否必輸

pydantic請求體 + 路徑參數 + 查詢參數
# 導入pydantic基本模型
from pydantic import BaseModel
# 創建請求體的pydantic模型類
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.put('/items/{item_id}')
async def update_book(item_id: int, item_type: str, item: Item):
return {'path_param': {'item_id': item_id}, 'query_param': {'item_type': item_type}, 'req_body': item}
可以同時聲明 路徑參數+查詢參數+請求體, fastapi會自動識別他們中的每一個並從正確的位置獲取數據, 函數參數將依次按如下規則進行識別:
- 如果在路徑中也聲明了該參數,它將被用作路徑參數。
- 如果參數屬於單一類型(比如
int、float、str、bool等)它將被解釋為查詢參數。 - 如果參數的類型被聲明為一個 Pydantic 模型,它將被解釋為請求體。
不使用pydantic聲明請求體-Body
使用fastapi的Body來定義單一的請求體參數, 在參數后面加上= Body(...)來表示這個參數是請求體參數, 如下面的item_name參數和item_price參數, 但是這樣只能一個一個定義請求體參數, 如果參數很多還是建議使用pydantic的模型類定義
from fastapi import Body
@app.patch('/items/{item_id}')
async def patch_book(item_id: int, item_type: str, item_name: str = Body(...), item_price: float = Body(...)):
return {'path_param': {'item_id': item_id}, 'query_param': {'item_type': item_type},
'req_body': {'item_name': item_name, 'item_price': item_price}}
請求為:

響應為:

查詢參數的額外校驗-Query
使用fastapi的Query類型, 在參數后面加上= Query(), 如限制最長不能超過5位, Query(max_length=5)
from fastapi import Query
@app.get('/query_items/')
async def query_items(item_type: Optional[str] = Query(None, max_length=5)):
result = {'items': [{'item_id': 1}, {'item_id': 2}]}
if item_type:
result['item_type'] = item_type
return item_type
點擊Query進入源碼可以查看Query所接受的參數
def Query( # noqa: N802
default: Any, # 參數默認值
*,
alias: Optional[str] = None, # 參數別名, 對應url中的查詢參數名
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None, # 參數值需大於...
ge: Optional[float] = None, # 參數值需大於等於...
lt: Optional[float] = None, # 參數值需小於...
le: Optional[float] = None, # 參數值需小於...
min_length: Optional[int] = None, # 參數最小長度...
max_length: Optional[int] = None, # 參數最大長度...
regex: Optional[str] = None, # 正則表達式限制...
deprecated: Optional[bool] = None, # 是否已經失效
**extra: Any,
) -> Any:
- default: Query的第一個參數, 表示該查詢參數的默認值, 如果設置了這個默認值的話, 那么就認為這個參數是可選參數(非必輸), 相當於
Optional[type] = default, 如果想要設置參數必輸, 那么就把default值設為Query(...)
查詢參數列表
from typing import List
@app.get('/list_items/')
async def get_list_items(q: Optional[List[str]] = Query(None)):
return {'q': q}
訪問urlhttp://127.0.0.1:8000/list_items/?q=1&q=2, 返回結果:
{
"q": [
"1",
"2"
]
}
- 在參數中使用了List類型后, fastapi會將該參數解析為請求體, 因此如果想要該參數為查詢參數, 那么需要顯式地使用
Query
路徑參數的額外校驗-Path
與使用Query聲明查詢參數類似, 路徑參數可以使用Path來聲明
from fastapi import Path
@app.get('/path_items/{item_name}')
async def get_path_items(item_name: str = Path('Python', max_length=6)):
return {'item_name': item_name}
Path的參數用法與Query一樣:
def Path( # noqa: N802
default: Any,
*,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
) -> Any
Pydantic的額外校驗-Field
與使用 Query、Path 和 Body 在路徑操作函數中聲明額外的校驗和元數據的方式相同,可以使用 Pydantic 的 Field 在 Pydantic 模型內部聲明校驗和元數據。需要導入pydantic的Field
from pydantic import Field
class MyItem(BaseModel):
item_id: int
item_name: str = Field(None, max_length=5)
@app.post('/field_items/')
async def get_field_items(my_item: MyItem):
return my_item
其他數據類型
-
UUID- 一種標准的 "通用唯一標識符" ,在許多數據庫和系統中用作ID。
- 在請求和響應中將以
str表示。
-
datetime.datetime- 一個 Python
datetime.datetime. - 在請求和響應中將表示為 ISO 8601 格式的
str,比如:2008-09-15T15:53:00+05:00.
- 一個 Python
-
datetime.date- Python
datetime.date. - 在請求和響應中將表示為 ISO 8601 格式的
str,比如:2008-09-15.
- Python
-
datetime.time- 一個 Python
datetime.time. - 在請求和響應中將表示為 ISO 8601 格式的
str,比如:14:23:55.003.
- 一個 Python
-
datetime.timedelta- 一個 Python
datetime.timedelta. - 在請求和響應中將表示為
float代表總秒數。 - Pydantic 也允許將其表示為 "ISO 8601 時間差異編碼"
- 一個 Python
-
frozenset- 在請求和響應中,作為
set對待:- 在請求中,列表將被讀取,消除重復,並將其轉換為一個
set。 - 在響應中
set將被轉換為list。 - 產生的模式將指定那些
set的值是唯一的 (使用 JSON 模式的uniqueItems)。
- 在請求中,列表將被讀取,消除重復,並將其轉換為一個
- 在請求和響應中,作為
-
bytes- 標准的 Python
bytes。 - 在請求和相應中被當作
str處理。 - 生成的模式將指定這個
str是binary"格式"。
- 標准的 Python
-
Decimal- 標准的 Python
Decimal。 - 在請求和相應中被當做
float一樣處理
- 標准的 Python
from datetime import datetime, time, timedelta
from uuid import UUID
@app.put('/others/{item_id}')
async def get_others(
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_process = 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_process,
'duration': duration,
}
Cookie
聲明 Cookie 參數的結構與聲明 Query 參數和 Path 參數時相同。
第一個值是參數的默認值,同時也可以傳遞所有驗證參數或注釋參數,來校驗參數:
from fastapi import Cookie
@app.get('/cookies/')
async def get_cookies(user_id: Optional[str] = Cookie(None)):
return {'user_id': user_id}
Header
可以使用定義 Query, Path 和 Cookie 參數一樣的方法定義 Header 參數。
from fastapi import Header
from typing import List
@app.get('/headers/')
async def get_headers(user_agent: Optional[str] = Header(None, convert_underscores=False), user_id: Optional[List[str]] = Header(None)):
return {'user_agent': user_agent, 'user_id': user_id}
-
Header比Query,Path和Cookie多了一個自動轉換的功能, 因為大多數標准的headers是用中划線-連接的, 但是中划線-不符合python中定義變量的規范, 因此默認情況下, fastapi會把函數中定義的Header參數中的下划線_自動替換成中划線-, 然后去請求的headers中尋找對應的header -
同時, HTTP headers是大小寫不敏感的, 所以在python中定義參數時無需考慮大寫, 可以全部使用小寫來命名, 例如http headers中的
User-Agent, 對應fastapi函數的參數就可以寫為user_agent(大小寫和中划線都可以轉換) -
如果不想在某個參數上使用fastapi的自動轉換功能, 可以設置
convert_underscores=False來取消自動轉換 -
Header支持接收重復的headers, 可以通過List類型來重復接收, 比如headers中有兩個同名headeruser_id: foo user_id: bar那么函數中的
user_id就為:{ "user_id": [ "bar", "foo" ] }
響應
基本使用
在@app.method()裝飾器中使用response_model參數來聲明用於響應的模型
from pydantic import EmailStr
# 請求體json模型
class UserIn(BaseModel):
user_name: str
pass_word: str
email: Optional[EmailStr] = None
full_name: Optional[str] = None
# 響應體json模型
class UserOut(BaseModel):
user_id: int
user_name: str
email: Optional[EmailStr] = None
full_name: Optional[str] = None
@app.post('/create_user/', response_model=UserOut)
async def create_user(user: UserIn):
user_dict = user.dict()
user_dict['user_id'] = 1
return user_dict
-
pydantic有自帶的郵箱格式校驗類型EmailStr, 不過需要額外進行安裝,pydantic只會安裝核心的功能pip install pydantic[email] -
這里定義的響應json模型
UserOut比UserIn多了user_id和少了pass_word字段 -
視圖函數中返回的
user_dict中是包括了pass_word字段的, 但是由於設置了response_model=UserOut, fastapi會過濾掉其他的字段
其他響應參數選擇
def post(...
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
...):
- response_model_include: 返回模型中需要包括哪些字段
- response_model_exclude: 返回模型中不能包括哪些字段
- response_model_exclude_unset: 設置為True的話那么響應模型中未顯式設置具體值的字段則不會返回
響應模型類-Union
可以將一個響應聲明為兩種類型的 Union,這意味着該響應將是兩種類型中的任何一種。
from typing import Union
class Student(BaseModel):
student_name: str
student_score: float
class Teacher(BaseModel):
teacher_name: str
teacher_num: str
items = {
'student': {'student_name': 'xiaoming', 'student_score': 90.3},
'teacher': {'teacher_name': 'li', 'teacher_num': 'NO.123'}
}
@app.get('/person/', response_model=Union[Student, Teacher])
async def get_person(item_type: str):
return items[item_type]
- 返回
Student模型類或者Teacher模型類
響應模型類-List
from typing import List
class Student(BaseModel):
student_name: str
student_score: float
students = [
{'student_name': 'xiaoming', 'student_score': 90.3},
{'student_name': 'xiaohong', 'student_score': 80.7}
]
@app.get('/students/', response_model=List[Student])
async def get_students():
return students
- 返回的是
Student模型類列表,List中只能有一種模型類型
