From a recent application, I have found tarfile._Stream doesn't have a seekable() method. A quick reproduction of the problem is as follows:
importtarfileimportiodata=b"Test data."buf=io.BytesIO()
withtarfile.open(fileobj=buf, mode="w") astf:
ti=tarfile.TarInfo(name="test.txt")
ti.size=len(data)
tf.addfile(tarinfo=ti, fileobj=io.BytesIO(data))
buf.seek(0)
tf=tarfile.open(fileobj=buf, mode="r|")
forentryintf:
f=tf.extractfile(entry) # `f` is in type of `class ExFileObject(io.BufferedReader)`f.seekable() # AttributeErrorf.seek(0) # AttributeError
Output:
Traceback (most recent call last):
File "a.py", line 17, in <module>
print(f.seekable())
File "AppData\Local\Programs\Python\Python310\lib\tarfile.py", line 641, in seekable
return self.fileobj.seekable()
AttributeError: '_Stream' object has no attribute 'seekable'
When opening tarfile with r| mode, ExFileObject.fileobj would be a _Stream object. So when calling ExFileObject.seekable(), as _Stream object dun have seekable() method, it will raise AttributeError.
Also, as BufferedReader.seek() will also check its raw.seekable() (i.e. in Modules/_io/bufferedio.c, func _io__Buffered_seek_impl(), there is a call to _PyIOBase_check_seekable(state, self->raw, Py_True)), f.seek() in the above example will also raise AttributeError.
I have thought of if we can simply define seekable() in _Stream object, but as it is only "forward-seekable", I am not sure if directly returning a true/false in _Stream.seekable() is appropriate or not. However, this issue does break the IOBase interface and there should be a solution.
Thanks for everyone's time and effort for looking into this.
Thank you for the report, @GLGDLY! Confirmed this on 3.12, 3.13, and the main branch. We can't do anything about <3.12, because those are security-only.
I'm pretty sure the fix is to simply define seekable as:
defseekable(self):
returnTrue
This is OK, because AFAIK, there's nothing in the IOBase interface about negative seeking. The requirement in the interface is that if seekable returns False, then seek always fails with an OSError. Otherwise, it's fine for seek to sometimes work.
Thank you for the report, @GLGDLY! Confirmed this on 3.12, 3.13, and the main branch. We can't do anything about <3.12, because those are security-only.
I'm pretty sure the fix is to simply define seekable as:
defseekable(self):
returnTrue
This is OK, because AFAIK, there's nothing in the IOBase interface about negative seeking. The requirement in the interface is that if seekable returns False, then seek always fails with an OSError. Otherwise, it's fine for seek to sometimes work.
Generally, it's good courtesy to let someone else write the PR if they have already said they wanted to. Though, thanks for finding the test failures!
Based on the traceback, it seems that this issue is expected behavior, somewhat:
Traceback (most recent call last):
File "/home/zero/Desktop/Projects/Python/cpython/Lib/test/test_tarfile.py", line 520, in test_extractfile_attrs
self.assertRaises(AttributeError, fobj.seekable)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised by seekable
Though, I'm not sure why AttributeError is something we're testing for here. If it really was intentional to not support seeking, then seekable should just return false, rather than raising.
Activity
ZeroIntensity commented on Sep 21, 2024
Thank you for the report, @GLGDLY! Confirmed this on 3.12, 3.13, and the main branch. We can't do anything about <3.12, because those are security-only.
I'm pretty sure the fix is to simply define seekable as:
This is OK, because AFAIK, there's nothing in the IOBase interface about negative seeking. The requirement in the interface is that if seekable returns False, then seek always fails with an OSError. Otherwise, it's fine for seek to sometimes work.
Would you like to author the PR?
GLGDLY commented on Sep 23, 2024
sure, i will handle that these few days
soufrabi commented on Sep 28, 2024
Can i work on this issue ?
soufrabi commented on Sep 28, 2024
Upon adding the seekable function it fails test_tarfile test
soufrabi commented on Sep 28, 2024
Change in Lib/tarfile.py
diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 1475b3da2d3..fd86dd2b869 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -507,6 +507,11 @@ def tell(self): """ return self.pos + def seekable(self): + """Returns True as the _Stream class supports random access + """ + return True + def seek(self, pos=0): """Set the stream's file pointer to pos. Negative seeking is forbidden.test_tarfile test fails
== Tests result: FAILURE == 23 tests skipped: test.test_asyncio.test_windows_events test.test_asyncio.test_windows_utils test_android test_bz2 test_dbm_gnu test_dbm_ndbm test_devpoll test_free_threading test_idle test_kqueue test_launcher test_msvcrt test_readline test_startfile test_tcl test_tkinter test_ttk test_ttk_textonly test_turtle test_winapi test_winconsoleio test_winreg test_wmi 9 tests skipped (resource denied): test_curses test_peg_generator test_pyrepl test_smtpnet test_socketserver test_urllib2net test_urllibnet test_winsound test_zipfile64 1 test failed: test_tarfile 446 tests OK. Total duration: 5 min 52 sec Total tests: run=44,076 failures=3 skipped=1,924 Total test files: run=470/479 failed=1 skipped=23 resource_denied=9 Result: FAILUREZeroIntensity commented on Sep 28, 2024
Generally, it's good courtesy to let someone else write the PR if they have already said they wanted to. Though, thanks for finding the test failures!
Based on the traceback, it seems that this issue is expected behavior, somewhat:
Traceback (most recent call last): File "/home/zero/Desktop/Projects/Python/cpython/Lib/test/test_tarfile.py", line 520, in test_extractfile_attrs self.assertRaises(AttributeError, fobj.seekable) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError: AttributeError not raised by seekableThough, I'm not sure why AttributeError is something we're testing for here. If it really was intentional to not support seeking, then seekable should just return false, rather than raising.
cc @serhiy-storchaka, who wrote that test.
soufrabi commented on Sep 28, 2024
@ZeroIntensity Thanks for the feedback.