データクラスしたい(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に対してカスタムバリデーターを定義しています。 datapd.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を継承したクラスを使うことで、 設定ファイルのバリデーションを実行しています。

設定クラス

ツールの設定を操作するクラスの作り方を整理します。

  1. 設定ファイルを定義する

    1. config.tomlのようなファイル

    2. サポートするファイル形式を選択する: TOML / YAML / JSON / CSV などなど

    3. 設定項目を決定する: label / summary / …

  2. 設定のデータクラスを定義する

    1. class Configのようなクラス

    2. クラス内のデータ構造のみを定義する

    3. ここでPydantic.BaseModelを継承すると便利

  3. 設定の操作クラスを定義する

    1. class ConfigLoaderのようなクラス

    2. 単一のデータクラスに、設定ファイルの値を読み込ませる

    3. BaseModelを継承していると読み込み値が検証できる

  4. 設定の管理クラスを定義する

    1. class ConfigReaderclass ConfigWriterのようなクラス

    2. 設定ファイルの検索や設定項目の表示などをまかせるクラス

 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/)