mirror of https://github.com/python/cpython
gh-91860: Add typing.dataclass_transform (PEP 681) (#91861)
Copied from typing-extensions (python/typing#1054, python/typing#1120). Documentation is intentionally omitted, so we can focus on getting the runtime part in before the feature freeze.
This commit is contained in:
parent
d174ebe91e
commit
5397b5afc1
|
@ -24,6 +24,7 @@ from typing import get_type_hints
|
|||
from typing import get_origin, get_args
|
||||
from typing import is_typeddict
|
||||
from typing import reveal_type
|
||||
from typing import dataclass_transform
|
||||
from typing import no_type_check, no_type_check_decorator
|
||||
from typing import Type
|
||||
from typing import NamedTuple, NotRequired, Required, TypedDict
|
||||
|
@ -6607,6 +6608,91 @@ class RevealTypeTests(BaseTestCase):
|
|||
self.assertEqual(stderr.getvalue(), "Runtime type is 'object'\n")
|
||||
|
||||
|
||||
class DataclassTransformTests(BaseTestCase):
|
||||
def test_decorator(self):
|
||||
def create_model(*, frozen: bool = False, kw_only: bool = True):
|
||||
return lambda cls: cls
|
||||
|
||||
decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model)
|
||||
|
||||
class CustomerModel:
|
||||
id: int
|
||||
|
||||
self.assertIs(decorated, create_model)
|
||||
self.assertEqual(
|
||||
decorated.__dataclass_transform__,
|
||||
{
|
||||
"eq_default": True,
|
||||
"order_default": False,
|
||||
"kw_only_default": True,
|
||||
"field_specifiers": (),
|
||||
"kwargs": {},
|
||||
}
|
||||
)
|
||||
self.assertIs(
|
||||
decorated(frozen=True, kw_only=False)(CustomerModel),
|
||||
CustomerModel
|
||||
)
|
||||
|
||||
def test_base_class(self):
|
||||
class ModelBase:
|
||||
def __init_subclass__(cls, *, frozen: bool = False): ...
|
||||
|
||||
Decorated = dataclass_transform(
|
||||
eq_default=True,
|
||||
order_default=True,
|
||||
# Arbitrary unrecognized kwargs are accepted at runtime.
|
||||
make_everything_awesome=True,
|
||||
)(ModelBase)
|
||||
|
||||
class CustomerModel(Decorated, frozen=True):
|
||||
id: int
|
||||
|
||||
self.assertIs(Decorated, ModelBase)
|
||||
self.assertEqual(
|
||||
Decorated.__dataclass_transform__,
|
||||
{
|
||||
"eq_default": True,
|
||||
"order_default": True,
|
||||
"kw_only_default": False,
|
||||
"field_specifiers": (),
|
||||
"kwargs": {"make_everything_awesome": True},
|
||||
}
|
||||
)
|
||||
self.assertIsSubclass(CustomerModel, Decorated)
|
||||
|
||||
def test_metaclass(self):
|
||||
class Field: ...
|
||||
|
||||
class ModelMeta(type):
|
||||
def __new__(
|
||||
cls, name, bases, namespace, *, init: bool = True,
|
||||
):
|
||||
return super().__new__(cls, name, bases, namespace)
|
||||
|
||||
Decorated = dataclass_transform(
|
||||
order_default=True, field_specifiers=(Field,)
|
||||
)(ModelMeta)
|
||||
|
||||
class ModelBase(metaclass=Decorated): ...
|
||||
|
||||
class CustomerModel(ModelBase, init=False):
|
||||
id: int
|
||||
|
||||
self.assertIs(Decorated, ModelMeta)
|
||||
self.assertEqual(
|
||||
Decorated.__dataclass_transform__,
|
||||
{
|
||||
"eq_default": True,
|
||||
"order_default": True,
|
||||
"kw_only_default": False,
|
||||
"field_specifiers": (Field,),
|
||||
"kwargs": {},
|
||||
}
|
||||
)
|
||||
self.assertIsInstance(CustomerModel, Decorated)
|
||||
|
||||
|
||||
class AllTests(BaseTestCase):
|
||||
"""Tests for __all__."""
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ __all__ = [
|
|||
'assert_never',
|
||||
'cast',
|
||||
'clear_overloads',
|
||||
'dataclass_transform',
|
||||
'final',
|
||||
'get_args',
|
||||
'get_origin',
|
||||
|
@ -3271,3 +3272,81 @@ def reveal_type(obj: T, /) -> T:
|
|||
"""
|
||||
print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
|
||||
return obj
|
||||
|
||||
|
||||
def dataclass_transform(
|
||||
*,
|
||||
eq_default: bool = True,
|
||||
order_default: bool = False,
|
||||
kw_only_default: bool = False,
|
||||
field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (),
|
||||
**kwargs: Any,
|
||||
) -> Callable[[T], T]:
|
||||
"""Decorator that marks a function, class, or metaclass as providing
|
||||
dataclass-like behavior.
|
||||
|
||||
Example usage with a decorator function:
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
@dataclass_transform()
|
||||
def create_model(cls: type[_T]) -> type[_T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
@create_model
|
||||
class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
On a base class:
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelBase: ...
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
On a metaclass:
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type): ...
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
Each of the ``CustomerModel`` classes defined in this example will now
|
||||
behave similarly to a dataclass created with the ``@dataclasses.dataclass``
|
||||
decorator. For example, the type checker will synthesize an ``__init__``
|
||||
method.
|
||||
|
||||
The arguments to this decorator can be used to customize this behavior:
|
||||
- ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
|
||||
True or False if it is omitted by the caller.
|
||||
- ``order_default`` indicates whether the ``order`` parameter is
|
||||
assumed to be True or False if it is omitted by the caller.
|
||||
- ``kw_only_default`` indicates whether the ``kw_only`` parameter is
|
||||
assumed to be True or False if it is omitted by the caller.
|
||||
- ``field_specifiers`` specifies a static list of supported classes
|
||||
or functions that describe fields, similar to ``dataclasses.field()``.
|
||||
|
||||
At runtime, this decorator records its arguments in the
|
||||
``__dataclass_transform__`` attribute on the decorated object.
|
||||
It has no other runtime effect.
|
||||
|
||||
See PEP 681 for more details.
|
||||
"""
|
||||
def decorator(cls_or_fn):
|
||||
cls_or_fn.__dataclass_transform__ = {
|
||||
"eq_default": eq_default,
|
||||
"order_default": order_default,
|
||||
"kw_only_default": kw_only_default,
|
||||
"field_specifiers": field_specifiers,
|
||||
"kwargs": kwargs,
|
||||
}
|
||||
return cls_or_fn
|
||||
return decorator
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add :func:`typing.dataclass_transform`, implementing :pep:`681`. Patch by
|
||||
Jelle Zijlstra.
|
Loading…
Reference in New Issue