本次的程式碼與目錄結構可以參考 FastAPI Tutorial : Day05 branch
- Typing
- Path parameter
- Query parameter
昨天都在談 Path parameter
和 Query parameter
的 Typing ,但是我們沒有談到 body
的部分
所以今天就先從 pydantic
開始談起吧 !
pydantic
是一個專門用來驗證資料類別的套件
如這個 User
class 的 id 必須為 int , name 必須為 str , birthday 必須為 date
並且 User
需要繼承 BaseModel
!
而這個專門用來驗證資料類的 class 我們稱之為 schema
測試 code 在 PydanticExample/test.py
from datetime import date
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
birthday: date
def __str__(self):
return f'User(id={self.id}, name={self.name}, birthday={self.birthday})'
try :
User(id=1, name='John', birthday=date(2000, 1, 1))
except Exception as e:
print(e)
try :
User(id='str', name='John', birthday=date(2000, 1, 1))
except Exception as e:
print(e)
如果我們在創建 User
的 instance 時,傳入的參數不符合 User
的 schema
那麼就會報錯
User(id=1, name=John, birthday=2000-01-01 00:00:00)
1 validation error for User
id
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='u_id', input_type=str]
For further information visit https://errors.pydantic.dev/2.3/v/int_parsing
@validator
可以用來驗證 schema
的資料
from datetime import date
from pydantic import BaseModel, validator
class User(BaseModel):
id: int
name: str
birthday: date
@validator('id')
def id_must_be_positive(cls, v):
if v < 0:
raise ValueError('id must be positive')
return v
@validator('name')
def name_must_be_capitalized(cls, v):
if v[0].islower():
raise ValueError('name must be capitalized')
return v
@validator('birthday')
def birthday_must_be_after_2000(cls, v):
if v.year < 2000:
raise ValueError('birthday must be after 2000')
return v
在這次的範例中,我們會使用到假資料
在 main.py
中,我們可以加入 fake_db
( 在正式專案中不會這樣寫! )
# from fastapi import FastAPI
# ...
# fake db
fake_db = {
"users": {
1: {
"name": "John",
"age": 35,
"email": "john@fakemail.com",
"birthday": "2000-01-01",
},
2: {
"name": "Jane",
"age": 25,
"email": "jane@fakemail.com",
"birthday": "2010-12-04",
}
},
"items": {
1: {
"name": "iPhone 12",
"price": 1000,
"brand": "Apple"
},
2: {
"name": "Galaxy S21",
"price": 800,
"brand": "Samsung"
}
}
}
# ...
# router
在 FastAPI 中,我們可以透過 schema
來定義 Body
一般我們都會建立 schemas
的資料夾,並根據 router
來分類
而 schemas
的 User
class
同樣要繼承 pydantic
的 BaseModel
並依據 User
的 schema
來定義
from datetime import date
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
birthday: date
同樣的,我們也可以在 schemas
中定義 Item
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
brand: str
完成後的目錄結構如下
schemas
├── items.py
└── users.py
接下來我們就可以在 router
中使用 schemas
來定義 Body
在 main.py
中引入剛剛定義的 schemas
# from fastapi import FastAPI
# from setting.config import get_settings
# 引入剛剛定義的 schemas !
from schemas.users import User as UserSchema
from schemas.items import Item as ItemSchema
# fake_db
# ...
再更新我們的 API
- users
@app.get("/users/{user_id}")
def get_users(user_id: int, qry: str = None):
if user_id not in fake_db["users"]:
return {"error": "User not found"}
return {"user": fake_db['users'][user_id], "query": qry }
@app.post("/users")
def create_users(user: UserSchema):
fake_db["users"][user.id] = user
return user
- items
@app.get("/items/{item_id}")
def get_items_without_typing(item_id, qry):
if item_id not in fake_db["items"]:
return {"error": "Item not found"}
return {"item": fake_db['items'][item_id], "query": qry }
@app.post("/items")
def create_items(item: ItemSchema):
fake_db["items"][item.id] = item
return item
先到 Swagger UI 看一下 create user 的 API
使用 get user 的 API 來看一下
可以看到我們剛剛新增的 user 已經在 fake_db 中了
使用 schema 可以確保我們的資料符合我們的預期 ( 因為 pydantic
會幫我們驗證 )
而透過 schema 產生的 Swagger UI 也可以幫助我們測試 API
也可以很清楚的看到 API 要帶入的 Body 格式
在前端開發時,可以很方便的參考 Swagger UI 來開發
我們會在明天的文章中談到
如何設定 API 的 response 的 schema