# Strict Dataclasses

Gói `huggingface_hub` cung cấp tiện ích để tạo **strict dataclasses**. Đây là các phiên bản nâng cao của `dataclass` chuẩn của Python với các tính năng xác thực bổ sung. Strict dataclasses đảm bảo rằng các trường được xác thực cả trong quá trình khởi tạo và gán giá trị, khiến chúng trở nên lý tưởng cho các kịch bản mà tính toàn vẹn của dữ liệu là yếu tố then chốt.

## Tổng quan

Strict dataclasses được tạo bằng decorator `@strict`. Chúng mở rộng chức năng của các dataclass thông thường bằng cách:

- Xác thực kiểu trường dựa trên gợi ý kiểu
- Hỗ trợ trình xác thực tùy chỉnh cho các kiểm tra bổ sung
- Tùy chọn cho phép các đối số từ khóa tùy ý trong bộ khởi tạo
- Xác thực các trường cả khi khởi tạo và trong quá trình gán

## Lợi ích

- **Tính toàn vẹn dữ liệu**: Đảm bảo các trường luôn chứa dữ liệu hợp lệ
- **Dễ sử dụng**: Tích hợp liền mạch với module `dataclass` của Python
- **Tính linh hoạt**: Hỗ trợ các trình xác thực tùy chỉnh cho logic xác thực phức tạp
- **Nhẹ**: Không yêu cầu các phụ thuộc bổ sung như Pydantic, attrs hoặc các thư viện tương tự

## Cách sử dụng

### Ví dụ cơ bản

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

Các trường được xác thực trong quá trình khởi tạo:

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

Sự nhất quán giữa các trường cũng được xác thực trong quá trình khởi tạo (xác thực theo lớp):

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

Các trường cũng được xác thực trong quá trình gán:

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

Để chạy lại xác thực cho toàn bộ lớp sau khi gán, bạn phải gọi `.validate` một cách tường minh:

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

### Trình xác thực tùy chỉnh

Bạn có thể gắn nhiều bộ xác thực tùy chỉnh vào các trường bằng cách sử dụng `validated_field`. Một bộ xác thực là một hàm có thể gọi nhận một đối số duy nhất và ném ngoại lệ nếu giá trị không hợp lệ.

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

Trong ví dụ này, cả hai bộ xác thực đều được áp dụng cho trường `hidden_size`.

### Các đối số từ khóa bổ sung

Mặc định, strict dataclasses chỉ chấp nhận các trường được định nghĩa trong lớp. Bạn có thể cho phép các đối số từ khóa bổ sung bằng cách đặt `accept_kwargs=True` trong bộ trang trí `@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')
```

Các đối số từ khóa bổ sung xuất hiện trong biểu diễn chuỗi của dataclass nhưng được thêm tiền tố `*` để nhấn mạnh rằng chúng không được kiểm tra.

### Tích hợp với Type Hints

Strict dataclasses tuân thủ các type hints và tự động kiểm tra chúng. Ví dụ:

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

Các kiểu được hỗ trợ bao gồm:
- Bất kỳ
- Union
- Tùy chọn
- Literal
- Danh sách
- Dict
- Tuple
- Đặt

Và bất kỳ sự kết hợp nào của các kiểu này. Nếu bạn cần xác thực kiểu phức tạp hơn, bạn có thể thực hiện thông qua một bộ xác thực tùy chỉnh.

### Trình xác thực lớp

Các phương thức có tên `validate_xxx` được coi là bộ xác thực lớp. Các phương thức này chỉ được phép nhận `self` làm đối số. Bộ xác thực lớp được chạy một lần trong quá trình khởi tạo, ngay sau `__post_init__`. Bạn có thể định nghĩa bao nhiêu bộ xác thực như vậy cũng được—chúng sẽ được thực thi tuần tự theo thứ tự xuất hiện.

Lưu ý rằng các bộ xác thực lớp (class validators) không được tự động chạy lại khi một trường được cập nhật sau khi khởi tạo. Để xác thực lại đối tượng thủ công, bạn cần gọi `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]
> Phương thức `.validate()` là tên được dành riêng cho các dataclass strict. Để ngăn chặn các hành vi không mong muốn, một lỗi `StrictDataclassDefinitionError` sẽ được ném ra nếu lớp của bạn đã định nghĩa phương thức này.

## Tham khảo API

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

Trình trang trí `@strict` tăng cường cho một dataclass với tính năng xác thực nghiêm ngặt.

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

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

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

**Tham số:**

cls : Lớp cần chuyển đổi thành dataclass nghiêm ngặt.

accept_kwargs (`bool`, *tuỳ chọn*) : Nếu là True, cho phép các đối số từ khóa tùy ý trong `__init__`. Mặc định là False.

**Trả về:**

Dataclass được tăng cường với xác thực nghiêm ngặt khi gán giá trị cho trường.

Trình trang trí để thêm xác thực nghiêm ngặt vào một dataclass.

Trình trang trí này phải được sử dụng phía trên `@dataclass` để đảm bảo các IDE và công cụ kiểm tra kiểu tĩnh nhận diện lớp như một dataclass.

Có thể được sử dụng có hoặc không có đối số:
- `@strict`
- `@strict(accept_kwargs=True)`

Ví dụ:
```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]]

Phương thức để xác minh rằng một từ điển tuân thủ các kiểu được định nghĩa trong lớp `TypedDict`.

Đây là tương đương của việc xác thực dataclass nhưng dành cho `TypedDict`. Vì typed dict không bao giờ được khởi tạo (chỉ được sử dụng bởi trình kiểm tra kiểu tĩnh), bước xác thực phải được gọi thủ công.

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

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

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

**Tham số:**

schema (`type[TypedDictType]`) : Lớp TypedDict định nghĩa cấu trúc và các kiểu dữ liệu mong đợi.

data (`dict`) : Từ điển cần kiểm tra.

**Gây ra:** ``StrictDataclassFieldValidationError``

- ``StrictDataclassFieldValidationError`` -- Nếu bất kỳ trường nào trong từ điển không tuân thủ kiểu dữ liệu mong đợi.

Kiểm tra xem một từ điển có tuân thủ các kiểu được định nghĩa trong lớp TypedDict hay không.

Bên trong, typed dict được chuyển đổi thành một dataclass nghiêm ngặt và được xác thực bằng decorator `@strict`.

Ví dụ:
```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 để tạo một `validated_field`. Được khuyến nghị cho các trường có một validator duy nhất để tránh mã lặp lại.

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

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

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

**Tham số:**

validator (`Callable`) : Một phương thức nhận một giá trị làm đầu vào và gây ra ValueError/TypeError nếu giá trị đó không hợp lệ.

Trang trí một hàm validator như một `validated_field` (tức là một trường dataclass có validator tùy chỉnh).

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

Tạo một trường dataclass với xác thực tùy chỉnh.

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

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

**Tham số:**

validator (`Callable` hoặc `list[Callable]`) : Một phương thức nhận một giá trị làm đầu vào và gây ra ValueError/TypeError nếu giá trị đó không hợp lệ. Có thể là một danh sách các bộ xác thực để áp dụng nhiều kiểm tra.

- ****kwargs** : Các đối số bổ sung để truyền cho `dataclasses.field()`.

**Trả về:**

Một trường có bộ xác thực được gắn trong metadata

Tạo một trường dataclass với bộ xác thực tùy chỉnh.

Có ích khi áp dụng nhiều kiểm tra cho một trường. Nếu chỉ áp dụng một quy tắc, hãy xem trang trí `as_validated_field`.

### Lỗi[[huggingface_hub.errors.StrictDataclassError]]

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

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

Ngoại lệ cơ bản cho các dataclass nghiêm ngặt.

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

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

Lỗi ngoại lệ được ném ra khi một dataclass nghiêm ngặt được định nghĩa không chính xác.

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

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

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

Ngoại lệ được ném ra khi một dataclass nghiêm ngặt không vượt qua kiểm tra xác thực cho một trường nhất định.

## Tại sao không dùng `pydantic`? (hay `attrs`? hay `marshmallow_dataclass`?)

- Xem thảo luận tại https://github.com/huggingface/transformers/issues/36329 về việc thêm Pydantic như một dependency. Đây sẽ là một bổ sung nặng và đòi hỏi logic cẩn thận để hỗ trợ cả v1 và v2.
- Chúng ta không cần hầu hết các tính năng của Pydantic, đặc biệt là những tính năng liên quan đến chuyển đổi kiểu tự động, jsonschema, chuỗi hóa, alias, v.v.
- Chúng ta không cần khả năng khởi tạo một lớp từ một từ điển.
- Chúng ta không muốn thay đổi dữ liệu. Trong `@strict`, "validation" có nghĩa là "kiểm tra xem một giá trị có hợp lệ hay không." Trong Pydantic, "validation" có nghĩa là "chuyển đổi kiểu một giá trị, có thể làm thay đổi nó, rồi sau đó kiểm tra xem nó có hợp lệ hay không."
- Chúng ta không cần kiểm tra cực kỳ nhanh. `@strict` không được thiết kế cho các tải nặng mà hiệu suất là yếu tố then chốt. Các trường hợp sử dụng phổ biến bao gồm kiểm tra cấu hình mô hình (được thực hiện một lần và không đáng kể so với việc chạy mô hình). Điều này cho phép chúng ta giữ mã ở mức tối giản.

