# Strict Dataclasses

Paket `huggingface_hub` menyediakan utilitas untuk membuat **strict dataclasses**. Ini adalah versi yang ditingkatkan dari `dataclass` standar Python dengan fitur validasi tambahan. Strict dataclasses memastikan bahwa field divalidasi baik saat inisialisasi maupun penugasan, sehingga ideal untuk skenario di mana integritas data sangat penting.

## Ikhtisar

Strict dataclasses dibuat menggunakan dekorator `@strict`. Mereka memperluas fungsionalitas dataclasses reguler dengan:

- Memvalidasi tipe bidang berdasarkan type hints
- Mendukung validator kustom untuk pemeriksaan tambahan
- Secara opsional, mengizinkan argumen kata kunci arbitrer dalam konstruktor
- Memvalidasi field baik saat inisialisasi maupun selama penugasan

## Manfaat

- **Integritas Data**: Memastikan field selalu berisi data yang valid
- **Kemudahan Penggunaan**: Terintegrasi dengan mulus dengan modul `dataclass` Python
- **Fleksibilitas**: Mendukung validator kustom untuk logika validasi yang kompleks
- **Ringan**: Tidak memerlukan dependensi tambahan seperti Pydantic, attrs, atau pustaka serupa

## Penggunaan

### Contoh Dasar

```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})")
```

Field divalidasi selama inisialisasi:

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

Konsistensi antar bidang juga divalidasi selama inisialisasi (validasi per kelas):

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

Field juga divalidasi saat penugasan:

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

Untuk menjalankan ulang validasi seluruh kelas setelah penugasan, Anda harus memanggil `.validate` secara eksplisit:

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

### Validator Kustom

Anda dapat melampirkan beberapa validator kustom ke field menggunakan `validated_field`. Validator adalah callable yang menerima satu argumen dan melempar pengecualian jika nilai tidak valid.

```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])
```

Pada contoh ini, kedua validator diterapkan pada bidang `hidden_size`.

### Argumen Kata Kunci Tambahan

Secara default, strict dataclasses hanya menerima field yang didefinisikan dalam kelas. Anda dapat mengizinkan argumen kata kunci tambahan dengan mengatur `accept_kwargs=True` di dekorator `@strict`.

```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')
```

Argumen kata kunci tambahan muncul dalam representasi string dari dataclass tetapi diberi awalan `*` untuk menyoroti bahwa argumen tersebut tidak divalidasi.

### Integrasi dengan Type Hints

Strict dataclasses menghormati type hints dan memvalidasinya secara otomatis. Sebagai contoh:

```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
```

Jenis yang didukung meliputi:
- Semua
- Union
- Opsional
- Literal
- Daftar
- Dict
- Tuple
- Set

Dan kombinasi apa pun dari tipe-tipe ini. Jika Anda memerlukan validasi tipe yang lebih kompleks, Anda dapat melakukannya melalui validator kustom.

### Validator kelas

Metode bernama `validate_xxx` diperlakukan sebagai validator kelas. Metode-metode ini hanya boleh mengambil `self` sebagai argumen. Validator kelas dijalankan sekali selama inisialisasi, tepat setelah `__post_init__`. Anda dapat mendefinisikan sebanyak yang diperlukan—mereka akan dieksekusi secara berurutan sesuai urutan kemunculannya.

Perhatikan bahwa validator kelas tidak dijalankan ulang secara otomatis ketika sebuah field diperbarui setelah inisialisasi. Untuk memvalidasi ulang objek secara manual, Anda perlu memanggil `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]
> Metode `.validate()` adalah nama yang direservasi pada dataclass ketat. Untuk mencegah perilaku yang tidak terduga, error `StrictDataclassDefinitionError` akan dilemparkan jika kelas Anda sudah mendefinisikannya.

## Referensi API

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

Dekorator `@strict` meningkatkan dataclass dengan validasi ketat.

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

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

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

**Parameter:**

cls : Kelas yang akan dikonversi menjadi dataclass ketat.

accept_kwargs (`bool`, *opsional*) : Jika True, memungkinkan argumen kata kunci arbitrer di `__init__`. Bawaan adalah False.

**Mengembalikan:**

Dataclass yang ditingkatkan dengan validasi ketat pada penugasan field.

Decorator untuk menambahkan validasi ketat ke dataclass.

Decorator ini harus digunakan di atas `@dataclass` untuk memastikan IDE dan alat pengetikan statis mengenali kelas tersebut sebagai dataclass.

Dapat digunakan dengan atau tanpa argumen:
- `@strict`
- `@strict(accept_kwargs=True)`

Contoh:
```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]]

Metode untuk memvalidasi bahwa sebuah kamus sesuai dengan tipe yang didefinisikan dalam kelas `TypedDict`.

Ini adalah ekuivalen dari validasi dataclass tetapi untuk `TypedDict`. Karena typed dict tidak pernah diinstansiasi (hanya digunakan oleh penguji tipe statis), langkah validasi harus dipanggil secara manual.

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

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

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

**Parameter:**

schema (`type[TypedDictType]`) : Kelas TypedDict yang mendefinisikan struktur dan tipe yang diharapkan.

data (`dict`) : Kamus yang akan divalidasi.

**Mengangkat:** ``StrictDataclassFieldValidationError``

- ``StrictDataclassFieldValidationError`` -- Jika ada field dalam dictionary yang tidak sesuai dengan tipe yang diharapkan.

Validasi bahwa sebuah dictionary sesuai dengan tipe yang didefinisikan dalam kelas TypedDict.

Di balik layar, typed dict dikonversi menjadi dataclass yang ketat dan divalidasi menggunakan dekorator `@strict`.

Contoh:
```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]]

Decorator untuk membuat `validated_field`. Direkomendasikan untuk field dengan satu validator untuk menghindari kode boilerplate.

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

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

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

**Parameter:**

validator (`Callable`) : Metode yang menerima nilai sebagai input dan memicu ValueError/TypeError jika nilai tersebut tidak valid.

Menghiasi fungsi validator sebagai `validated_field` (yaitu field dataclass dengan validator kustom).

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

Membuat field dataclass dengan validasi kustom.

#### 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)
```

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

**Parameter:**

validator (`Callable` atau `list[Callable]`) : Metode yang menerima nilai sebagai input dan memicu ValueError/TypeError jika nilai tersebut tidak valid. Dapat berupa daftar validator untuk menerapkan beberapa pemeriksaan.

- ****kwargs** : Argumen tambahan untuk diteruskan ke `dataclasses.field()`.

**Mengembalikan:**

Field dengan validator yang terlampir di metadata

Buat field dataclass dengan validator kustom.

Berfungsi untuk menerapkan beberapa pemeriksaan pada sebuah field. Jika hanya menerapkan satu aturan, lihat dekorator `as_validated_field`.

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

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

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

Pengecualian dasar untuk dataclass ketat.

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

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

Pengecualian yang dilempar ketika dataclass ketat didefinisikan dengan tidak benar.

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

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

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

Exception yang dilempar ketika strict dataclass gagal validasi untuk field tertentu.

## Mengapa Tidak Menggunakan `pydantic`? (atau `attrs`? atau `marshmallow_dataclass`?)

- Lihat diskusi di https://github.com/huggingface/transformers/issues/36329 mengenai penambahan Pydantic sebagai dependensi. Tambahkan ini akan menjadi beban yang berat dan memerlukan logika yang cermat untuk mendukung baik v1 maupun v2.
- Kami tidak membutuhkan sebagian besar fitur Pydantic, terutama yang terkait dengan casting otomatis, jsonschema, serialisasi, alias, dan sebagainya.
- Kami tidak memerlukan kemampuan untuk menginstansiasi kelas dari kamus.
- Kami tidak ingin mengubah data. Dalam `@strict`, "validation" berarti "memeriksa apakah suatu nilai valid." Dalam Pydantic, "validation" berarti "mengubah tipe suatu nilai, yang mungkin mengubah nilainya, lalu memeriksa apakah nilainya valid."
- Kami tidak perlu validasi yang sangat cepat. `@strict` tidak dirancang untuk beban berat di mana kinerja sangat krusial. Kasus penggunaan umum melibatkan validasi konfigurasi model (dilakukan sekali dan dapat diabaikan dibandingkan dengan menjalankan model). Hal ini memungkinkan kami untuk menjaga kode tetap minimal.

