diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d65c0609..206d641d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,13 @@ Changelog .. This document is user facing. Please word the changes in such a way .. that users understand how the changes affect the new version. +version 1.10.0-dev +------------------ ++ Fix ``tell()`` and forward ``seek()`` on ``igzip.IGzipFile`` objects that + read from an unseekable file object, such as a pipe or a socket. Versions + 1.4.0 to 1.9.1 raised ``io.UnsupportedOperation`` for these calls. + + version 1.9.1 ----------------- + Fix BrokenPipeError when piping ``python -m isal.igzip`` into ``head``, diff --git a/src/isal/isal_zlibmodule.c b/src/isal/isal_zlibmodule.c index 3ee12d83..4ba1197c 100644 --- a/src/isal/isal_zlibmodule.c +++ b/src/isal/isal_zlibmodule.c @@ -2269,7 +2269,9 @@ GzipReader_writable(GzipReader *self, PyObject *Py_UNUSED(ignore)) static PyObject * GzipReader_seekable(GzipReader *self, PyObject *Py_UNUSED(ignore)) { - return PyObject_CallMethod(self->fp, "seekable", NULL); + /* Same as gzip._PaddedFile.seekable(): seek() moves forward by reading, + so tell() and forward seeks also work on unseekable streams. */ + Py_RETURN_TRUE; } static PyObject * diff --git a/tests/test_igzip.py b/tests/test_igzip.py index 457f13f6..dc8251d9 100644 --- a/tests/test_igzip.py +++ b/tests/test_igzip.py @@ -688,6 +688,32 @@ def test_seek(tmp_path): assert gzip_file.read(1) == b"C" +def test_tell_and_seek_forward_on_unseekable_file(): + class UnseekableFile(io.RawIOBase): + # Like a pipe or a socket: seekable() is False and seek() fails. + def __init__(self, data): + self.data = io.BytesIO(data) + + def readable(self): + return True + + def readinto(self, buffer): + return self.data.readinto(buffer) + + data = bytes(range(256)) * 1000 + fileobj = UnseekableFile(gzip.compress(data)) + with igzip.IGzipFile(fileobj=fileobj) as gzip_file: + assert gzip_file.read(10) == data[:10] + assert gzip_file.tell() == 10 + assert gzip_file.seek(200_000) == 200_000 + assert gzip_file.read(10) == data[200_000:200_010] + assert gzip_file.seek(5, io.SEEK_CUR) == 200_015 + assert gzip_file.read(10) == data[200_015:200_025] + # Rewinding needs fileobj.seek(), as in gzip. + with pytest.raises(io.UnsupportedOperation): + gzip_file.seek(0) + + def test_bgzip(): bgzip_file = Path(__file__).parent / "data" / "test.fastq.bgzip.gz" gzip_file = Path(__file__).parent / "data" / "test.fastq.gz"