@@ -71,133 +71,100 @@ def convert_datetime_to_timestamp(obj):
7171 return obj
7272
7373
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-
13674def convert_timestamp_to_datetime (obj , model_fields ):
13775 """Convert Unix timestamps back to datetime objects based on model field types."""
13876 if isinstance (obj , dict ):
13977 result = {}
14078 for key , value in obj .items ():
14179 if key in model_fields :
14280 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+ )
16484
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 ()
175109 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+ )
182125 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 ):
187128 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 , {})
188158 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 , {})
194160 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 , {})
196165 return result
197-
198166 elif isinstance (obj , list ):
199167 return [convert_timestamp_to_datetime (item , model_fields ) for item in obj ]
200-
201168 else :
202169 return obj
203170
0 commit comments