Files
2026-07-13 13:22:34 +08:00

46 lines
1.9 KiB
Python

from dataclasses import fields, is_dataclass
from types import UnionType
from typing import Union, get_args, get_origin
def _is_optional_dataclass(field_type) -> bool:
"""
Check if the field type is an Optional containing a dataclass.
Currently, ... | None (in Python 3.10) is not supported.
"""
if get_origin(field_type) in (Union, UnionType):
inner_types = get_args(field_type)
# Check if it's a Union[Dataclass, NoneType] (i.e., Optional[Dataclass])
if len(inner_types) == 2 and any(t is type(None) for t in inner_types):
effective_type = next(t for t in get_args(field_type) if t is not type(None))
return is_dataclass(effective_type)
return False
def _hydrate_dataclass(dataclass_type, data):
"""Recursively create an instance of the dataclass_type from data."""
if not (is_dataclass(dataclass_type) or _is_optional_dataclass(dataclass_type)):
raise ValueError(f"{dataclass_type.__name__} is not a dataclass")
if data is None:
return None
field_names = {f.name: f.type for f in fields(dataclass_type)}
kwargs = {}
for key, field_type in field_names.items():
if key in data:
value = data[key]
if is_dataclass(field_type):
kwargs[key] = _hydrate_dataclass(field_type, value)
elif _is_optional_dataclass(field_type):
effective_type = next(t for t in get_args(field_type) if t is not type(None))
kwargs[key] = _hydrate_dataclass(effective_type, value)
elif get_origin(field_type) == list:
item_type = get_args(field_type)[0]
if is_dataclass(item_type):
kwargs[key] = [_hydrate_dataclass(item_type, item) for item in value]
else:
kwargs[key] = value
else:
kwargs[key] = value
return dataclass_type(**kwargs)