BaseModel / Config
===
###### tags: `Python / FastAPI`
###### tags: `Python`, `PyPi`, `backend`, `fastapi`
<br>
[TOC]
<br>
## orm mode
> - 新版已改用 `from_attribute = True`
### orm mapping
> (Object-Relational Mapping)
>
- **Question**
```python=
from pydantic import BaseModel, validator
class Prompt(BaseModel):
id: int
class Config:
orm_mode = True
```
orm_mode 是在幹麻用?
- **ChatGPT**
`orm_mode` 是 Pydantic 模型中的一個配置選項,用於將 SQLAlchemy ORM (Object-Relational Mapping) 物件轉換為 Pydantic 模型。
當你使用 FastAPI 與 SQLAlchemy 一起使用時,你通常會有一個 SQLAlchemy 模型(ORM 模型)用於映射數據庫表,以及一個 Pydantic 模型用於在 API 中定義輸入和輸出數據的格式。`orm_mode` 的作用是告訴 Pydantic 在轉換 ORM 物件時,按照 ORM 物件的屬性進行轉換。
在你的例子中,`Prompt` 是一個 Pydantic 模型,其中包含一個名為 `id` 的屬性,代表數據庫中的 ID。配置中的 `orm_mode = True` 表示在轉換時應該使用 ORM 模式,即按照 ORM 物件的屬性進行轉換。
例如,如果你有一個 SQLAlchemy 模型如下:
```python
from sqlalchemy import Column, Integer
class PromptModel(Base):
__tablename__ = "prompts"
id = Column(Integer, primary_key=True, index=True)
# 其他欄位...
```
當你從數據庫中檢索 PromptModel 物件,並希望將其轉換為 API 响應時,FastAPI 將使用 Pydantic 模型 `Prompt` 並應用 `orm_mode`,這樣 Pydantic 就知道按照 ORM 物件的屬性進行轉換。這使得在 API 中使用 Pydantic 模型時更加方便,而不需要手動定義每個屬性。
<br>
## 如何序列化成 JSON
### 討論
- ### 要如何序列化成 JSON ?
[ChatGPT4]
要將 Pydantic 模型的實例序列化成 JSON,你可以使用 Pydantic 提供的 `.json()` 方法。 這個方法將模型實例轉換成 JSON 格式的字串,這個過程會自動將 Python 的資料型別(包括列舉)轉換為 JSON 支援的格式。 這裡有一個具體的步驟和例子來說明如何操作:
#### 範例:序列化 Pydantic 模型
假設你有以下的模型和列舉類型:
```python=
from enum import Enum
from pydantic import BaseModel
# 定義列舉類型
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# 定義 Pydantic 模型
class Item(BaseModel):
color: Color
class Config:
use_enum_values = True # 確保在序列化時使用列舉的值
```
### 進行序列化
你可以建立 `Item` 的實例並呼叫 `.json()` 方法來進行序列化:
```python=
item = Item(color=Color.RED)
item_json = item.json()
print(item_json) # 輸出:{"color": "red"}
```
在這個例子中,`item_json` 是一個字串,內容是 JSON 格式。 由於 `Config` 中設定了 `use_enum_values` 為 `True`,`Color.RED` 被序列化為其值 `"red"` 而不是 `"Color.RED"`。
### 進階用法:自訂 JSON 序列化
如果需要自訂 JSON 序列化的行為(例如格式化時間戳記或處理複雜的資料類型),你可以在 `Config` 類別中進一步設定 `json_encoders`:
```python=
from datetime import datetime
from pydantic.json import timedelta_isoformat
class Event(BaseModel):
start_time: datetime
class Config:
json_encoders = {
datetime: lambda dt: dt.strftime('%Y-%m-%d %H:%M:%S')
}
event = Event(start_time=datetime.now())
print(event.json()) # 使用自定義的日期時間格式序列化
```
### 結論
使用 Pydantic 的 `.json()` 方法來序列化模型是一個簡潔且強大的方式,可以輕鬆地將 Python 中的複雜資料結構轉換為 JSON 格式。 這對於建構 RESTful API 和處理網路通訊特別有用。
<br>
<hr>
<br>
## use_enum_values vs json_encoders
### enum 用法
```python=
from enum import Enum
from pydantic import BaseModel
import json
# 定義枚舉
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# 定義模型
class Item(BaseModel):
color: Color
class Config:
use_enum_values = False # 確保使用枚舉值進行序列化
def my_json_convertor(v):
print(f'[debug] value: {v} (type: {type(v)}) (dir: {dir(v)})')
return v.value
# my_json_convertor 定義,要在使用前先定義
json_encoders = {
Color: my_json_convertor
}
# 單元測試使用 pytest
def test_serialize_item_color():
item = Item(color=Color.RED)
item_json = item.json()
print(f'item_json: {item_json}')
assert item_json == '{"color":"red"}'
item = Item(color=Color.GREEN)
item_json = item.json()
print(f'item_json: {item_json}')
assert item_json == '{"color":"green"}'
test_serialize_item_color()
```
- `use_enum_values = False`
```
[debug] value: Color.RED (type: <enum 'Color'>) (dir: ['__class__', '__doc__', '__module__', 'name', 'value'])
item_json: {"color":"red"}
[debug] value: Color.GREEN (type: <enum 'Color'>) (dir: ['__class__', '__doc__', '__module__', 'name', 'value'])
item_json: {"color":"green"}
```
- value 傳進來的 type 是 **Color enum**
- `use_enum_values = True`
```
[debug] value: red (type: <class 'str'>) (dir: ['__add__', '__class__', ......, 'upper', 'zfill'])
PydanticSerializationError: Error serializing to JSON:
PydanticSerializationError: Error calling function `my_json_convertor`:
AttributeError: 'str' object has no attribute 'value'
```
- value 傳進來的 type 是 **str**
- 應該是合理,`use_enum_values = True` 意味著值就是 "red" / "green" / "blue"
<br>
### 討論
- ### use_enum_values 沒有作用?
- **Background**
```python=
from enum import Enum
from pydantic import BaseModel
import json
# 定義枚舉
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# 定義模型
class Item(BaseModel):
color: Color
class Config:
use_enum_values = True # 確保使用列舉值進行序列化
# 單元測試使用 pytest
def test_serialize_item_color():
item = Item(color=Color.RED)
item_json = item.json()
assert item_json == '{"color": "red"}'
item = Item(color=Color.GREEN)
item_json = item.json()
assert item_json == '{"color": "green"}'
```
- **Question**
`use_enum_values` 設為 False,呼叫 item.json() 依舊輸出 {"color": "red"}
- **ChatGPT4**
如果您的設定是將 `use_enum_values` 設定為 `False`,而呼叫`item.json()` 依舊輸出 `{"color": "red"}` 而非預期的列舉成員名(例如 "`Color.RED`"),這可能 是由於Pydantic 預設行為或其他設定導致的。 讓我們澄清這一行為。
### 預設列舉行為
在 Pydantic 中,不論 `use_enum_values` 的設定如何,當對列舉類型進行 JSON 序列化時,Pydantic 預設是使用列舉值而非列舉的名稱。 這意味著,即使設定 `use_enum_values = False`,輸出也會是列舉的值(`"red"`、`"green"`、`"blue"`),而不是列舉的名稱(`"Color.RED"`、`"Color.GREEN"` 、`"Color.BLUE"`)。
### 解決方法
如果您希望在序列化時使用列舉的名稱而非值,您需要明確地在模型的 `Config` 中設定自訂的 JSON 編碼器。 這樣做可以讓您控制列舉如何被序列化,無論 `use_enum_values` 的設定如何:
```Python=
from enum import Enum
from pydantic import BaseModel
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
class Item(BaseModel):
color: Color
class Config:
use_enum_values = False
json_encoders = {
Color: lambda v: v.name # 使用列舉的名稱進行序列化
}
item = Item(color=Color.RED)
print(item.json()) # 預期輸出:{"color": "RED"}
```
在這個例子中,無論 `use_enum_values` 的設定為何,`json_encoders` 覆寫了預設的序列化行為,使得列舉成員在序列化時使用它們的名稱而非值。
### 總結
所以,當您發現設定 `use_enum_values = False` 時序列化的行為沒有改變,這是因為 Pydantic 的預設行為是使用列舉的值。 若需要改變此行為,可以透過自訂 `json_encoders` 在 `Config` 中明確指定。 這提供了靈活的方式來控制您的資料如何被序列化,特別是在處理列舉時。
<br>
## 如何驗證序列化後的 JSON ?
### 討論
- ### 針對 item.color , 我要如何驗證序列化的 JSON?
[ChatGPT4]
要驗證序列化後的 JSON 資料是否符合預期,你可以使用多種方法,包括單元測試和使用 Python 的 `assert` 語句來手動檢查。 以下是一些方法來確保 `item.color` 在序列化過程中處理正確:
### 方法一:單元測試
最佳實踐是為你的模型序列化過程編寫單元測試。 你可以使用 Python 的內建 unittest 框架或第三方函式庫如 pytest 來實現:
```Python=
from enum import Enum
from pydantic import BaseModel
import json
# 定義列舉
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# 定義模型
class Item(BaseModel):
color: Color
class Config:
use_enum_values = True # 確保使用列舉值進行序列化
# 單元測試使用 pytest
def test_serialize_item_color():
item = Item(color=Color.RED)
item_json = item.json()
assert item_json == '{"color": "red"}'
item = Item(color=Color.GREEN)
item_json = item.json()
assert item_json == '{"color": "green"}'
```
在這個測試中,我們建立了 Item 的實例,設定了 color 屬性為 Color.RED 和 Color.GREEN,並檢查序列化的 JSON 是否符合預期。
### 方法二:使用 assert 進行手動檢查
如果你只是偶爾需要檢查或在小型腳本中工作,可以直接使用 Python 的 assert 語句來手動檢查:
```Python=
item = Item(color=Color.BLUE)
item_json = item.json()
assert item_json == '{"color": "blue"}', "The serialized JSON does not match expected value"
# 輸出序列化 JSON 查看
print(item_json)
```
這段程式碼建立了一個 Item 實例,將 color 設為 Color.BLUE,並驗證產生的 JSON 字串是否正確。
### 方法三:使用自訂驗證器
如果需要更複雜的驗證邏輯,Pydantic 允許你在模型中定義自訂驗證方法:
```Python=
from pydantic import BaseModel, validator
class Item(BaseModel):
color: Color
@validator('color')
def check_color(cls, v):
if v not in Color:
raise ValueError('Invalid color')
return v
class Config:
use_enum_values = True
# 測試驗證器
try:
item = Item(color="pink") # 這應該會觸發一個錯誤
except ValueError as e:
print(e)
```
在上述方法中,我們用單元測試進行持續的品質保證是最推薦的方式,因為它可以在軟體開發的早期階段捕捉到問題,並且可以自動化。 這保證了當你修改模型結構或行為時,你的序列化邏輯仍然正確。
### 備註1:
- 使用 `@validator('color')` 會有 warning (舊版用法)
> PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/
- 新版用法:
`from pydantic import field_validator`
### 備註2:
- Line#8: `if v not in Color:` 產生底下錯誤:
> TypeError: unsupported operand type(s) for 'in': 'str' and 'EnumMeta'
- 一些比對方式:
```python=
def check_color(cls, v):
if isinstance(v, Color):
print('case1: type=Color')
if v not in Color.__members__:
raise ValueError('Invalid color')
elif isinstance(v, str):
print('case2: type=str')
if v not in [c.value for c in Color.__members__.values()]:
raise ValueError('Invalid color')
return v
```
- 實際上,`item = Item(color="pink")` 就會因為不合法而擋掉