Skip to content

Commit d872af2

Browse files
committed
Account for negative seek on empty file
1 parent eb6b89e commit d872af2

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

smart_open/s3.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,12 @@ def _open_body(self, start=None, stop=None):
586586
raise
587587

588588
actual_object_size = int(error_response.get('ActualObjectSize', 0))
589-
if start >= actual_object_size: # empty file or start is past end of file
589+
if (
590+
# empty file (==) or start is past end of file (>)
591+
(start is not None and start >= actual_object_size)
592+
# empty file (==) or negative seek like seek(-4, WHENCE_END) is past end of file (>)
593+
or (start is None and stop is not None and stop >= actual_object_size)
594+
):
590595
self._position = self._content_length = actual_object_size
591596
self._body = io.BytesIO()
592597
else: # stop is past end of file: request the correct remainder instead

tests/test_s3.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,15 @@ def test_seek_past_end(self):
319319
seek = fin.seek(60)
320320
self.assertEqual(seek, len(self.body))
321321

322+
def test_seek_past_end_from_end(self):
323+
"""Test seeking from end with offset larger than file size."""
324+
body_len = len(self.body)
325+
with self.assertApiCalls(GetObject=1), patch_invalid_range_response(str(body_len)):
326+
fin = smart_open.s3.Reader(BUCKET_NAME, KEY_NAME, defer_seek=True)
327+
seek = fin.seek(-(body_len + 10), whence=smart_open.constants.WHENCE_END)
328+
self.assertEqual(seek, 0) # Should clamp to start of file
329+
330+
322331
def test_detect_eof(self):
323332
with self.assertApiCalls(GetObject=1):
324333
fin = smart_open.s3.Reader(BUCKET_NAME, KEY_NAME)
@@ -470,6 +479,14 @@ def test_read_empty_file_no_actual_size(self):
470479
# a subsequent read does not call _open_body
471480
self.assertEqual(fin.read(), b'')
472481

482+
def test_seek_empty_file_from_end(self):
483+
"""Test seeking from end on an empty file."""
484+
_resource('s3').Object(BUCKET_NAME, KEY_NAME).put(Body=b'')
485+
with self.assertApiCalls(GetObject=1), patch_invalid_range_response('0'):
486+
with smart_open.s3.Reader(BUCKET_NAME, KEY_NAME, defer_seek=True) as fin:
487+
seek = fin.seek(-10, whence=smart_open.constants.WHENCE_END)
488+
self.assertEqual(seek, 0) # Should be at position 0 for empty file
489+
473490

474491
@mock_s3
475492
class MultipartWriterTest(unittest.TestCase):

0 commit comments

Comments
 (0)