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")` 就會因為不合法而擋掉