Skip to content

Commit bb8bf64

Browse files
committed
Conditionally import _UnionGenericAlias
1 parent ddcf8b6 commit bb8bf64

2 files changed

Lines changed: 59 additions & 16 deletions

File tree

src/cattrs/_compat.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
_AnnotatedAlias,
3030
_GenericAlias,
3131
_SpecialGenericAlias,
32-
_UnionGenericAlias,
3332
get_args,
3433
get_origin,
3534
get_type_hints,
@@ -256,7 +255,22 @@ def is_tuple(type):
256255
)
257256

258257

259-
if sys.version_info >= (3, 10):
258+
if sys.version_info >= (3, 14):
259+
260+
def is_union_type(obj):
261+
from types import UnionType # noqa: PLC0415
262+
263+
return obj is Union or isinstance(obj, UnionType)
264+
265+
def get_newtype_base(typ: Any) -> Optional[type]:
266+
if typ is NewType or isinstance(typ, NewType):
267+
return typ.__supertype__
268+
return None
269+
270+
from typing import NotRequired, Required
271+
272+
elif sys.version_info >= (3, 10):
273+
from typing import _UnionGenericAlias
260274

261275
def is_union_type(obj):
262276
from types import UnionType # noqa: PLC0415
@@ -279,6 +293,8 @@ def get_newtype_base(typ: Any) -> Optional[type]:
279293

280294
else:
281295
# 3.9
296+
from typing import _UnionGenericAlias
297+
282298
from typing_extensions import NotRequired, Required
283299

284300
def is_union_type(obj):

tests/test_baseconverter.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,14 @@ def handler(obj, _):
149149
simple_typed_classes(
150150
defaults="never", newtypes=False, allow_nan=False, min_attrs=1
151151
),
152-
unstructure_strats,
153152
)
154-
def test_310_union_field_roundtrip(cl_and_vals_a, cl_and_vals_b, strat):
153+
def test_310_union_field_roundtrip_dict(cl_and_vals_a, cl_and_vals_b):
155154
"""
156155
Classes with union fields can be unstructured and structured.
157156
"""
158-
converter = BaseConverter(unstruct_strat=strat)
157+
converter = BaseConverter()
159158
cl_a, vals_a, kwargs_a = cl_and_vals_a
160159
cl_b, _, _ = cl_and_vals_b
161-
assume(strat is UnstructureStrategy.AS_DICT or not kwargs_a)
162160
a_field_names = {a.name for a in fields(cl_a)}
163161
b_field_names = {a.name for a in fields(cl_b)}
164162

@@ -171,18 +169,47 @@ class C:
171169

172170
inst = C(a=cl_a(*vals_a, **kwargs_a))
173171

174-
if strat is UnstructureStrategy.AS_DICT:
175-
assert inst == converter.structure(converter.unstructure(inst), C)
176-
else:
177-
# Our disambiguation functions only support dictionaries for now.
178-
with pytest.raises(StructureHandlerNotFoundError):
179-
converter.structure(converter.unstructure(inst), C)
172+
assert inst == converter.structure(converter.unstructure(inst), C)
180173

181-
def handler(obj, _):
182-
return converter.structure(obj, cl_a)
183174

184-
converter.register_structure_hook(cl_a | cl_b, handler)
185-
assert inst == converter.structure(converter.unstructure(inst), C)
175+
@pytest.mark.skipif(not is_py310_plus, reason="3.10+ union syntax")
176+
@settings(suppress_health_check=[HealthCheck.too_slow])
177+
@given(
178+
simple_typed_classes(
179+
defaults="never", newtypes=False, allow_nan=False, min_attrs=1, kw_only="never"
180+
),
181+
simple_typed_classes(
182+
defaults="never", newtypes=False, allow_nan=False, min_attrs=1, kw_only="never"
183+
),
184+
)
185+
def test_310_union_field_roundtrip_tuple(cl_and_vals_a, cl_and_vals_b):
186+
"""
187+
Classes with union fields can be unstructured and structured.
188+
"""
189+
converter = BaseConverter(unstruct_strat=UnstructureStrategy.AS_TUPLE)
190+
cl_a, vals_a, kwargs_a = cl_and_vals_a
191+
cl_b, _, _ = cl_and_vals_b
192+
a_field_names = {a.name for a in fields(cl_a)}
193+
b_field_names = {a.name for a in fields(cl_b)}
194+
195+
common_names = a_field_names & b_field_names
196+
assume(len(a_field_names) > len(common_names))
197+
198+
@define
199+
class C:
200+
a: cl_a | cl_b
201+
202+
inst = C(a=cl_a(*vals_a, **kwargs_a))
203+
204+
# Our disambiguation functions only support dictionaries for now.
205+
with pytest.raises(StructureHandlerNotFoundError):
206+
converter.structure(converter.unstructure(inst), C)
207+
208+
def handler(obj, _):
209+
return converter.structure(obj, cl_a)
210+
211+
converter.register_structure_hook(cl_a | cl_b, handler)
212+
assert inst == converter.structure(converter.unstructure(inst), C)
186213

187214

188215
@given(simple_typed_classes(defaults="never", newtypes=False, allow_nan=False))

0 commit comments

Comments
 (0)