# `typing.dataclass_transform` ```toml [environment] python-version = "3.12" ``` `dataclass_transform` is a decorator that can be used to let type checkers know that a function, class, or metaclass is a `dataclass`-like construct. ## Basic example ```py from typing_extensions import dataclass_transform @dataclass_transform() def my_dataclass[T](cls: type[T]) -> type[T]: # modify cls return cls @my_dataclass class Person: name: str age: int | None = None Person("Alice", 20) Person("Bob", None) Person("Bob") # error: [missing-argument] Person() ``` ## Decorating decorators that take parameters themselves If we want our `dataclass`-like decorator to also take parameters, that is also possible: ```py from typing_extensions import dataclass_transform, Callable @dataclass_transform() def versioned_class[T](*, version: int = 1): def decorator(cls): # modify cls return cls return decorator @versioned_class(version=2) class Person: name: str age: int | None = None Person("Alice", 20) # error: [missing-argument] Person() ``` We properly type-check the arguments to the decorator: ```py from typing_extensions import dataclass_transform, Callable # error: [invalid-argument-type] @versioned_class(version="a string") class C: name: str ``` ## Types of decorators The examples from this section are straight from the Python documentation on [`typing.dataclass_transform`]. ### Decorating a decorator function ```py from typing_extensions import dataclass_transform @dataclass_transform() def create_model[T](cls: type[T]) -> type[T]: ... return cls @create_model class CustomerModel: id: int name: str CustomerModel(id=1, name="Test") ``` ### Decorating a metaclass ```py from typing_extensions import dataclass_transform @dataclass_transform() class ModelMeta(type): ... class ModelBase(metaclass=ModelMeta): ... class CustomerModel(ModelBase): id: int name: str CustomerModel(id=1, name="Test") # error: [missing-argument] CustomerModel() ``` ### Decorating a base class ```py from typing_extensions import dataclass_transform @dataclass_transform() class ModelBase: ... class CustomerModel(ModelBase): id: int name: str # TODO: this is not supported yet # error: [unknown-argument] # error: [unknown-argument] CustomerModel(id=1, name="Test") ``` ## Arguments to `dataclass_transform` ### `eq_default` `eq=True/False` does not have a observable effect (apart from a minor change regarding whether `other` is positional-only or not, which is not modelled at the moment). ### `order_default` The `order_default` argument controls whether methods such as `__lt__` are generated by default. This can be overwritten using the `order` argument to the custom decorator: ```py from typing_extensions import dataclass_transform @dataclass_transform() def normal(*, order: bool = False): raise NotImplementedError @dataclass_transform(order_default=False) def order_default_false(*, order: bool = False): raise NotImplementedError @dataclass_transform(order_default=True) def order_default_true(*, order: bool = True): raise NotImplementedError @normal class Normal: inner: int Normal(1) < Normal(2) # error: [unsupported-operator] @normal(order=True) class NormalOverwritten: inner: int NormalOverwritten(1) < NormalOverwritten(2) @order_default_false class OrderFalse: inner: int OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator] @order_default_false(order=True) class OrderFalseOverwritten: inner: int OrderFalseOverwritten(1) < OrderFalseOverwritten(2) @order_default_true class OrderTrue: inner: int OrderTrue(1) < OrderTrue(2) @order_default_true(order=False) class OrderTrueOverwritten: inner: int # error: [unsupported-operator] OrderTrueOverwritten(1) < OrderTrueOverwritten(2) ``` ### `kw_only_default` To do ### `field_specifiers` To do ## Overloaded dataclass-like decorators In the case of an overloaded decorator, the `dataclass_transform` decorator can be applied to the implementation, or to *one* of the overloads. ### Applying `dataclass_transform` to the implementation ```py from typing_extensions import dataclass_transform, TypeVar, Callable, overload T = TypeVar("T", bound=type) @overload def versioned_class( cls: T, *, version: int = 1, ) -> T: ... @overload def versioned_class( *, version: int = 1, ) -> Callable[[T], T]: ... @dataclass_transform() def versioned_class( cls: T | None = None, *, version: int = 1, ) -> T | Callable[[T], T]: raise NotImplementedError @versioned_class class D1: x: str @versioned_class(version=2) class D2: x: str D1("a") D2("a") D1(1.2) # error: [invalid-argument-type] D2(1.2) # error: [invalid-argument-type] ``` ### Applying `dataclass_transform` to an overload ```py from typing_extensions import dataclass_transform, TypeVar, Callable, overload T = TypeVar("T", bound=type) @overload @dataclass_transform() def versioned_class( cls: T, *, version: int = 1, ) -> T: ... @overload def versioned_class( *, version: int = 1, ) -> Callable[[T], T]: ... def versioned_class( cls: T | None = None, *, version: int = 1, ) -> T | Callable[[T], T]: raise NotImplementedError @versioned_class class D1: x: str @versioned_class(version=2) class D2: x: str # TODO: these should not be errors D1("a") # error: [too-many-positional-arguments] D2("a") # error: [too-many-positional-arguments] # TODO: these should be invalid-argument-type errors D1(1.2) # error: [too-many-positional-arguments] D2(1.2) # error: [too-many-positional-arguments] ``` [`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform