[英]Use Pydantic child model to manage sets of default values for the parent model
我正在使用 pydantic 來管理支持不同數據集的應用程序的設置。 每個都有一組可覆蓋的默認值,但每個數據集它們是不同的。 目前,我通過驗證器正確實現了所有邏輯:
from pydantic import BaseModel
class DatasetSettings(BaseModel):
dataset_name: str
table_name: str
@validator("table_name", always=True)
def validate_table_name(cls, v, values):
if isinstance(v, str):
return v
if values["dataset_name"] == "DATASET_1":
return "special_dataset_1_default_table"
if values["dataset_name"] == "DATASET_2":
return "special_dataset_2_default_table"
return "default_table"
class AppSettings(BaseModel):
dataset_settings: DatasetSettings
app_url: str
這樣,我會根據dataset_name
獲得不同的默認值,但用戶可以在必要時覆蓋它們。 這是期望的行為。 問題是,一旦有多個這樣的字段和名稱,閱讀和維護就會變得一團糟。 似乎繼承/多態可以解決這個問題,但 pydantic 工廠邏輯似乎過於硬編碼,無法實現,尤其是嵌套模型。
class Dataset1Settings(DatasetSettings):
dataset_name: str = "DATASET_1"
table_name: str = "special_dataset_1_default_table"
class Dataset2Settings(DatasetSettings):
dataset_name: str = "DATASET_2"
table_name: str = "special_dataset_2_default_table"
def dataset_settings_factory(dataset_name, table_name=None):
if dataset_name == "DATASET_1":
return Dataset1Settings(dataset_name, table_name)
if dataset_name == "DATASET_2":
return Dataset2Settings(dataset_name, table_name)
return DatasetSettings(dataset_name, table_name)
class AppSettings(BaseModel):
dataset_settings: DatasetSettings
app_url: str
我考慮過的選項:
DatasetSettings
的__init__
,實例化子類並將其屬性復制到父 class 中。 有點笨重。dataset_settings_factory
覆蓋AppSettings
的__init__
以設置AppSettings
的dataset_settings
屬性。 不太好,因為默認行為在DatasetSettings
中根本不起作用,只有在AppSettings
中實例化為嵌套的 model 時才起作用。 我希望Field(default_factory=dataset_settings_factory)
可以工作,但default_factory
僅適用於實際默認值,因此它的參數為零。 是否有其他方法可以攔截特定 pydantic 字段的參數並使用自定義工廠?
另一種選擇是使用Discriminate/Tagged Unions 。
但是您的解決方案(沒有詳細查看)看起來也不錯。
我最終解決了第一個選項后的問題,如下所示。 代碼可使用 pydantic 1.8.2 和 pydantic 1.9.1 運行。
from typing import Optional
from pydantic import BaseModel, Field
class DatasetSettings(BaseModel):
dataset_name: Optional[str] = Field(default="DATASET_1")
table_name: Optional[str] = None
def __init__(self, **data):
factory_dict = {"DATASET_1": Dataset1Settings, "DATASET_2": Dataset2Settings}
dataset_name = (
data["dataset_name"]
if "dataset_name" in data
else self.__fields__["dataset_name"].default
)
if dataset_name in factory_dict:
data = factory_dict[dataset_name](**data).dict()
super().__init__(**data)
class Dataset1Settings(BaseModel):
dataset_name: str = "DATASET_1"
table_name: str = "special_dataset_1_default_table"
class Dataset2Settings(BaseModel):
dataset_name: str = "DATASET_2"
table_name: str = "special_dataset_2_default_table"
class AppSettings(BaseModel):
dataset_settings: DatasetSettings = Field(default_factory=DatasetSettings)
app_url: Optional[str]
app_settings = AppSettings(dataset_settings={"dataset_name": "DATASET_1"})
assert app_settings.dataset_settings.table_name == "special_dataset_1_default_table"
app_settings = AppSettings(dataset_settings={"dataset_name": "DATASET_2"})
assert app_settings.dataset_settings.table_name == "special_dataset_2_default_table"
# bonus: no args mode
app_settings = AppSettings()
assert app_settings.dataset_settings.table_name == "special_dataset_1_default_table"
在此過程中我發現了幾個問題:
Dataset1Settings
繼承自DatasetSettings
,它將進入一個遞歸循環,在 init 和 infinitum 上調用 init。 這可以通過一些內省來打破,但我選擇了鴨式方法。DatasetSettings
上的所有驗證器。 我確信無論如何都有一種調用驗證邏輯的方法,但是當前的解決方案通過僅使用super().__init__
進行初始化,有效地回避了您擁有的任何類級驗證BaseSettings
對象,但你必須拖動它們繁瑣的 init args: def __init__(
self,
_env_file: Union[Path, str, None] = None,
_env_file_encoding: Optional[str] = None,
_secrets_dir: Union[Path, str, None] = None,
**values: Any
):
...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.