データクラスしたい(pydantic
)
1from pydantic import BaseModel
2
3class UserSettings(BaseModel):
4 settings: str
5 """(必須)設定ファイル名"""
6
7 drive: str = ""
8 """(オプション)設定ファイルのディレクトリ名(相対パス)"""
9
10us = UserSettings(settings="設定ファイル名")
11print(us)
12# UserSettings(settings='設定ファイル名', drive='')
pydantic
でPythonの型ヒントをデータクラスのバリデーションができます。
pydantic.BaseModel
を継承するだけで、そのデータクラスをインスタンス化する時に、
型ヒントにしたがってバリデーションしてくれます。
また、データクラスの変数はFieldsで詳細に設定できます。
参考
Pydantic v1.10の過去ドキュメントのField Typesも参考になると思います。
設定したい(model_config
)
1from pydantic import BaseModel, ConfigDict
2
3class UserSettings(BaseModel):
4 model_config = ConfigDic(arbitrary_types_allowed = True)
5
6 settings: str
7 drive: str = ""
8 data: pd.DataFrame = pd.DataFrame()
9
10
11us = UserSettings("設定ファイル名")
12us
13# UserSettings(settings='設定ファイル名', drive='', data=Empty DataFrame)
14
model_config
でPydanticのモデル設定を変更できます。
値はConfigDict
で設定します。
設定できる項目は ConfigDictを参照してください。
上記サンプルでは、pd.DataFrame
を設定できるようにしました。
pd.DataFrame
はPythonのデフォルトの型ではないため、そのままでは使えませんが、
arbitrary_types_allowed = True
にすると使えるようになります。
再代入したい(validate_assignment
)
1from pydantic import BaseModel, ConfigDict
2
3class UserSettings(BaseModel):
4 model_config = ConfigDict(validate_assignment=True)
5 settings: str
validate_assignment=True
で、インスタンスに再代入したときにもバリデーションを追加できます。
注意
デフォルトでは、クラスをインスタンス化するときと、シリアライズするときにバリデーションが実行されます。 メンバー変数に値を再代入しただけでは、バリデーションは実行されないことに留意してください。
リスト型したい(list
)
1from pydantic import BaseModel, Field
2
3class UserSettings(BaseModel):
4 items: list[int] = Field(
5 default_factory=list,
6 min_items=1,
7 max_items=10,
8 description="整数のリスト",
9 )
list[int]
の型ヒントを設定したあとで、Field
で詳細に設定できます。
注釈
Python3.8以前ではtyping
モジュールのList[int]
を使う必要がありました。
Python3.9以降では組み込みのジェネリック型であるlist[int]
が推奨されています。
例外処理したい(ValidationError
)
1from pydantic import BaseModel, ValidationError
2
3class UserSettings(BaseModel):
4 name: str
5
6if __name__ == "__main__":
7 try:
8 us = UserSettings(name=100)
9 print(us)
10 except ValidationError as e:
11 print(e.errors())
pydantic.ValidationError
でバリデーションエラーの場合に例外処理できます。
errors()
メソッドで、エラーになった理由などの詳細が確認できます。
カスタムバリデーターしたい(field_validator
)
1from pydantic import BaseModel, field_validator
2import pandas as pd
3from typing import Any
4
5
6class UserSettings(BaseModel):
7 settings: str
8 drive: str
9 data: Any = pd.DataFrame({"timestamp": []})
10
11 @field_validator("data")
12 def check_dataframe(cls, field_value):
13 if not isinstance(field_value, pd.DataFrame):
14 raise ValueError("data must be a pandas.DataFrame")
15 required_columns = ["timestamp"]
16 if not all(col in field_value.columns for col in required_columns):
17 raise ValueError(
18 f"DataFrame must contain the following columns: {required_columns}"
19 )
20
21 return field_value
22
23us = UserSettings(settings="設定ファイル名", drive=".")
24# => OK
25
26us = UserSettings(settings="設定ファイル名", drive=".", data=pd.DataFrame({"time": []}))
27# => ValidationError
arbitrary_types_allowed = True
にすると、そのフィールドのバリデーションがスキップされます。
型安全を(ある程度)保ちたい場合は、field_validator
で自分でバリデーターを定義できます。
上記のサンプルでは、data
に対してカスタムバリデーターを定義しています。
data
がpd.DataFrame
であることと、必要なカラム名が存在することを確認しています。
確認に失敗した場合は、raise ValueError
を発生させ、説明を出力しています。
データクラスを出力したい(model_dump
/ model_dump_json
)
1us.model_dump()
2# {'settings': '設定ファイル名', 'drive': ''}
3
4us.model_dump_json()
5# '{"settings":"設定ファイル名","drive":""}'
model_dump
で、データクラスの中身を出力(=シリアライズ)できます。
シリアライズするときに型ヒントを使ったバリデーションが実行されます。
型ヒントと異なる値を代入していた場合はUserWarning
が表示されます。
注釈
arbitrary_types_allowed = True
としていたら
pd.DataFrame
はJSON形式にシリアライズできませんでした。
モデル設定を変更した場合は、注意が必要かもしれません。
プライベート変数したい
1from pydantic import BaseModel, ConfigDict, PrivateAttr
2
3
4class UserSettings(BaseModel):
5 model_config = ConfigDic(arbitrary_types_allowed = True)
6
7 settings: str
8 drive: str = ""
9 _data: pd.DataFrame = PrivateAttr()
10
11us = UserSettings("設定ファイル名")
12print(us)
13# UserSettings(settings='設定ファイル', drive='')
PrivateAttr()
を初期値に設定すると、メンバー変数を非表示にできます。
変数名は、_変数名
のようにsunder (single underscore)
する必要があります。
設定ファイルしたい
1import tomllib
2import datetime
3from pydantic import BaseModel
4
5class UserSettings(BaseModel):
6 string: str
7 integer: int
8 number: float
9 boolean: bool
10 local_datetime: datetime.datetime
11 local_date: datetime.date
12 local_time: datetime.time
13 array: list
14 table: dict
15 inline_table: dict
16 array_table: list
17
18# 設定ファイルはTOML形式
19# ここではTOMLのシンタックスを模した文字列
20
21settings_string = """
22[basic]
23string = "str"
24integer = 10
25number = 100.0
26boolean = true
27
28[datetime]
29local_datetime = "2024-08-28 12:34:56"
30local_date = "2024-08-28"
31local_time = "12:34:56"
32
33[arrays]
34array = ["array1", "array2", 0]
35inline_table = { k1 = "v1", k2 = "v2" }
36[arrays.table]
37k1 = "v1"
38k2 = 10
39
40[[arrays.array_table]]
41key = "key1"
42value = "value1"
43
44[[arrays.array_table]]
45key = "key2"
46value = "value2"
47"""
48
49# TOMLファイルを読み込む → dict型
50sd = tomllib.loads(settings_string)
51
52# 読み込んだ内容をフラットにする
53args = {**sd.get("basic", {}), **sd.get("datetime", {}), **sd.get("arrays", {})}
54
55# インスタンス化
56UserSettings(**args)
設定ファイルを読み込んで、クラスを初期化するケースはよくあります。
このサンプルでは、pydantic.BaseModel
を継承したクラスを使うことで、
設定ファイルのバリデーションを実行しています。
設定クラス
ツールの設定を操作するクラスの作り方を整理します。
設定ファイルを定義する
config.toml
のようなファイルサポートするファイル形式を選択する:
TOML
/YAML
/JSON
/CSV
などなど設定項目を決定する:
label
/summary
/ …
設定のデータクラスを定義する
class Config
のようなクラスクラス内のデータ構造のみを定義する
ここで
Pydantic.BaseModel
を継承すると便利
設定の操作クラスを定義する
class ConfigLoader
のようなクラス単一のデータクラスに、設定ファイルの値を読み込ませる
BaseModel
を継承していると読み込み値が検証できる
設定の管理クラスを定義する
class ConfigReader
、class ConfigWriter
のようなクラス設定ファイルの検索や設定項目の表示などをまかせるクラス
1class RsyncTask(BaseModel):
2 """A single configuration
3
4 Attributes:
5 label (str): Unique identifier of the task.
6 summary (str): Short description of the task.
7 host (str): Remove host (e.g., "user@host.example.com") or "none" for local.
8 source (str): Source path of rsync.
9 target (str): Target path of rsync.
10 options (List[str]): List of rsync options.
11 enabled (bool): Whether the task is enabled
12 """
13
14 label: str
15 summary: str
16 host: str
17 source: str
18 target: str
19 options: list[str]
20 enabled: bool = True
21
22 @property
23 def is_remote(self) -> bool:
24 return self.host.lower() != "none"
25
26 @property
27 def rsync_source(self) -> str:
28 return f"{self.host}:{self.source}" if self.is_remote else self.source
29
30 @property
31 def rsync_target(self) -> str:
32 return self.target
33
34
35class ConfigLoader(BaseModel):
36 """Configuration loader
37
38 This class loads and parsed configuration files
39 """
40
41 fetch: dict[str, RsyncTask] = {}
42 backup: dict[str, RsyncTask] = {}
43
44 def get_enabled_task(self, kind: Literal["fetch", "backup"]) -> list[RsyncTask]:
45 settings = getattr(self)
46 return [setting for setting in settings.values() if setting.enabled]
47
48 def get_task_by_label
49
50
51
52
53## リファレンス
54
55- [Pydantic - docs.pydantic.dev](https://docs.pydantic.dev/latest/)
56- [pydantic/pydantic - GitHub](https://github.com/pydantic/pydantic)
57- [Configuration](https://docs.pydantic.dev/dev/concepts/config/)
58- [Fields](https://docs.pydantic.dev/dev/concepts/fields/)
59- [field_validator](https://docs.pydantic.dev/dev/api/functional_validators/)