Skip to content

Commit ee4c6b4

Browse files
committed
Support Python 3.14 unions
1 parent b84d7f7 commit ee4c6b4

1 file changed

Lines changed: 28 additions & 14 deletions

File tree

datafiles/converters/__init__.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ def register(cls: Union[type, str], converter: type):
3737
register(dict, Dictionary)
3838

3939

40+
def _convert_union(args: tuple[type, ...]) -> type | None:
41+
"""Return converter for currently supported unions."""
42+
if len(args) != 2:
43+
return None
44+
none_type = type(None)
45+
if args[0] is none_type or args[1] is none_type:
46+
value_type = args[1] if args[0] is none_type else args[0]
47+
return map_type(value_type).as_optional()
48+
if str in args:
49+
return map_type(str)
50+
if args in {(int, float), (float, int)}:
51+
return Number
52+
return None
53+
54+
4055
@cached
4156
def map_type(cls, *, name: str = "", item_cls: Optional[type] = None):
4257
"""Infer the converter type from a dataclass, type, or annotation."""
@@ -59,11 +74,10 @@ def map_type(cls, *, name: str = "", item_cls: Optional[type] = None):
5974
return converter
6075

6176
if hasattr(types, "UnionType") and isinstance(cls, types.UnionType): # type: ignore
62-
# Python 3.10 behavior
63-
converter = map_type(cls.__args__[0])
64-
assert len(cls.__args__) == 2
65-
assert cls.__args__[1] == type(None)
66-
converter = converter.as_optional()
77+
args = tuple(getattr(cls, "__args__", ()))
78+
converter = _convert_union(args)
79+
if converter is None:
80+
raise TypeError(f"Unsupported union type: {cls}")
6781
return converter
6882

6983
if hasattr(cls, "__origin__"):
@@ -114,16 +128,16 @@ def map_type(cls, *, name: str = "", item_cls: Optional[type] = None):
114128
converter = Dictionary.of_mapping(key, value)
115129

116130
elif cls.__origin__ == Union:
117-
if str in cls.__args__:
118-
converter = map_type(str)
119-
if type(None) in cls.__args__:
120-
converter = converter.as_optional()
121-
elif cls.__args__ in {(int, float), (float, int)}:
122-
converter = Number
131+
args = tuple(cls.__args__)
132+
if len(args) == 2:
133+
converter = _convert_union(args)
123134
else:
124-
assert len(cls.__args__) == 2
125-
assert cls.__args__[1] == type(None)
126-
converter = map_type(cls.__args__[0]).as_optional()
135+
if str in cls.__args__:
136+
converter = map_type(str)
137+
if type(None) in cls.__args__:
138+
converter = converter.as_optional()
139+
if converter is None:
140+
raise TypeError(f"Unsupported union type: {cls}")
127141

128142
elif issubclass(cls.__origin__, Converter):
129143
subtypes = [map_type(t) for t in cls.__args__]

0 commit comments

Comments
 (0)