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)