From 190aa0535e13be89c06d859a0ea2a326b3d46e6c Mon Sep 17 00:00:00 2001 From: Hector Date: Fri, 27 Feb 2026 09:36:35 +0000 Subject: [PATCH] Add seek() method to ZstdCompressionReader Even though ZstdCompressionReader doesn't support seek()ing we should probably follow the interface of: https://docs.python.org/3/library/io.html#io.IOBase.seek and define a seek method which raises OSError. --- c-ext/compressionreader.c | 7 +++++++ rust-ext/src/compression_reader.rs | 4 ++++ tests/test_compressor_stream_reader.py | 14 ++++++++++++++ zstandard/__init__.pyi | 1 + zstandard/backend_cffi.py | 3 +++ 5 files changed, 29 insertions(+) diff --git a/c-ext/compressionreader.c b/c-ext/compressionreader.c index b14a63b1..67c4fba9 100644 --- a/c-ext/compressionreader.c +++ b/c-ext/compressionreader.c @@ -83,6 +83,11 @@ static PyObject *compressionreader_seekable(ZstdCompressionReader *self) { Py_RETURN_FALSE; } +static PyObject *compressionreader_seek(PyObject *self, PyObject *args) { + PyErr_SetString(PyExc_OSError, "stream is not seekable"); + return NULL; +} + static PyObject *compressionreader_readline(PyObject *self, PyObject *args) { set_io_unsupported_operation(); return NULL; @@ -760,6 +765,8 @@ static PyMethodDef compressionreader_methods[] = { PyDoc_STR("Not implemented")}, {"readlines", (PyCFunction)compressionreader_readlines, METH_VARARGS, PyDoc_STR("Not implemented")}, + {"seek", (PyCFunction)compressionreader_seek, METH_VARARGS, + PyDoc_STR("Raises OSError")}, {"seekable", (PyCFunction)compressionreader_seekable, METH_NOARGS, PyDoc_STR("Returns False")}, {"tell", (PyCFunction)compressionreader_tell, METH_NOARGS, diff --git a/rust-ext/src/compression_reader.rs b/rust-ext/src/compression_reader.rs index 47626e34..0f6d85c9 100644 --- a/rust-ext/src/compression_reader.rs +++ b/rust-ext/src/compression_reader.rs @@ -169,6 +169,10 @@ impl ZstdCompressionReader { false } + fn seek(&self, _data: &Bound<'_, PyAny>) -> PyResult<()> { + Err(PyOSError::new_err("stream is not seekable")) + } + fn readline(&self, py: Python) -> PyResult<()> { let io = py.import("io")?; let exc = io.getattr("UnsupportedOperation")?; diff --git a/tests/test_compressor_stream_reader.py b/tests/test_compressor_stream_reader.py index 6c1fca21..81cb55bb 100644 --- a/tests/test_compressor_stream_reader.py +++ b/tests/test_compressor_stream_reader.py @@ -54,6 +54,20 @@ def test_not_implemented(self): with self.assertRaises(OSError): reader.write(b"foo") + def test_seek(self): + cctx = zstd.ZstdCompressor() + + with cctx.stream_reader(b"foo" * 60) as reader: + with self.assertRaises(OSError): + reader.seek(0) + + reader.read(1) + with self.assertRaises(OSError): + reader.seek(0) + + with self.assertRaises(OSError): + reader.seek(0) + def test_constant_methods(self): cctx = zstd.ZstdCompressor() diff --git a/zstandard/__init__.pyi b/zstandard/__init__.pyi index f341405f..3f7eb22f 100644 --- a/zstandard/__init__.pyi +++ b/zstandard/__init__.pyi @@ -216,6 +216,7 @@ class ZstdCompressionReader(BinaryIO): def readable(self) -> bool: ... def writable(self) -> bool: ... def seekable(self) -> bool: ... + def seek(self, pos: int, whence: int = ...) -> int: ... def readline(self, limit: int = ...) -> bytes: ... def readlines(self, hint: int = ...) -> List[bytes]: ... def write(self, data: ByteString): ... diff --git a/zstandard/backend_cffi.py b/zstandard/backend_cffi.py index 9d830cf1..ee21f2cb 100644 --- a/zstandard/backend_cffi.py +++ b/zstandard/backend_cffi.py @@ -1427,6 +1427,9 @@ def writable(self): def seekable(self): return False + def seek(self, offset, whence=None): + raise io.UnsupportedOperation() + def readline(self): raise io.UnsupportedOperation()