@@ -217,7 +217,7 @@ def __init__(self, ec, logfile=None):
217217 self .skip = None
218218 self .module_extra_extensions = '' # extra stuff for module file required by extensions
219219
220- # indicates whether or not this instance represents an extension or not;
220+ # indicates whether or not this instance represents an extension
221221 # may be set to True by ExtensionEasyBlock
222222 self .is_extension = False
223223
@@ -1023,64 +1023,18 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
10231023
10241024 mkdir (targetdir , parents = True )
10251025
1026- for url in source_urls :
1027-
1028- if extension :
1029- targetpath = os .path .join (targetdir , "extensions" , filename )
1030- else :
1031- targetpath = os .path .join (targetdir , filename )
1032-
1033- url_filename = download_filename or filename
1034-
1035- if isinstance (url , str ):
1036- if url [- 1 ] in ['=' , '/' ]:
1037- fullurl = "%s%s" % (url , url_filename )
1038- else :
1039- fullurl = "%s/%s" % (url , url_filename )
1040- elif isinstance (url , tuple ):
1041- # URLs that require a suffix, e.g., SourceForge download links
1042- # e.g. http://sourceforge.net/projects/math-atlas/files/Stable/3.8.4/atlas3.8.4.tar.bz2/download
1043- fullurl = "%s/%s/%s" % (url [0 ], url_filename , url [1 ])
1044- else :
1045- self .log .warning ("Source URL %s is of unknown type, so ignoring it." % url )
1046- continue
1047-
1048- # PyPI URLs may need to be converted due to change in format of these URLs,
1049- # cfr. https://bitbucket.org/pypa/pypi/issues/438
1050- if PYPI_PKG_URL_PATTERN in fullurl and not is_alt_pypi_url (fullurl ):
1051- alt_url = derive_alt_pypi_url (fullurl )
1052- if alt_url :
1053- _log .debug ("Using alternative PyPI URL for %s: %s" , fullurl , alt_url )
1054- fullurl = alt_url
1055- else :
1056- _log .debug ("Failed to derive alternative PyPI URL for %s, so retaining the original" ,
1057- fullurl )
1058-
1059- if self .dry_run :
1060- self .dry_run_msg (" * %s will be downloaded to %s" , filename , targetpath )
1061- if extension and urls :
1062- # extensions typically have custom source URLs specified, only mention first
1063- self .dry_run_msg (" (from %s, ...)" , fullurl )
1064- downloaded = True
1065-
1066- else :
1067- self .log .debug ("Trying to download file %s from %s to %s ..." % (filename , fullurl , targetpath ))
1068- downloaded = False
1069- try :
1070- if download_file (filename , fullurl , targetpath ):
1071- downloaded = True
1026+ if extension :
1027+ target_path = os .path .join (targetdir , "extensions" , filename )
1028+ else :
1029+ target_path = os .path .join (targetdir , filename )
1030+ downloaded , failed_urls = self .download_file (target_path , download_filename = download_filename or filename ,
1031+ urls = source_urls ,
1032+ # Only log when extensions have explicit URLs
1033+ log_url_in_dry_run = extension and urls )
1034+ if downloaded :
1035+ return target_path
10721036
1073- except IOError as err :
1074- self .log .debug ("Failed to download %s from %s: %s" % (filename , url , err ))
1075- failedpaths .append (fullurl )
1076- continue
1077-
1078- if downloaded :
1079- # if fetching from source URL worked, we're done
1080- self .log .info ("Successfully downloaded source file %s from %s" % (filename , fullurl ))
1081- return targetpath
1082- else :
1083- failedpaths .append (fullurl )
1037+ failedpaths .extend (failed_urls )
10841038
10851039 if self .dry_run :
10861040 self .dry_run_msg (" * %s (MISSING)" , filename )
@@ -1107,20 +1061,82 @@ def obtain_file(self, filename, extension=False, urls=None, download_filename=No
11071061 self .log .warning (error_msg , filename )
11081062 return None
11091063
1064+ def download_file (self , target_path , download_filename , urls , log_url_in_dry_run ):
1065+ """Try downloading a file from multiple source URLs, until one works.
1066+
1067+ :param target_path: Full path where the file should be stored (including filename)
1068+ :param download_filename: Name of the file on the server (which may differ from the target filename)
1069+ :param source_urls: list of URLs to try downloading from
1070+ :param log_url_in_dry_run: whether to log each URL in dry-run mode
1071+
1072+ Returns a tuple of (success, failed_urls), where
1073+ success is True if the file was successfully downloaded from any of the source URLs
1074+ failed_urls is a list of (full) URLs that were attempted but failed to download from
1075+ """
1076+ failed_urls = []
1077+ filename = os .path .basename (target_path )
1078+ for url in urls :
1079+ if isinstance (url , str ):
1080+ if url [- 1 ] in ['=' , '/' ]:
1081+ full_url = "%s%s" % (url , download_filename )
1082+ else :
1083+ full_url = "%s/%s" % (url , download_filename )
1084+ elif isinstance (url , tuple ):
1085+ # URLs that require a suffix, e.g., SourceForge download links
1086+ # e.g. http://sourceforge.net/projects/math-atlas/files/Stable/3.8.4/atlas3.8.4.tar.bz2/download
1087+ full_url = "%s/%s/%s" % (url [0 ], download_filename , url [1 ])
1088+ else :
1089+ self .log .warning ("Source URL %s is of unknown type, so ignoring it." % url )
1090+ continue
1091+
1092+ # PyPI URLs may need to be converted due to change in format of these URLs,
1093+ # cfr. https://bitbucket.org/pypa/pypi/issues/438
1094+ if PYPI_PKG_URL_PATTERN in full_url and not is_alt_pypi_url (full_url ):
1095+ alt_url = derive_alt_pypi_url (full_url )
1096+ if alt_url :
1097+ _log .debug ("Using alternative PyPI URL for %s: %s" , full_url , alt_url )
1098+ full_url = alt_url
1099+ else :
1100+ _log .debug ("Failed to derive alternative PyPI URL for %s, so retaining the original" ,
1101+ full_url )
1102+
1103+ if self .dry_run :
1104+ msg = f" * { filename } will be downloaded to { target_path } "
1105+ if self .log_url_in_dry_run :
1106+ msg += f" from { full_url } "
1107+ self .dry_run_msg (msg )
1108+ downloaded = True
1109+
1110+ else :
1111+ self .log .debug ("Trying to download file %s from %s to %s ..." % (filename , full_url , target_path ))
1112+ try :
1113+ downloaded = download_file (filename , full_url , target_path )
1114+ except IOError as err :
1115+ self .log .debug ("Failed to download %s from %s: %s" % (filename , url , err ))
1116+ downloaded = False
1117+
1118+ if downloaded :
1119+ # if fetching from source URL worked, we're done
1120+ self .log .info ("Successfully downloaded source file %s from %s" % (filename , full_url ))
1121+ return True , failed_urls
1122+ else :
1123+ failed_urls .append (full_url )
1124+ return False , failed_urls
1125+
11101126 #
11111127 # GETTER/SETTER UTILITY FUNCTIONS
11121128 #
11131129 @property
11141130 def name (self ):
11151131 """
1116- Shortcut the get the module name.
1132+ Shortcut to get the module name.
11171133 """
11181134 return self .cfg ['name' ]
11191135
11201136 @property
11211137 def version (self ):
11221138 """
1123- Shortcut the get the module version.
1139+ Shortcut to get the module version.
11241140 """
11251141 return self .cfg ['version' ]
11261142
0 commit comments