2626import logging
2727import random
2828import time
29+ import zipfile
30+ import io
2931from datetime import datetime
3032
3133import boto3
3739logging .basicConfig (level = "INFO" )
3840
3941
40- def _default_filename ():
41- return "smartapi_" + datetime .today ().strftime ("%Y%m%d" ) + ".json"
42+ def _default_filename (extension = ".json" ):
43+ return "smartapi_" + datetime .today ().strftime ("%Y%m%d" ) + extension
4244
4345
44- def save_to_file (mapping , filename = None ):
45- filename = filename or _default_filename ()
46- with open (filename , "w" ) as file :
47- json .dump (mapping , file , indent = 2 )
48-
49-
50- def save_to_s3 (mapping , filename = None , bucket = "smartapi" ):
51- filename = filename or _default_filename ()
46+ def save_to_file (mapping , filename = None , format = "zip" ):
47+ """
48+ Save data to a file in either JSON or ZIP format.
49+ :param mapping: Data to save
50+ :param filename: File name
51+ :param format: File format, either 'json' or 'zip'
52+ """
53+ if format == "zip" :
54+ filename = filename or _default_filename (".zip" )
55+ with zipfile .ZipFile (filename , 'w' , zipfile .ZIP_DEFLATED ) as zfile :
56+ json_data = json .dumps (mapping , indent = 2 )
57+ zfile .writestr (filename .replace (".zip" , ".json" ), json_data )
58+ else :
59+ filename = filename or _default_filename (".json" )
60+ with open (filename , "w" ) as file :
61+ json .dump (mapping , file , indent = 2 )
62+
63+
64+ def save_to_s3 (data , filename = None , bucket = "smartapi" , format = "zip" ):
65+ """
66+ Save data to S3 in either JSON or ZIP format.
67+ :param data: Data to save
68+ :param filename: File name
69+ :param bucket: S3 bucket name
70+ :param format: File format, either 'json' or 'zip'
71+ """
72+ filename = filename or _default_filename (f".{ format } " )
5273 s3 = boto3 .resource ("s3" )
53- s3 .Bucket (bucket ).put_object (Key = "db_backup/{}" .format (filename ), Body = json .dumps (mapping , indent = 2 ))
74+
75+ if format == "zip" :
76+ with zipfile .ZipFile (filename , 'w' , zipfile .ZIP_DEFLATED ) as zfile :
77+ json_data = json .dumps (data , indent = 2 )
78+ zfile .writestr (filename .replace (".zip" , ".json" ), json_data )
79+ logging .info (f"Uploading { filename } to AWS S3" )
80+ s3 .Bucket (bucket ).upload_file (Filename = filename , Key = f"db_backup/{ filename } " )
81+ else :
82+ logging .info (f"Uploading { filename } to AWS S3" )
83+ s3 .Bucket (bucket ).put_object (Key = f"db_backup/{ filename } " , Body = json .dumps (data , indent = 2 ))
5484
5585
5686def _backup ():
@@ -69,14 +99,14 @@ def _backup():
6999 return smartapis
70100
71101
72- def backup_to_file (filename = None ):
102+ def backup_to_file (filename = None , format = "zip" ):
73103 smartapis = _backup ()
74- save_to_file (smartapis , filename )
104+ save_to_file (smartapis , filename , format )
75105
76106
77- def backup_to_s3 (filename = None , bucket = "smartapi" ):
107+ def backup_to_s3 (filename = None , bucket = "smartapi" , format = "zip" ):
78108 smartapis = _backup ()
79- save_to_s3 (smartapis , filename , bucket )
109+ save_to_s3 (smartapis , filename , bucket , format )
80110
81111
82112def _restore (smartapis ):
@@ -99,7 +129,7 @@ def restore_from_s3(filename=None, bucket="smartapi"):
99129 s3 = boto3 .client ("s3" )
100130
101131 if not filename :
102- objects = s3 .list_objects_v2 (Bucket = "smartapi" , Prefix = "db_backup" )["Contents" ]
132+ objects = s3 .list_objects_v2 (Bucket = bucket , Prefix = "db_backup" )["Contents" ]
103133 filename = max (objects , key = lambda x : x ["LastModified" ])["Key" ]
104134
105135 if not filename .startswith ("db_backup/" ):
@@ -108,14 +138,42 @@ def restore_from_s3(filename=None, bucket="smartapi"):
108138 logging .info ("GET s3://%s/%s" , bucket , filename )
109139
110140 obj = s3 .get_object (Bucket = bucket , Key = filename )
111- smartapis = json .loads (obj ["Body" ].read ())
141+
142+ filename = filename .replace ("db_backup/" , "" )
143+
144+ if filename .endswith (".zip" ):
145+ file_content = obj ["Body" ].read ()
146+ with zipfile .ZipFile (io .BytesIO (file_content )) as zfile :
147+ # Search for a JSON file inside the ZIP
148+ json_file = next ((f for f in zfile .namelist () if f .endswith (".json" )), None )
149+ if not json_file :
150+ raise ValueError ("No JSON file found inside the ZIP archive." )
151+ with zfile .open (json_file ) as json_data :
152+ smartapis = json .load (json_data )
153+ elif filename .endswith (".json" ):
154+ smartapis = json .loads (obj ["Body" ].read ())
155+ else :
156+ raise Exception ("Unsupported backup file type!" )
157+
112158 _restore (smartapis )
113159
114160
115161def restore_from_file (filename ):
116- with open (filename ) as file :
117- smartapis = json .load (file )
118- _restore (smartapis )
162+ if filename .endswith (".zip" ):
163+ with zipfile .ZipFile (filename , 'r' ) as zfile :
164+ # Search for a JSON file inside the ZIP
165+ json_file = next ((f for f in zfile .namelist () if f .endswith (".json" )), None )
166+ if not json_file :
167+ raise ValueError ("No JSON file found inside the ZIP archive." )
168+ with zfile .open (json_file ) as json_data :
169+ smartapis = json .load (json_data )
170+ elif filename .endswith (".json" ):
171+ with open (filename ) as file :
172+ smartapis = json .load (file )
173+ else :
174+ raise Exception ("Unsupported backup file type!" )
175+
176+ _restore (smartapis )
119177
120178
121179def refresh_document ():
@@ -226,7 +284,7 @@ def refresh_has_metakg():
226284_lock = FileLock (".lock" , timeout = 0 )
227285
228286
229- def routine (no_backup = False ):
287+ def routine (no_backup = False , format = "zip" ):
230288 logger = logging .getLogger ("routine" )
231289
232290 # Add jitter: random delay between 100 and 500 milliseconds (adjust range as needed)
@@ -244,8 +302,8 @@ def routine(no_backup=False):
244302 if lock_acquired :
245303 logger .info ("Schedule lock acquired successfully." )
246304 if not no_backup :
247- logger .info ("backup_to_s3()" )
248- backup_to_s3 ()
305+ logger .info (f "backup_to_s3(format= { format } )" )
306+ backup_to_s3 (format = format )
249307 logger .info ("refresh_document()" )
250308 refresh_document ()
251309 logger .info ("check_uptime()" )
@@ -262,6 +320,7 @@ def routine(no_backup=False):
262320 logger .warning ("Schedule lock acquired by another process. No need to run it in this process." )
263321 except Exception as e :
264322 logger .error (f"An error occurred during the routine: { e } " )
323+ logger .error ("Stack trace:" , exc_info = True )
265324 finally :
266325 if lock_acquired :
267326 _lock .release ()
0 commit comments