Fastapi學習總結(上)


簡介

FastAPI 是一個用於構建 API 的現代、快速(高性能)的 web 框架,使用 Python 3.6+ 並基於標准的 Python 類型提示。

關鍵特性:

  • 快速:可與 NodeJSGo 比肩的極高性能(歸功於 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, 可以看到返回結果

image-20210327110854188

查看交互式API文檔

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

image-20210327111145603

查看可選的api文檔

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

image-20210327112314401

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會自動檢測並返回錯誤信息, 如:

    image-20210327120852321

枚舉類型參數

# 導入枚舉模塊
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時需要繼承strEnum類, 表明枚舉值都是字符串類型, 下面的類變量即為枚舉變量和枚舉值
  • 參數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后定義了兩個參數pagecount, 這兩個參數並沒有對應的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模型類, 上面指明了類型和是否必輸

image-20210328091556266

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會自動識別他們中的每一個並從正確的位置獲取數據, 函數參數將依次按如下規則進行識別:

  • 如果在路徑中也聲明了該參數,它將被用作路徑參數。
  • 如果參數屬於單一類型(比如 intfloatstrbool 等)它將被解釋為查詢參數。
  • 如果參數的類型被聲明為一個 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}}

請求為:

image-20210328100918857

響應為:

image-20210328101025341

查詢參數的額外校驗-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

與使用 QueryPathBody路徑操作函數中聲明額外的校驗和元數據的方式相同,可以使用 Pydantic 的 Field Pydantic 模型內部聲明校驗和元數據。需要導入pydanticField

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.
  • datetime.date
    
    • Python datetime.date.
    • 在請求和響應中將表示為 ISO 8601 格式的 str ,比如: 2008-09-15.
  • datetime.time
    
    • 一個 Python datetime.time.
    • 在請求和響應中將表示為 ISO 8601 格式的 str ,比如: 14:23:55.003.
  • datetime.timedelta
    
    • 一個 Python datetime.timedelta.
    • 在請求和響應中將表示為 float 代表總秒數。
    • Pydantic 也允許將其表示為 "ISO 8601 時間差異編碼"
  • frozenset
    
    • 在請求和響應中,作為set對待:
      • 在請求中,列表將被讀取,消除重復,並將其轉換為一個 set
      • 在響應中 set 將被轉換為 list
      • 產生的模式將指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。
  • bytes
    
    • 標准的 Python bytes
    • 在請求和相應中被當作 str 處理。
    • 生成的模式將指定這個 strbinary "格式"。
  • Decimal
    
    • 標准的 Python Decimal
    • 在請求和相應中被當做 float 一樣處理
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 參數的結構與聲明 Query 參數和 Path 參數時相同。

第一個值是參數的默認值,同時也可以傳遞所有驗證參數或注釋參數,來校驗參數:

from fastapi import Cookie

@app.get('/cookies/')
async def get_cookies(user_id: Optional[str] = Cookie(None)):
    return {'user_id': user_id}

可以使用定義 Query, PathCookie 參數一樣的方法定義 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}
  • HeaderQuery, PathCookie 多了一個自動轉換的功能, 因為大多數標准的headers是用中划線-連接的, 但是中划線-不符合python中定義變量的規范, 因此默認情況下, fastapi會把函數中定義的Header參數中的下划線_自動替換成中划線-, 然后去請求的headers中尋找對應的header

  • 同時, HTTP headers是大小寫不敏感的, 所以在python中定義參數時無需考慮大寫, 可以全部使用小寫來命名, 例如http headers中的User-Agent, 對應fastapi函數的參數就可以寫為user_agent(大小寫和中划線都可以轉換)

  • 如果不想在某個參數上使用fastapi的自動轉換功能, 可以設置convert_underscores=False來取消自動轉換

  • Header支持接收重復的headers, 可以通過List類型來重復接收, 比如headers中有兩個同名header

    user_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模型UserOutUserIn多了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中只能有一種模型類型


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM