@@ -71,133 +71,100 @@ 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
-
136
74
def convert_timestamp_to_datetime (obj , model_fields ):
137
75
"""Convert Unix timestamps back to datetime objects based on model field types."""
138
76
if isinstance (obj , dict ):
139
77
result = {}
140
78
for key , value in obj .items ():
141
79
if key in model_fields :
142
80
field_info = model_fields [key ]
143
- field_type = getattr (field_info , "annotation" , None )
144
-
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 )
151
- ):
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
- )
81
+ field_type = (
82
+ field_info .annotation if hasattr (field_info , "annotation" ) else None
83
+ )
164
84
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
- ]
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 ()
175
109
else :
176
- result [key ] = convert_timestamp_to_datetime (value , {})
177
-
178
- # Handle other types
179
- else :
180
- if isinstance (value , (dict , list )):
181
- result [key ] = convert_timestamp_to_datetime (value , {})
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
+ )
182
125
else :
183
- result [key ] = value
184
- else :
185
- # No field type info, recurse for collections
186
- if isinstance (value , (dict , list )):
126
+ result [key ] = convert_timestamp_to_datetime (value , {})
127
+ except (TypeError , AttributeError ):
187
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__
138
+ ):
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 ):
157
+ result [key ] = convert_timestamp_to_datetime (value , {})
188
158
else :
189
- result [key ] = value
190
- else :
191
- # Key not in model fields, recurse for collections
192
- if isinstance (value , (dict , list )):
193
- result [key ] = convert_timestamp_to_datetime (value , {})
159
+ result [key ] = convert_timestamp_to_datetime (value , {})
194
160
else :
195
- result [key ] = value
161
+ result [key ] = convert_timestamp_to_datetime (value , {})
162
+ else :
163
+ # For keys not in model_fields, still recurse but with empty field info
164
+ result [key ] = convert_timestamp_to_datetime (value , {})
196
165
return result
197
-
198
166
elif isinstance (obj , list ):
199
167
return [convert_timestamp_to_datetime (item , model_fields ) for item in obj ]
200
-
201
168
else :
202
169
return obj
203
170
0 commit comments