|
8 | 8 | from typing import Any, Dict, Type |
9 | 9 |
|
10 | 10 | import iris |
11 | | -from pydantic import BaseModel, TypeAdapter |
| 11 | +from pydantic import BaseModel, TypeAdapter, ValidationError |
12 | 12 |
|
13 | 13 | from iop._message import _PydanticPickleMessage |
14 | 14 | from iop._utils import _Utils |
@@ -108,38 +108,42 @@ def _parse_classname(classname: str) -> tuple[str, str]: |
108 | 108 | return classname[:j], classname[j+1:] |
109 | 109 |
|
110 | 110 | def dataclass_from_dict(klass: Type, dikt: Dict) -> Any: |
111 | | - field_types = { |
112 | | - key: val.annotation |
113 | | - for key, val in inspect.signature(klass).parameters.items() |
114 | | - } |
115 | | - processed_dict = {} |
116 | | - for key, val in inspect.signature(klass).parameters.items(): |
117 | | - if key not in dikt and val.default != val.empty: |
118 | | - processed_dict[key] = val.default |
119 | | - continue |
120 | | - |
121 | | - value = dikt.get(key) |
| 111 | + """Converts a dictionary to a dataclass instance. |
| 112 | + Handles non attended fields and nested dataclasses.""" |
| 113 | + |
| 114 | + def process_field(value: Any, field_type: Type) -> Any: |
122 | 115 | if value is None: |
123 | | - processed_dict[key] = None |
| 116 | + return None |
| 117 | + if is_dataclass(field_type): |
| 118 | + return dataclass_from_dict(field_type, value) |
| 119 | + if field_type != inspect.Parameter.empty: |
| 120 | + try: |
| 121 | + return TypeAdapter(field_type).validate_python(value) |
| 122 | + except ValidationError: |
| 123 | + return value |
| 124 | + return value |
| 125 | + |
| 126 | + # Get field definitions from class signature |
| 127 | + fields = inspect.signature(klass).parameters |
| 128 | + field_dict = {} |
| 129 | + |
| 130 | + # Process each field |
| 131 | + for field_name, field_info in fields.items(): |
| 132 | + if field_name not in dikt: |
| 133 | + if field_info.default != field_info.empty: |
| 134 | + field_dict[field_name] = field_info.default |
124 | 135 | continue |
125 | | - |
126 | | - try: |
127 | | - field_type = field_types[key] |
128 | | - if field_type != inspect.Parameter.empty: |
129 | | - adapter = TypeAdapter(field_type) |
130 | | - processed_dict[key] = adapter.validate_python(value) |
131 | | - else: |
132 | | - processed_dict[key] = value |
133 | | - except Exception: |
134 | | - processed_dict[key] = value |
135 | | - |
136 | | - instance = klass( |
137 | | - **processed_dict |
138 | | - ) |
139 | | - # handle any extra fields |
140 | | - for k, v in dikt.items(): |
141 | | - if k not in processed_dict: |
142 | | - setattr(instance, k, v) |
| 136 | + |
| 137 | + field_dict[field_name] = process_field(dikt[field_name], field_info.annotation) |
| 138 | + |
| 139 | + # Create instance |
| 140 | + instance = klass(**field_dict) |
| 141 | + |
| 142 | + # Add any extra fields not in the dataclass definition |
| 143 | + for key, value in dikt.items(): |
| 144 | + if key not in field_dict: |
| 145 | + setattr(instance, key, value) |
| 146 | + |
143 | 147 | return instance |
144 | 148 |
|
145 | 149 | def dataclass_to_dict(instance: Any) -> Dict: |
|
0 commit comments