11import  uuid 
22from  datetime  import  datetime 
3+ from  datetime  import  timezone 
34from  typing  import  Optional 
45
6+ from  sqlalchemy  import  DateTime 
57from  sqlalchemy .orm  import  Mapped 
68from  sqlalchemy .orm  import  mapped_column 
9+ from  sqlalchemy .orm  import  validates 
710
811
912def  make_uuid4 () ->  uuid .UUID :
@@ -13,10 +16,56 @@ def make_uuid4() -> uuid.UUID:
1316class  ModelMixin :
1417    """Mixin for table model.""" 
1518
19+     EMPTY_VALUES : tuple [None , list , dict , tuple ] =  (None , [], {}, ())
20+ 
1621    id : Mapped [uuid .UUID ] =  mapped_column (primary_key = True , default = make_uuid4 , comment = 'UUID' )
1722    created_at : Mapped [datetime ] =  mapped_column (
18-         default = datetime .utcnow , comment = 'Date and time created' 
23+         DateTime ( timezone = True ),  default = datetime .utcnow , comment = 'Date and time created' 
1924    )
2025    updated_at : Mapped [Optional [datetime ]] =  mapped_column (
21-         onupdate = datetime .utcnow , comment = 'Date and time updated' 
26+         DateTime ( timezone = True ),  onupdate = datetime .utcnow , comment = 'Date and time updated' 
2227    )
28+ 
29+     @staticmethod  
30+     def  validate_utc_timezone (key : str , value : datetime  or  None ) ->  datetime :
31+         time_zone  =  getattr (value , 'tzinfo' , None )
32+         if  time_zone  !=  timezone .utc :
33+             raise  ValueError (
34+                 f'Field <{ key }  
35+                 f' but received timezone: <{ time_zone }  
36+             )
37+ 
38+         return  value 
39+ 
40+     @validates ('created_at' , 'updated_at' ) 
41+     def  validate_timezone (self , key , value ) ->  datetime :
42+         return  self .validate_utc_timezone (key , value )
43+ 
44+     def  to_dict (self , fields : dict [str , dict  or  list  or  Ellipsis ]):
45+         data  =  {}
46+         for  field , value  in  fields .items ():
47+             value_from_db  =  None 
48+ 
49+             if  value  is  Ellipsis :
50+                 value_from_db  =  getattr (self , field )
51+ 
52+             elif  isinstance (value , dict ):
53+                 obj  =  getattr (self , field )
54+                 if  obj :
55+                     value_from_db  =  obj .to_dict (value )
56+ 
57+             elif  isinstance (value , list ):
58+                 value_from_db  =  []
59+                 for  obj  in  getattr (self , field ):
60+                     for  item  in  value :
61+                         dictable_value_from_db  =  obj .to_dict (item )
62+                         if  dictable_value_from_db  not  in self .EMPTY_VALUES :
63+                             value_from_db .append (dictable_value_from_db )
64+ 
65+             else :
66+                 value_from_db  =  self .to_dict (value )
67+ 
68+             if  value_from_db  not  in self .EMPTY_VALUES :
69+                 data [field ] =  value_from_db 
70+ 
71+         return  data 
0 commit comments