@@ -150,7 +150,7 @@ def deprecated_func(*args, **kwargs):
150150def _download_file (
151151 url : str , output_file : Path , overwrite : bool = False , verbose = True
152152) -> None :
153- """Downloads a file.
153+ """Downloads a file with retry logic for network failures .
154154
155155 Parameters
156156 ----------
@@ -163,14 +163,36 @@ def _download_file(
163163 verbose : bool
164164 If true, print a download message, defaults to True.
165165 """
166+ import time
167+ from http .client import RemoteDisconnected
168+ from urllib .error import URLError
166169
167170 if output_file .exists () and not overwrite :
168171 return
169172
170173 if verbose :
171174 logger .info ("Downloading " + str (output_file ) + " from " + url + "." )
172- with urllib .request .urlopen (url ) as response , open (output_file , "wb" ) as out_file :
173- shutil .copyfileobj (response , out_file )
175+
176+ # Retry logic for intermittent network failures
177+ max_retries = 3
178+ retry_delay = 2 # seconds
179+
180+ for attempt in range (max_retries ):
181+ try :
182+ with urllib .request .urlopen (url , timeout = 30 ) as response , open (output_file , "wb" ) as out_file :
183+ shutil .copyfileobj (response , out_file )
184+ return # Success, exit function
185+ except (RemoteDisconnected , URLError , TimeoutError ) as e :
186+ if attempt < max_retries - 1 :
187+ logger .warning (
188+ f"Download attempt { attempt + 1 } /{ max_retries } failed: { e } . "
189+ f"Retrying in { retry_delay } seconds..."
190+ )
191+ time .sleep (retry_delay )
192+ retry_delay *= 2 # Exponential backoff
193+ else :
194+ logger .error (f"Download failed after { max_retries } attempts." )
195+ raise # Re-raise the exception after all retries fail
174196
175197
176198
0 commit comments