@@ -71,100 +71,133 @@ def convert_datetime_to_timestamp(obj):
71
71
return obj
72
72
73
73
74
+ def _extract_base_type (field_type ):
75
+ """Extract the base type from Optional or Union types."""
76
+ if hasattr (field_type , "__origin__" ) and field_type .__origin__ is Union :
77
+ # For Optional[T] which is Union[T, None], get the non-None type
78
+ args = getattr (field_type , "__args__" , ())
79
+ non_none_types = [
80
+ arg for arg in args if getattr (arg , "__name__" , None ) != "NoneType"
81
+ ]
82
+ if len (non_none_types ) == 1 :
83
+ return non_none_types [0 ]
84
+ return field_type
85
+
86
+
87
+ def _is_datetime_type (field_type ):
88
+ """Check if field type is a datetime or date type."""
89
+ return field_type in (datetime .datetime , datetime .date )
90
+
91
+
92
+ def _has_model_fields (type_obj ):
93
+ """Safely check if a type has model_fields attribute."""
94
+ return (
95
+ isinstance (type_obj , type )
96
+ and hasattr (type_obj , "model_fields" )
97
+ and type_obj .model_fields
98
+ )
99
+
100
+
101
+ def _convert_timestamp_value (value , target_type ):
102
+ """Convert a timestamp value to datetime/date with proper error handling."""
103
+ if not isinstance (value , (int , float , str )):
104
+ return value
105
+
106
+ try :
107
+ if isinstance (value , str ):
108
+ value = float (value )
109
+
110
+ # Convert to datetime - preserve original timezone behavior exactly
111
+ dt = datetime .datetime .fromtimestamp (value )
112
+
113
+ # Return date if target is date type
114
+ if target_type is datetime .date :
115
+ return dt .date ()
116
+ else :
117
+ return dt
118
+
119
+ except (ValueError , OSError , OverflowError ):
120
+ # Invalid timestamp, return original value
121
+ return value
122
+
123
+
124
+ def _get_list_inner_type (field_type ):
125
+ """Extract inner type from List[T] annotation."""
126
+ if (
127
+ hasattr (field_type , "__origin__" )
128
+ and field_type .__origin__ in (list , List )
129
+ and hasattr (field_type , "__args__" )
130
+ and field_type .__args__
131
+ ):
132
+ return field_type .__args__ [0 ]
133
+ return None
134
+
135
+
74
136
def convert_timestamp_to_datetime (obj , model_fields ):
75
137
"""Convert Unix timestamps back to datetime objects based on model field types."""
76
138
if isinstance (obj , dict ):
77
139
result = {}
78
140
for key , value in obj .items ():
79
141
if key in model_fields :
80
142
field_info = model_fields [key ]
81
- field_type = (
82
- field_info .annotation if hasattr (field_info , "annotation" ) else None
83
- )
143
+ field_type = getattr (field_info , "annotation" , None )
84
144
85
- # Handle Optional types - extract the inner type
86
- if hasattr (field_type , "__origin__" ) and field_type .__origin__ is Union :
87
- # For Optional[T] which is Union[T, None], get the non-None type
88
- args = getattr (field_type , "__args__" , ())
89
- non_none_types = [
90
- arg
91
- for arg in args
92
- if getattr (arg , "__name__" , None ) != "NoneType"
93
- ]
94
- if len (non_none_types ) == 1 :
95
- field_type = non_none_types [0 ]
96
-
97
- # Handle direct datetime/date fields
98
- if field_type in (datetime .datetime , datetime .date ) and isinstance (
99
- value , (int , float , str )
100
- ):
101
- try :
102
- if isinstance (value , str ):
103
- value = float (value )
104
- # Use fromtimestamp to preserve local timezone behavior
105
- dt = datetime .datetime .fromtimestamp (value )
106
- # If the field is specifically a date, convert to date
107
- if field_type is datetime .date :
108
- result [key ] = dt .date ()
109
- else :
110
- result [key ] = dt
111
- except (ValueError , OSError ):
112
- result [key ] = value # Keep original value if conversion fails
113
- # Handle nested models - check if it's a RedisModel subclass
114
- elif isinstance (value , dict ):
115
- try :
116
- # Check if field_type is a class and subclass of RedisModel
117
- if (
118
- isinstance (field_type , type )
119
- and hasattr (field_type , "model_fields" )
120
- and field_type .model_fields
121
- ):
122
- result [key ] = convert_timestamp_to_datetime (
123
- value , field_type .model_fields
124
- )
125
- else :
126
- result [key ] = convert_timestamp_to_datetime (value , {})
127
- except (TypeError , AttributeError ):
128
- result [key ] = convert_timestamp_to_datetime (value , {})
129
- # Handle lists that might contain nested models
130
- elif isinstance (value , list ):
131
- # Try to extract the inner type from List[SomeModel]
132
- inner_type = None
133
- if (
134
- hasattr (field_type , "__origin__" )
135
- and field_type .__origin__ in (list , List )
136
- and hasattr (field_type , "__args__" )
137
- and field_type .__args__
145
+ if field_type :
146
+ base_type = _extract_base_type (field_type )
147
+
148
+ # Handle datetime/date fields
149
+ if _is_datetime_type (base_type ) and isinstance (
150
+ value , (int , float , str )
138
151
):
139
- inner_type = field_type .__args__ [0 ]
140
-
141
- # Check if the inner type is a nested model
142
- try :
143
- if (
144
- isinstance (inner_type , type )
145
- and hasattr (inner_type , "model_fields" )
146
- and inner_type .model_fields
147
- ):
148
- result [key ] = [
149
- convert_timestamp_to_datetime (
150
- item , inner_type .model_fields
151
- )
152
- for item in value
153
- ]
154
- else :
155
- result [key ] = convert_timestamp_to_datetime (value , {})
156
- except (TypeError , AttributeError ):
152
+ result [key ] = _convert_timestamp_value (value , base_type )
153
+
154
+ # Handle nested dictionaries (models)
155
+ elif isinstance (value , dict ):
156
+ nested_fields = (
157
+ base_type .model_fields
158
+ if _has_model_fields (base_type )
159
+ else {}
160
+ )
161
+ result [key ] = convert_timestamp_to_datetime (
162
+ value , nested_fields
163
+ )
164
+
165
+ # Handle lists
166
+ elif isinstance (value , list ):
167
+ inner_type = _get_list_inner_type (field_type )
168
+ if inner_type and _has_model_fields (inner_type ):
169
+ result [key ] = [
170
+ convert_timestamp_to_datetime (
171
+ item , inner_type .model_fields
172
+ )
173
+ for item in value
174
+ ]
175
+ else :
157
176
result [key ] = convert_timestamp_to_datetime (value , {})
177
+
178
+ # Handle other types
158
179
else :
159
- result [key ] = convert_timestamp_to_datetime (value , {})
180
+ if isinstance (value , (dict , list )):
181
+ result [key ] = convert_timestamp_to_datetime (value , {})
182
+ else :
183
+ result [key ] = value
160
184
else :
161
- result [key ] = convert_timestamp_to_datetime (value , {})
185
+ # No field type info, recurse for collections
186
+ if isinstance (value , (dict , list )):
187
+ result [key ] = convert_timestamp_to_datetime (value , {})
188
+ else :
189
+ result [key ] = value
162
190
else :
163
- # For keys not in model_fields, still recurse but with empty field info
164
- result [key ] = convert_timestamp_to_datetime (value , {})
191
+ # Key not in model fields, recurse for collections
192
+ if isinstance (value , (dict , list )):
193
+ result [key ] = convert_timestamp_to_datetime (value , {})
194
+ else :
195
+ result [key ] = value
165
196
return result
197
+
166
198
elif isinstance (obj , list ):
167
199
return [convert_timestamp_to_datetime (item , model_fields ) for item in obj ]
200
+
168
201
else :
169
202
return obj
170
203
0 commit comments