
## Summary Fixes #17541 Before this change, in the case of overloaded functions, `@dataclass_transform` was detected only when applied to the implementation, not the overloads. However, the spec also allows this decorator to be applied to any of the overloads as well. With this PR, we start handling `@dataclass_transform`s applied to overloads. ## Test Plan Fixed existing TODOs in the test suite.
5.5 KiB
typing.dataclass_transform
[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
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:
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:
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
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
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
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:
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
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
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
D1("a")
D2("a")
D1(1.2) # error: [invalid-argument-type]
D2(1.2) # error: [invalid-argument-type]