# 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