@@ -217,7 +217,7 @@ def write_file(path, data, append=False, forced=False, backup=False, always_over
217217 overwrites current file contents without backup by default!
218218
219219 :param path: location of file
220- :param data: contents to write to file
220+ :param data: contents to write to file. Can be a file-like object of binary data
221221 :param append: append to existing file rather than overwrite
222222 :param forced: force actually writing file in (extended) dry run mode
223223 :param backup: back up existing file before overwriting or modifying it
@@ -246,15 +246,21 @@ def write_file(path, data, append=False, forced=False, backup=False, always_over
246246 # cfr. https://docs.python.org/3/library/functions.html#open
247247 mode = 'a' if append else 'w'
248248
249+ data_is_file_obj = hasattr (data , 'read' )
250+
249251 # special care must be taken with binary data in Python 3
250- if sys .version_info [0 ] >= 3 and isinstance (data , bytes ):
252+ if sys .version_info [0 ] >= 3 and ( isinstance (data , bytes ) or data_is_file_obj ):
251253 mode += 'b'
252254
253255 # note: we can't use try-except-finally, because Python 2.4 doesn't support it as a single block
254256 try :
255257 mkdir (os .path .dirname (path ), parents = True )
256258 with open_file (path , mode ) as fh :
257- fh .write (data )
259+ if data_is_file_obj :
260+ # if a file-like object was provided, use copyfileobj (which reads the file in chunks)
261+ shutil .copyfileobj (data , fh )
262+ else :
263+ fh .write (data )
258264 except IOError as err :
259265 raise EasyBuildError ("Failed to write to %s: %s" , path , err )
260266
@@ -710,7 +716,11 @@ def download_file(filename, url, path, forced=False):
710716 url_fd = response .raw
711717 url_fd .decode_content = True
712718 _log .debug ('response code for given url %s: %s' % (url , status_code ))
713- write_file (path , url_fd .read (), forced = forced , backup = True )
719+ # note: we pass the file object to write_file rather than reading the file first,
720+ # to ensure the data is read in chunks (which prevents problems in Python 3.9+);
721+ # cfr. https://github.com/easybuilders/easybuild-framework/issues/3455
722+ # and https://bugs.python.org/issue42853
723+ write_file (path , url_fd , forced = forced , backup = True )
714724 _log .info ("Downloaded file %s from url %s to %s" % (filename , url , path ))
715725 downloaded = True
716726 url_fd .close ()
0 commit comments