22
33import dataclasses
44from enum import Enum
5- from typing import Any , Callable , Tuple , Type
5+ from typing import Any , Callable , Sequence , Type
66
7- import typing_inspect
8- from typing_extensions import Annotated , Self , get_args , get_origin , get_type_hints
7+ from type_lens import TypeView
8+ from typing_extensions import Self , get_type_hints
99
1010from dataclass_settings .loaders import Loader
1111
@@ -22,7 +22,7 @@ def detect(cls: type) -> bool:
2222@dataclasses .dataclass
2323class Field :
2424 name : str
25- type : type
25+ type_view : TypeView
2626 annotations : tuple [Any , ...]
2727 mapper : Callable [..., Any ] | None = None
2828
@@ -32,10 +32,10 @@ def get_loaders(self, loaders: tuple[Type[Loader], ...]):
3232 yield m
3333
3434 def get_nested_type (self ) -> Type | None :
35- if typing_inspect . is_union_type ( self .type ) :
36- args = typing_inspect . get_args ( self .type )
35+ if self .type_view . is_union :
36+ args : Sequence [ type ] = self .type_view . args
3737 else :
38- args = [self .type ]
38+ args = [self .type_view . annotation ]
3939
4040 unsupported_args = []
4141 try :
@@ -61,66 +61,57 @@ def map_value(self, value: str | dict[str, Any]):
6161
6262 return self .mapper (value )
6363
64+ @classmethod
65+ def from_type_view (cls , name : str , type_view : TypeView ) -> Self :
66+ stripped = type_view .strip_optional ()
67+ return cls (
68+ name = name ,
69+ type_view = stripped ,
70+ annotations = type_view .metadata ,
71+ mapper = stripped .annotation ,
72+ )
73+
6474
6575@dataclasses .dataclass
6676class DataclassField (Field ):
6777 @classmethod
68- def collect (cls , value : type , type_hints : dict [str , Type ]) -> list [Self ]:
78+ def collect (cls , value : type , type_hints : dict [str , TypeView ]) -> list [Self ]:
6979 fields = []
7080 for f in value .__dataclass_fields__ .values (): # type: ignore
71- annotation = get_type (type_hints [f .name ])
72-
73- annotation , args = get_annotation_args (annotation )
74-
75- field = cls (
76- name = f .name ,
77- type = annotation ,
78- annotations = args ,
79- mapper = annotation ,
80- )
81+ type_view = type_hints [f .name ]
82+ field = cls .from_type_view (f .name , type_view )
8183 fields .append (field )
8284 return fields
8385
8486
8587@dataclasses .dataclass
8688class AttrsField (Field ):
8789 @classmethod
88- def collect (cls , value : type , type_hints : dict [str , Type ]) -> list [Self ]:
90+ def collect (cls , value : type , type_hints : dict [str , TypeView ]) -> list [Self ]:
8991 fields = []
9092
9193 for f in value .__attrs_attrs__ : # type: ignore
92- annotation = get_type (type_hints [f .name ])
93- annotation , args = get_annotation_args (annotation )
94-
95- field = cls (
96- name = f .name ,
97- type = annotation ,
98- annotations = args ,
99- mapper = annotation ,
100- )
94+ type_view = type_hints [f .name ]
95+ field = cls .from_type_view (f .name , type_view )
10196 fields .append (field )
10297 return fields
10398
10499
105100@dataclasses .dataclass
106101class MsgspecField (Field ):
107102 @classmethod
108- def collect (cls , value : type , type_hints : dict [str , Type ]) -> list [Self ]:
103+ def collect (cls , value : type , type_hints : dict [str , TypeView ]) -> list [Self ]:
109104 import msgspec
110105
111106 fields = []
112107 for f in msgspec .structs .fields (value ):
113- annotation = get_type (type_hints [f .name ])
114- annotation , args = get_annotation_args (annotation )
115-
116- mapper = cls .splat_mapper (annotation ) if detect (annotation ) else annotation
108+ type_view = type_hints [f .name ]
109+ field = cls .from_type_view (f .name , type_view )
117110
118- field = cls (
119- name = f .name ,
120- type = annotation ,
121- annotations = args ,
122- mapper = mapper ,
123- )
111+ if detect (field .type_view .annotation ):
112+ field = dataclasses .replace (
113+ field , mapper = cls .splat_mapper (type_view .annotation )
114+ )
124115 fields .append (field )
125116 return fields
126117
@@ -137,59 +128,45 @@ def convert(**value):
137128@dataclasses .dataclass
138129class PydanticV1Field (Field ):
139130 @classmethod
140- def collect (cls , value , type_hints : dict [str , Type ]) -> list [Self ]:
131+ def collect (cls , value , type_hints : dict [str , TypeView ]) -> list [Self ]:
141132 fields = []
142133 for name , f in value .__fields__ .items ():
143- annotation = get_type (type_hints [name ])
144- annotation , args = get_annotation_args (annotation )
145-
146- mapper = annotation if detect (annotation ) else None
134+ type_view = type_hints [f .name ]
135+ field = cls .from_type_view (name , type_view )
147136
148- field = cls (
149- name = name ,
150- type = annotation ,
151- annotations = args ,
152- mapper = mapper ,
153- )
137+ if not detect (field .type_view .annotation ):
138+ field = dataclasses .replace (field , mapper = None )
154139 fields .append (field )
155140 return fields
156141
157142
158143@dataclasses .dataclass
159144class PydanticV2Field (Field ):
160145 @classmethod
161- def collect (cls , value : type , type_hints : dict [str , Type ]) -> list [Self ]:
146+ def collect (cls , value : type , type_hints : dict [str , TypeView ]) -> list [Self ]:
162147 fields = []
163148 for name , f in value .model_fields .items (): # type: ignore
164- annotation = get_type (type_hints [name ])
165- mapper = annotation if detect (annotation ) else None
166-
167- field = cls (
168- name = name ,
169- type = annotation ,
170- annotations = tuple (f .metadata ),
171- mapper = mapper ,
172- )
149+ type_view = type_hints [f .name ]
150+ field = cls .from_type_view (name , type_view )
151+
152+ if not detect (field .type_view .annotation ):
153+ field = dataclasses .replace (field , mapper = None )
173154 fields .append (field )
174155 return fields
175156
176157
177158@dataclasses .dataclass
178159class PydanticV2DataclassField (Field ):
179160 @classmethod
180- def collect (cls , value : type , type_hints : dict [str , Type ]) -> list [Self ]:
161+ def collect (cls , value : type , type_hints : dict [str , TypeView ]) -> list [Self ]:
181162 fields = []
182163
183164 for name , f in value .__pydantic_fields__ .items (): # type: ignore
184- annotation = get_type (type_hints [name ])
185- mapper = annotation if detect (annotation ) else None
186-
187- field = cls (
188- name = name ,
189- type = annotation ,
190- annotations = tuple (f .metadata ),
191- mapper = mapper ,
192- )
165+ type_view = type_hints [name ]
166+ field = cls .from_type_view (name , type_view )
167+
168+ if not detect (field .type_view .annotation ):
169+ field = dataclasses .replace (field , mapper = None )
193170 fields .append (field )
194171 return fields
195172
@@ -202,7 +179,9 @@ def fields(cls: type):
202179 "Must be one of: dataclass, pydantic, or attrs class."
203180 )
204181
205- type_hints = get_type_hints (cls , include_extras = True )
182+ type_hints = {
183+ k : TypeView (v ) for k , v in get_type_hints (cls , include_extras = True ).items ()
184+ }
206185 return class_type .value .collect (cls , type_hints )
207186
208187
@@ -246,19 +225,3 @@ def from_cls(cls, obj: type) -> ClassTypes | None:
246225 return cls .attrs
247226
248227 return None
249-
250-
251- def get_type (value ):
252- if typing_inspect .is_optional_type (value ):
253- return get_args (value )[0 ]
254- return value
255-
256-
257- def get_annotation_args (annotation ) -> Tuple [Type , Tuple [Any , ...]]:
258- args : Tuple [Any , ...] = ()
259- if get_origin (annotation ) is Annotated :
260- args = get_args (annotation )
261- annotation , * _args = args
262- args = tuple (_args )
263-
264- return annotation , args
0 commit comments