# 厳密なデータクラス

`huggingface_hub` パッケージには、**厳密なデータクラス**を作成するためのユーティリティが提供されています。これらは、Python の標準的な `dataclass` の強化版であり、追加の検証機能を持っています。厳密なデータクラスは、初期化時と代入時の両方でフィールドが検証されることを保証するため、データの整合性が重要なシナリオに最適です。

## 概要

厳密なdataclassは`@strict`デコレーターを使用して作成されます。通常のdataclassの機能を以下のように拡張します:

- 型ヒントに基づいてフィールドの型を検証する
- 追加のチェック用のカスタムバリデータへの対応
- コンストラクタで任意のキーワード引数を許可するオプション
- 初期化時と代入時の両方でフィールドを検証する

## メリット

- **データ整合性**: フィールドが常に有効なデータを含むことを保証します
- **使いやすさ**: Pythonの`dataclass`モジュールとシームレスに統合
- **柔軟性**: 複雑な検証ロジックに対応するカスタムバリデーターをサポート
- **軽量**: Pydanticやattrsなどの追加の依存関係は不要です

## 使用方法

### 基本例

```python
from dataclasses import dataclass
from huggingface_hub.dataclasses import strict, as_validated_field

# Custom validator to ensure a value is positive
@as_validated_field
def positive_int(value: int):
    if not value > 0:
        raise ValueError(f"Value must be positive, got {value}")

@strict
@dataclass
class Config:
    model_type: str
    hidden_size: int = positive_int(default=16)
    vocab_size: int = 32  # Default value

    # Methods named `validate_xxx` are treated as class-wise validators
    def validate_big_enough_vocab(self):
        if self.vocab_size < self.hidden_size:
            raise ValueError(f"vocab_size ({self.vocab_size}) must be greater than hidden_size ({self.hidden_size})")
```

フィールドは初期化時に検証されます:

```python
config = Config(model_type="bert", hidden_size=24)   # Valid
config = Config(model_type="bert", hidden_size=-1)   # Raises StrictDataclassFieldValidationError
```

初期化時にもフィールド間の整合性が検証されます（クラス単位の検証）：

```python
# `vocab_size` too small compared to `hidden_size`
config = Config(model_type="bert", hidden_size=32, vocab_size=16)   # Raises StrictDataclassClassValidationError
```

フィールドは代入時にも検証されます:

```python
config.hidden_size = 512  # Valid
config.hidden_size = -1   # Raises StrictDataclassFieldValidationError
```

割り当て後にクラス全体の検証を再実行するには、`.validate`を明示的に呼び出す必要があります。

```python
config.validate()  # Runs all class validators
```

### カスタムバリデータ

`validated_field` を使用して、フィールドに複数のカスタムバリデータを追加できます。バリデータは、単一の引数を受け取り、値が無効な場合に例外を送出する呼び出し可能なオブジェクトです。

```python
from dataclasses import dataclass
from huggingface_hub.dataclasses import strict, validated_field

def multiple_of_64(value: int):
    if value % 64 != 0:
        raise ValueError(f"Value must be a multiple of 64, got {value}")

@strict
@dataclass
class Config:
    hidden_size: int = validated_field(validator=[positive_int, multiple_of_64])
```

この例では、両方のバリデータが`hidden_size`フィールドに適用されます。

### 追加のキーワード引数

デフォルトでは、strict dataclassesはクラス内で定義されたフィールドのみを受け入れます。`@strict`デコレーターで`accept_kwargs=True`を設定することで、追加のキーワード引数を許可できます。

```python
from dataclasses import dataclass
from huggingface_hub.dataclasses import strict

@strict(accept_kwargs=True)
@dataclass
class ConfigWithKwargs:
    model_type: str
    vocab_size: int = 16

config = ConfigWithKwargs(model_type="bert", vocab_size=30000, extra_field="extra_value")
print(config)  # ConfigWithKwargs(model_type='bert', vocab_size=30000, *extra_field='extra_value')
```

`*`プレフィックスが付けられた追加のキーワード引数は、dataclassの文字列表現に表示されますが、検証されないことを示すためにこのプレフィックスが付けられています。

### 型ヒントとの統合

Strict dataclassesは型ヒントを尊重し、自動的に検証します。例えば:

```python
from typing import List
from dataclasses import dataclass
from huggingface_hub.dataclasses import strict

@strict
@dataclass
class Config:
    layers: List[int]

config = Config(layers=[64, 128])  # Valid
config = Config(layers="not_a_list")  # Raises StrictDataclassFieldValidationError
```

サポートされている型には、以下が含まれます:
- すべて
- Union
- オプション
- リテラル
- リスト
- Dict
- タプル
- セット

これらの型の任意の組み合わせも可能です。より複雑な型の検証が必要な場合は、カスタムバリデータを通じて行うことができます。

### クラスバリデータ

`validate_xxx` という名前のメソッドは、クラスバリデータとして扱われます。これらのメソッドは、引数として `self` のみを指定する必要があります。クラスバリデータは、初期化時に `__post_init__` の直後に一度だけ実行されます。必要に応じて任意の数を定義でき、定義された順序で順次実行されます。

クラスバリデータは、初期化後にフィールドが更新されても自動的に再実行されません。オブジェクトを手動で再検証するには、`obj.validate()`を呼び出す必要があります。

```py
from dataclasses import dataclass
from huggingface_hub.dataclasses import strict

@strict
@dataclass
class Config:
    foo: str
    foo_length: int
    upper_case: bool = False

    def validate_foo_length(self):
        if len(self.foo) != self.foo_length:
            raise ValueError(f"foo must be {self.foo_length} characters long, got {len(self.foo)}")

    def validate_foo_casing(self):
        if self.upper_case and self.foo.upper() != self.foo:
            raise ValueError(f"foo must be uppercase, got {self.foo}")

config = Config(foo="bar", foo_length=3) # ok

config.upper_case = True
config.validate() # Raises StrictDataclassClassValidationError

Config(foo="abcd", foo_length=3) # Raises StrictDataclassFieldValidationError
Config(foo="Bar", foo_length=3, upper_case=True) # Raises StrictDataclassFieldValidationError
```

> [!WARNING]
> `.validate()` メソッドは、strict dataclass において予約名です。予期しない動作を防ぐため、クラスがすでにこのメソッドを定義している場合は、`StrictDataclassDefinitionError` エラーが発生します。

## API リファレンス

### `@strict`[[huggingface_hub.dataclasses.strict]]

`@strict` デコレーターは、データクラスに厳密なバリデーション機能を追加します。

#### huggingface_hub.dataclasses.strict[[huggingface_hub.dataclasses.strict]]

```python
huggingface_hub.dataclasses.strict(accept_kwargs: bool = False)
```

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/dataclasses.py#L56)

**パラメータ:**

cls : 厳密なデータクラスに変換するクラス。

accept_kwargs (`bool`, *任意*) : True の場合、`__init__` で任意のキーワード引数を許可します。デフォルトは False です。

**戻り値:**

フィールドの代入時に厳密な検証を行う強化されたデータクラス。

データクラスに厳密な検証を追加するためのデコレーター。

このデコレーターは、IDEや静的型付けツールがクラスをdataclassとして認識できるようにするため、`@dataclass`の上に使用する必要があります。

引数なしまたは引数ありで利用できます:
- `@strict`
- `@strict(accept_kwargs=True)`

例:
```py
>>> from dataclasses import dataclass
>>> from huggingface_hub.dataclasses import as_validated_field, strict, validated_field

>>> @as_validated_field
>>> def positive_int(value: int):
...     if not value >= 0:
...         raise ValueError(f"Value must be positive, got {value}")

>>> @strict(accept_kwargs=True)
... @dataclass
... class User:
...     name: str
...     age: int = positive_int(default=10)

# Initialize
>>> User(name="John")
User(name='John', age=10)

# Extra kwargs are accepted
>>> User(name="John", age=30, lastname="Doe")
User(name='John', age=30, *lastname='Doe')

# Invalid type => raises
>>> User(name="John", age="30")
huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
    TypeError: Field 'age' expected int, got str (value: '30')

# Invalid value => raises
>>> User(name="John", age=-1)
huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
    ValueError: Value must be positive, got -1
```

### `validate_typed_dict`[[huggingface_hub.dataclasses.validate_typed_dict]]

`TypedDict`クラスで定義された型に辞書が準拠していることを検証するためのメソッドです。

これは`TypedDict`におけるdataclassのバリデーションに相当する機能です。型付き辞書はインスタンス化されることがなく（静的型チェッカーによってのみ使用されるため）、バリデーションの手順は手動で呼び出す必要があります。

#### huggingface_hub.dataclasses.validate_typed_dict[[huggingface_hub.dataclasses.validate_typed_dict]]

```python
huggingface_hub.dataclasses.validate_typed_dict(schema: type, data: dict)
```

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/dataclasses.py#L286)

**パラメータ:**

schema (`type[TypedDictType]`) : 期待される構造と型を定義する TypedDict クラス。

data (`dict`) : 検証対象の辞書。

**例外:** ``StrictDataclassFieldValidationError``

- ``StrictDataclassFieldValidationError`` -- 辞書内のフィールドが期待される型に準拠していない場合。

TypedDictクラスで定義された型に辞書が準拠していることを検証します。

内部では、型付き dict は厳密な dataclass に変換され、`@strict` デコレーターを使用して検証されます。

例:
```py
>>> from typing import Annotated, TypedDict
>>> from huggingface_hub.dataclasses import validate_typed_dict

>>> def positive_int(value: int):
...     if not value >= 0:
...         raise ValueError(f"Value must be positive, got {value}")

>>> class User(TypedDict):
...     name: str
...     age: Annotated[int, positive_int]

>>> # Valid data
>>> validate_typed_dict(User, {"name": "John", "age": 30})

>>> # Invalid type for age
>>> validate_typed_dict(User, {"name": "John", "age": "30"})
huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
    TypeError: Field 'age' expected int, got str (value: '30')

>>> # Invalid value for age
>>> validate_typed_dict(User, {"name": "John", "age": -1})
huggingface_hub.errors.StrictDataclassFieldValidationError: Validation error for field 'age':
    ValueError: Value must be positive, got -1
```

### `as_validated_field`[[huggingface_hub.dataclasses.as_validated_field]]

`validated_field` を作成するためのデコレーターです。ボイラープレートコードを避けるため、バリデータが1つのみのフィールドに推奨されます。

#### huggingface_hub.dataclasses.as_validated_field[[huggingface_hub.dataclasses.as_validated_field]]

```python
huggingface_hub.dataclasses.as_validated_field(validator: Callable)
```

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/dataclasses.py#L426)

**パラメータ:**

validator (`Callable`) : 値を入力として受け取り、値が無効な場合は ValueError/TypeError を送出するメソッド。

バリデータ関数を`validated_field`（つまり、カスタムバリデータを持つdataclassフィールド）としてデコレートします。

### `validated_field`[[huggingface_hub.dataclasses.validated_field]]

カスタム検証付きのdataclassフィールドを作成します。

#### huggingface_hub.dataclasses.validated_field[[huggingface_hub.dataclasses.validated_field]]

```python
huggingface_hub.dataclasses.validated_field(validator: list[collections.abc.Callable[[typing.Any], None]] | collections.abc.Callable[[typing.Any], None], default: typing.Any = <dataclasses._MISSING_TYPE object at 0x7f9675fda620>, default_factory: typing.Any = <dataclasses._MISSING_TYPE object at 0x7f9675fda620>, init: bool = True, repr: bool = True, hash: bool | None = None, compare: bool = True, metadata: dict | None = None, **kwargs: typing.Any)
```

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/dataclasses.py#L383)

**パラメータ:**

validator (`Callable` または `list[Callable]`) : 値を入力として受け取り、値が無効な場合は ValueError/TypeError を送出するメソッドです。複数のチェックを適用するために、バリデータの一覧を指定することもできます。

- ****kwargs** : `dataclasses.field()`に渡す追加の引数。

**戻り値:**

メタデータにバリデータがアタッチされたフィールド

カスタムバリデータ付きのdataclassフィールドを作成します。

フィールドに複数のチェックを適用する際に便利です。ルールを1つだけ適用する場合は、`as_validated_field`デコレーターをご覧ください。

### エラー[[huggingface_hub.errors.StrictDataclassError]]

#### huggingface_hub.errors.StrictDataclassError[[huggingface_hub.errors.StrictDataclassError]]

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/errors.py#L540)

厳密なデータクラス用のベース例外。

#### huggingface_hub.errors.StrictDataclassDefinitionError[[huggingface_hub.errors.StrictDataclassDefinitionError]]

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/errors.py#L544)

厳密なデータクラスが正しく定義されていない場合にスローされる例外。

#### huggingface_hub.errors.StrictDataclassFieldValidationError[[huggingface_hub.errors.StrictDataclassFieldValidationError]]

```python
huggingface_hub.errors.StrictDataclassFieldValidationError(field: str, cause: Exception)
```

[ソース](https://github.com/huggingface/huggingface_hub/blob/v2.4.0/src/huggingface_hub/errors.py#L548)

厳格なデータクラスが特定のフィールドの検証に失敗した場合にスローされる例外。

## `pydantic`（または`attrs`、`marshmallow_dataclass`）を使わないのはなぜですか？

- Pydanticを依存関係に追加することに関する議論は、https://github.com/huggingface/transformers/issues/36329 を参照してください。これは重い追加となり、v1とv2の両方をサポートするには慎重なロジックが必要です。
- Pydanticの機能の大部分は必要ありません。特に、自動キャスト、jsonschema、シリアライズ、エイリアスなどに関連する機能は不要です。
- 辞書からクラスをインスタンス化する機能は必要ありません。
- データの破壊的変更（mutation）は行いたくありません。`@strict`では、「validation（検証）」とは「値が有効かどうかを確認すること」を意味します。一方、Pydanticでは、「validation（検証）」とは「値をキャストし、場合によっては変更を加えた上で、それが有効かどうかを確認すること」を意味します。
- 超高速な検証は必要ありません。`@strict`は、パフォーマンスが重要な高負荷環境向けには設計されていません。一般的なユースケースは、モデル設定の検証であり、これは一度だけ実行され、モデルの実行と比較して無視できるほど軽微です。これにより、コードを最小限に保つことができます。

