-
Notifications
You must be signed in to change notification settings - Fork 246
/
Copy pathdataclasses_transform_converter.py
133 lines (85 loc) · 3.02 KB
/
dataclasses_transform_converter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
Tests the dataclass_transform mechanism supports the "converter" parameter
in a field specifier class.
"""
# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#converters
from typing import (
Any,
Callable,
TypeVar,
dataclass_transform,
overload,
)
T = TypeVar("T")
S = TypeVar("S")
def model_field(
*,
converter: Callable[[S], T],
default: S | None = None,
default_factory: Callable[[], S] | None = None,
) -> T:
...
@dataclass_transform(field_specifiers=(model_field,))
class ModelBase:
...
# > The converter must be a callable that must accept a single positional
# > argument (but may accept other optional arguments, which are ignored for
# > typing purposes).
def bad_converter1() -> int:
return 0
def bad_converter2(*, x: int) -> int:
return 0
class DC1(ModelBase):
field1: int = model_field(converter=bad_converter1) # E
field2: int = model_field(converter=bad_converter2) # E
# > The type of the first positional parameter provides the type of the
# > synthesized __init__ parameter for the field.
# > The return type of the callable must be assignable to the field’s
# > declared type.
def converter_simple(s: str) -> int:
return int(s)
def converter_with_param_before_args(s: str, *args: int, **kwargs: int) -> int:
return int(s)
def converter_with_args(*args: str) -> int:
return int(args[0])
@overload
def overloaded_converter(s: str) -> int:
...
@overload
def overloaded_converter(s: list[str]) -> int:
...
def overloaded_converter(s: str | list[str], *args: str) -> int | str:
return 0
class ConverterClass:
@overload
def __init__(self, val: str) -> None:
...
@overload
def __init__(self, val: bytes) -> None:
...
def __init__(self, val: str | bytes) -> None:
pass
class DC2(ModelBase):
field0: int = model_field(converter=converter_simple)
field1: int = model_field(converter=converter_with_param_before_args)
field2: int = model_field(converter=converter_with_args)
field3: ConverterClass = model_field(converter=ConverterClass)
field4: int = model_field(converter=overloaded_converter)
field5: dict[str, str] = model_field(converter=dict, default=())
DC2(1, "f1", "f2", b"f3", []) # E
DC2("f0", "f1", "f2", 1, []) # E
DC2("f0", "f1", "f2", "f3", 3j) # E
dc1 = DC2("f0", "f1", "f2", b"f6", [])
dc1.field0 = "f1"
dc1.field3 = "f6"
dc1.field3 = b"f6"
dc1.field0 = 1 # E
dc1.field3 = 1 # E
dc2 = DC2("f0", "f1", "f2", "f6", "1", (("a", "1"), ("b", "2")))
# > If default or default_factory are provided, the type of the default value
# > should be assignable to the first positional parameter of the converter.
class DC3(ModelBase):
field0: int = model_field(converter=converter_simple, default="")
field1: int = model_field(converter=converter_simple, default=1) # E
field2: int = model_field(converter=converter_simple, default_factory=str)
field3: int = model_field(converter=converter_simple, default_factory=int) # E