diff --git a/s3transfer/compat.py b/s3transfer/compat.py index 85ae1126..df54bd54 100644 --- a/s3transfer/compat.py +++ b/s3transfer/compat.py @@ -54,6 +54,20 @@ def accepts_kwargs(func): MAXINT = sys.maxint +def writes_are_seekable(fileobj): + """Backwards compat function to determine if writes to a fileobj are seekable + + :param fileobj: The file-like object to determine if writes are seekable + + :returns: True, if in append mode. False, otherwise. + """ + APPEND_ONLY_MODE = 'a' + is_seekable = seekable(fileobj) + if hasattr(fileobj, 'mode') and APPEND_ONLY_MODE in fileobj.mode: + is_seekable = False + return is_seekable + + def seekable(fileobj): """Backwards compat function to determine if a fileobj is seekable diff --git a/s3transfer/download.py b/s3transfer/download.py index d7ca2b4b..48ba30e5 100644 --- a/s3transfer/download.py +++ b/s3transfer/download.py @@ -24,7 +24,7 @@ ReadTimeoutError from s3transfer.compat import SOCKET_ERROR -from s3transfer.compat import seekable +from s3transfer.compat import seekable, writes_are_seekable from s3transfer.exceptions import RetriesExceededError from s3transfer.futures import IN_MEMORY_DOWNLOAD_TAG from s3transfer.utils import random_file_extension @@ -202,7 +202,7 @@ def _get_temp_fileobj(self): class DownloadSeekableOutputManager(DownloadOutputManager): @classmethod def is_compatible(cls, download_target, osutil): - return seekable(download_target) + return writes_are_seekable(download_target) def get_fileobj_for_io_writes(self, transfer_future): # Return the fileobj provided to the future. diff --git a/tests/unit/test_compat.py b/tests/unit/test_compat.py index e90c3695..9bcfe88b 100644 --- a/tests/unit/test_compat.py +++ b/tests/unit/test_compat.py @@ -17,7 +17,7 @@ from botocore.compat import six from tests import unittest -from s3transfer.compat import seekable, readable +from s3transfer.compat import seekable, readable, writes_are_seekable class ErrorRaisingSeekWrapper(object): @@ -49,6 +49,12 @@ def test_seekable_fileobj(self): with open(self.filename, 'w') as f: self.assertTrue(seekable(f)) + def test_non_seekable_append_fileobj(self): + with open(self.filename, 'a') as f: + self.assertFalse(writes_are_seekable(f)) + with open(self.filename, 'a+') as f: + self.assertFalse(writes_are_seekable(f)) + def test_non_file_like_obj(self): # Fails becase there is no seekable(), seek(), nor tell() self.assertFalse(seekable(object()))