# FastAPI FastAPI 是基於 Python 用於開發網頁的後端框架。 ### FastAPI 的優點 - 速度最快 - 容易理解 - 驗證資料格式 - 自動產生文件 - 整合WebSocket雙向即時通訊 ### 使用 FastAPI 環境需求與設定 - 須具備 python 的程式設計基礎 - 安裝 python 3.10 以上的版本 - 安裝 Uvicorn ASGI Server - 安裝 FastAPI - 撰寫後端程式 - 執行、測試網站運作 ## 安裝 Uvicorn ASGI Server ### Uvicorn 是用來處理前後端請求與回應的細節 首先去python的官網下載3.10以上的版本,接著開啟VScode的終端輸入 ``` pip install "uvicorn[standard]" ``` ## 安裝FastAPI ### 在VScode的終端輸入 ``` pip install "fastapi[all]" ``` ## 快速開始FastAPI ```python= from fastapi import FastAPI app = FastAPI() #創建一個FastAPI的物件 @app.get("/") #當訪問'/'這個路徑時會呼叫底下index這個函式 async def index(): return {"x" : 1, "y" : 2} ``` ## 運行 FastAPI Server `--reload` 會在 App 更動後自動重載不需重新啟動。 ```bash! uvicorn main:app --reload ``` ## 載入 requirements.txt 依賴 ```bash! pip freeze > requirements.txt ``` ## 路徑參數 ### 路徑參數的設定 ```python= from fastapi import FastAPI app = FastAPI() @app.get('/users/{user_id}') #可在括號內設定路徑的參數 async def index(user_id: int): return {'user':f'This is the user for {user_id}'} ``` ### 在設定路徑參數時需要注意順序 :::info ### 舉例 下方程式碼中當路徑為 `./users/{一個整數}` 時會正確執行第一個函數,但是當想要執行第二個函數時 `./users/current` 則會出現問題,因為這個路徑也符合第一個函數所指定的路徑,但是函數內的參數為int所以無法正確執行程式。 ::: ```python= from fastapi import FastAPI app = FastAPI() @app.get('/users/{user_id}') async def index(user_id: int): return {'user':f'This is the user for {user_id}'} @app.get('/users/current') async def get_current_user(): return {'user':f'This is current user'} ``` 當我們把兩個函數的順序互換後,當輸入 `./users/current` 後會正確執行第一個函數,而當輸入 `./users/{一個整數}` 後因為不符合第一個函數的路徑,所以會跳過執行第二個。 ```python= from fastapi import FastAPI app = FastAPI() @app.get('/users/current') async def get_current_user(): return {'user':f'This is current user'} @app.get('/users/{user_id}') async def index(user_id: int): return {'user':f'This is the user for {user_id}'} ``` ## 查詢參數 `./users?` 問號後面接定義的查詢參數,若有多個參數則用`&`連接 ### 定義查詢參數 ```python= from fastapi import FastAPI app = FastAPI() @app.get('/users') async def get_users(page_index: int, page_size: int): #定義查詢參數 return {'page info':f'index: {page_index}, size: {page_size}'} ``` ### 可選查詢參數 使用Optional之後,使用者可以選擇要不要自己輸入數值給這個參數 ```python= from typing import Optional import uvicorn from fastapi import FastAPI app = FastAPI() @app.get('/users') #使用Optional來設定這個參數的預設值 async def get_users(page_index: int, page_size: Optional[int] = 30): return {'page info':f'index: {page_index}, size: {page_size}'} ``` ### 路徑參數與查詢參數混合使用 路徑參數和查詢參數在函數定義時可以直接定義且不必在乎順序, 分辨路徑參數和查詢參數的方法:不在路徑裡面定義的參數都是查詢參數。 ```python= from typing import Optional import uvicorn from fastapi import FastAPI app = FastAPI() @app.get('/users/{user_id}/friends') async def get_user_friends(page_index: int, user_id: int, page_size: Optional[int] = 30): return {'user friends' : f'user id: {user_id}, index: {page_index}, size: {page_size}'} ``` ## Request Body 當要發送資料給後端 API 時需要用到 Request Body 來發送, 不可使用 `GET` 發送 Request Body,發送 Request Body 的類型可以是 `POST`、`PUT`、`DELET`、`PATCH` ### `POST` 的範例 ::: info 註1:因為 user_model 是一個 UserModel 的類別,而函數只能 return JSON 格式的內容,所以使用 model_dump() 改成字典的格式。 ::: ```python= from typing import Optional import uvicorn from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() # 創建一個 UserModel 的類別,裡面包含 username 和 description class UserModel(BaseModel): username: str description: Optional[str] = "default" # Optional代表可填可不填 @app.post('/users') async def create_user(user_model: UserModel): print(user_model.username) user_dict = user_model.model_dump() #註1 return user_dict ``` ### 與路徑參數混合使用 使用 `PUT`,當需要修改資料時 ```python= from typing import Optional import uvicorn from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class UserModel(BaseModel): username: str description: Optional[str] = "default" @app.post('/users') async def create_user(user_model: UserModel): print(user_model.username) user_dict = user_model.model_dump() return user_dict @app.put('/user/{user_id}') # 加入路徑參數 async def update_user(user_id: int, user_model: UserModel): print(user_model.username) user_dict = user_model.model_dump() user_dict.update({'id': user_id}) # 將路徑參數增加到字典裏面 return user_dict ``` ### Request Body Model 嵌套 ```python= class Address(BaseModel): adress: str postcode: str class UserModel(BaseModel): username: str = Field(..., min_length=3) description: Optional[str] = Field(None, max_length=128) gender: Gender address: Address ``` ### 在 Model 內使用 `List` , `Set` :::info 須從 `typing` 裡面import `List` 和 `Set` ::: ```python= class Item(BaseModel): name: str feature: list #無法得知list裡面的資料型態 ``` ```python= class Item(BaseModel): name: str feature: List[str] #可以指定list裡面的資料型態 ``` ## FastAPI 如何識別參數類型 1. 如果在路徑裡面定義了,則為路徑參數。 2. 沒有在路徑中定義,而且在函數裡面定義的參數,且為 int、str 等基本類別。 3. 如果是 pydantic 的模型類型,則為請求體。 ## 參數驗證 ### 參數驗證的工具種類 * 路徑參數: fastapi.Path * 查詢參數: fastapi.Query ### 路徑參數驗證方法 ```python= from typing import Optional import uvicorn from fastapi import FastAPI, Path, Query from pydantic import BaseModel app = FastAPI() @app.get('/user/{user_id}') async def get_user( user_id: int = Path( ..., title='User ID', ge = 1, # greater than or equal le = 1000 # less then or equal ) ): return {'user': f'This is the user for {user_id}'} @app.get('/book/{book_name}') async def get_book( book_name: str = Path( ..., title = 'Book Name', min_length = 3, max_length = 10 ) ): return {'book info': f'This is a book for {book_name}'} @app.get('/items/{item_no}') #註3 async def get_item( item_no: str = Path( ..., title = 'Item No', regex = '^[a|b|c]-[\\d]*$' # 正則表達式 ) ): return {'Item Info': f'This is an item for {item_no}'} ``` ### 查詢參數驗證方法 使用 `Query` 來驗證 ::: success 註1: 因為查詢參數可以為必選項或可選項,當沒有預設值 `1` 而是 `...` 時,為必選項 ::: ```py= from typing import Optional import uvicorn from fastapi import FastAPI, Path, Query from pydantic import BaseModel app = FastAPI() @app.get('/users') async def get_user(page_index: int = Query( 1,#註1 title='Page Index', ge = 1, le = 1000 ) ): return {'user': f'Index:{page_index}'} ``` ### 驗證 Request Body Model 內屬性的驗證 使用 `pydantic` 裡面的 `Field` 可以對請求體內的屬性作驗證 ```python= from typing import Optional import uvicorn from fastapi import FastAPI, Body from pydantic import BaseModel, Field app = FastAPI() class User(BaseModel): #使用Field做驗證,字串長度不小於3 username: str = Field(..., min_length=3) description: Optional[str] = Field(None, max_length=10) class Item(BaseModel): name: str length: int @app.put('/carts/{cart_id}') async def update_cart(cart_id: int, user: User, item: Item, count: int = Body(..., ge=2)): print(user.username) print(item.name) result_dict = { "cartid": cart_id, "username": user.username, "itemname": item.name } return result_dict ``` ## 範例數據 範例數據是顯示在文件中的例子,給使用者提供可視化的樣板數據,便於用戶理解API的使用 #### 透過 `Field` 來定義範例數據 ```python= class Address(BaseModel): address: str = Field(..., examples=["5 Queen street"]) postcode: str = Field(..., examples=["0762"]) ``` #### 透過模型屬性 `model_config` 定義 透過 `model_config` 比透過 `Field` 定義優先 ```python= class Address(BaseModel): address: str = Field(..., examples=["5 Queen Street"]) postcode: str = Field(..., examples=['0765']) model_config = { "json_schema_extra": { "examples": [{ "address": "2 Queens Street", "postcode": "0987" }] } } ``` ## Cookie 與 Header 參數 `Cookie` 內通常存放一些介面的一些自訂信息 `Header` 通常存放一些使用者和驗證的信息 :::success 註1 : 在 Header 裡面定義的 key 最好使用 `-` 來分隔,因為用 `_` 可能被過濾掉,但是 `-` 在 python 裡面不符合命名規則,所以需要使用 alias 來指定別名 ::: ```python= from typing import Optional, Union import uvicorn from fastapi import FastAPI, Body, Cookie, Header from pydantic import BaseModel, Field app = FastAPI() @app.put('/carts') #註1 async def update_cart(*, favorite_schema: Optional[str] = Cookie(None, alias="favorite-schema"), api_token: Union[str, None] = Header(None, alias="api-token")): result_dict = { "favorite_schema": favorite_schema, "api_token": api_token } return result_dict ``` ## 響應模型 ## Reference - https://youtube.com/playlist?list=PLvQDgAXJ4ADP4G8Iuc02B11bvFokZMChK&si=jq_VHjVwsMctNSIQ --- - Contributor: 劉長諺、Ateto