Skip to content

'_Stream' object has no attribute 'seekable' #124293

Description

@GLGDLY

Bug report

Bug description:

From a recent application, I have found tarfile._Stream doesn't have a seekable() method. A quick reproduction of the problem is as follows:

import tarfile
import io

data = b"Test data."
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w") as tf:
    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|")
for entry in tf:
    f = tf.extractfile(entry)  # `f` is in type of `class ExFileObject(io.BufferedReader)`
    f.seekable()  # AttributeError
    f.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.

CPython versions tested on:

3.10, 3.11, 3.12, 3.13, CPython main branch

Operating systems tested on:

Linux, Windows

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Sep 20, 2024
  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    3.12only security fixes
    3.13only security fixes
    3.14bugs and security fixes
    on Sep 21, 2024
  3. ZeroIntensity commented on Sep 21, 2024

    @ZeroIntensity
    Member

    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:

    def seekable(self):
        return True

    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?

  4. GLGDLY commented on Sep 23, 2024

    @GLGDLY
    Author

    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:

    def seekable(self):
        return True

    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?

    sure, i will handle that these few days

  5. soufrabi commented on Sep 28, 2024

    @soufrabi

    Can i work on this issue ?

  6. soufrabi commented on Sep 28, 2024

    @soufrabi

    Upon adding the seekable function it fails test_tarfile test

  7. soufrabi commented on Sep 28, 2024

    @soufrabi

    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: FAILURE
    
  8. ZeroIntensity commented on Sep 28, 2024

    @ZeroIntensity
    Member

    Can i work on this issue ?

    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.

    cc @serhiy-storchaka, who wrote that test.

  9. soufrabi commented on Sep 28, 2024

    @soufrabi

    @ZeroIntensity Thanks for the feedback.

  10. removed
    3.12only security fixes
    3.13only security fixes
    3.14bugs and security fixes
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions