internal classes used by the gzip lzma and bz2 modules import io import sys buffersize io defaultbuffersize compressed data read chunk size class basestreamio bufferediobase adapts the decompressor api to a rawiobase reader api def readableself return true def initself fp decompfactory trailingerror decompargs self fp fp self eof false self pos 0 current offset in decompressed stream set to size of decompressed stream once it is known for seekend self size 1 save the decompressor factory and arguments if the file contains multiple compressed streams each stream will need a separate decompressor object a new decompressor object is also needed when implementing a backwards seek self decompfactory decompfactory self decompargs decompargs self decompressor self decompfactoryself decompargs exception class to catch from decompressor signifying invalid trailing data to ignore self trailingerror trailingerror def closeself self decompressor none return super close def seekableself return self fp seekable def readintoself b with memoryviewb as view view castb as byteview data self readlenbyteview byteview lendata data return lendata def readself size1 if size 0 return self readall if not size or self eof return b data none default if eof is encountered depending on the input data our call to the decompressor may not return any data in this case try again after reading another block while true if self decompressor eof rawblock self decompressor unuseddata or self fp readbuffersize if not rawblock break continue to next stream self decompressor self decompfactory self decompargs try data self decompressor decompressrawblock size except self trailingerror trailing data isn t a valid compressed stream ignore it break else if self decompressor needsinput rawblock self fp readbuffersize if not rawblock raise eoferrorcompressed file ended before the endofstream marker was reached else rawblock b data self decompressor decompressrawblock size if data break if not data self eof true self size self pos return b self pos lendata return data def readallself chunks sys maxsize means the max length of output buffer is unlimited so that the whole input buffer can be decompressed within one decompress call while data self readsys maxsize chunks appenddata return b joinchunks rewind the file to the beginning of the data stream def rewindself self fp seek0 self eof false self pos 0 self decompressor self decompfactoryself decompargs def seekself offset whenceio seekset recalculate offset as an absolute file position if whence io seekset pass elif whence io seekcur offset self pos offset elif whence io seekend seeking relative to eof we need to know the file s size if self size 0 while self readio defaultbuffersize pass offset self size offset else raise valueerrorinvalid value for whence formatwhence make it so that offset is the number of bytes to skip forward if offset self pos self rewind else offset self pos read and discard data until we reach the desired position while offset 0 data self readminio defaultbuffersize offset if not data break offset lendata return self pos def tellself compressed data read chunk size mode checking helper functions adapts the decompressor api to a rawiobase reader api current offset in decompressed stream set to size of decompressed stream once it is known for seek_end save the decompressor factory and arguments if the file contains multiple compressed streams each stream will need a separate decompressor object a new decompressor object is also needed when implementing a backwards seek exception class to catch from decompressor signifying invalid trailing data to ignore default if eof is encountered depending on the input data our call to the decompressor may not return any data in this case try again after reading another block continue to next stream trailing data isn t a valid compressed stream ignore it sys maxsize means the max length of output buffer is unlimited so that the whole input buffer can be decompressed within one decompress call rewind the file to the beginning of the data stream recalculate offset as an absolute file position seeking relative to eof we need to know the file s size make it so that offset is the number of bytes to skip forward read and discard data until we reach the desired position return the current file position
import io
import sys

BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE  


class BaseStream(io.BufferedIOBase):
    

    def _check_not_closed(self):
        if self.closed:
            raise ValueError("I/O operation on closed file")

    def _check_can_read(self):
        if not self.readable():
            raise io.UnsupportedOperation("File not open for reading")

    def _check_can_write(self):
        if not self.writable():
            raise io.UnsupportedOperation("File not open for writing")

    def _check_can_seek(self):
        if not self.readable():
            raise io.UnsupportedOperation("Seeking is only supported "
                                          "on files open for reading")
        if not self.seekable():
            raise io.UnsupportedOperation("The underlying file object "
                                          "does not support seeking")


class DecompressReader(io.RawIOBase):
    

    def readable(self):
        return True

    def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args):
        self._fp = fp
        self._eof = False
        self._pos = 0  

        
        self._size = -1

        
        
        
        
        self._decomp_factory = decomp_factory
        self._decomp_args = decomp_args
        self._decompressor = self._decomp_factory(**self._decomp_args)

        
        
        self._trailing_error = trailing_error

    def close(self):
        self._decompressor = None
        return super().close()

    def seekable(self):
        return self._fp.seekable()

    def readinto(self, b):
        with memoryview(b) as view, view.cast("B") as byte_view:
            data = self.read(len(byte_view))
            byte_view[:len(data)] = data
        return len(data)

    def read(self, size=-1):
        if size < 0:
            return self.readall()

        if not size or self._eof:
            return b""
        data = None  
        
        
        while True:
            if self._decompressor.eof:
                rawblock = (self._decompressor.unused_data or
                            self._fp.read(BUFFER_SIZE))
                if not rawblock:
                    break
                
                self._decompressor = self._decomp_factory(
                    **self._decomp_args)
                try:
                    data = self._decompressor.decompress(rawblock, size)
                except self._trailing_error:
                    
                    break
            else:
                if self._decompressor.needs_input:
                    rawblock = self._fp.read(BUFFER_SIZE)
                    if not rawblock:
                        raise EOFError("Compressed file ended before the "
                                       "end-of-stream marker was reached")
                else:
                    rawblock = b""
                data = self._decompressor.decompress(rawblock, size)
            if data:
                break
        if not data:
            self._eof = True
            self._size = self._pos
            return b""
        self._pos += len(data)
        return data

    def readall(self):
        chunks = []
        
        
        
        while data := self.read(sys.maxsize):
            chunks.append(data)

        return b"".join(chunks)

    
    def _rewind(self):
        self._fp.seek(0)
        self._eof = False
        self._pos = 0
        self._decompressor = self._decomp_factory(**self._decomp_args)

    def seek(self, offset, whence=io.SEEK_SET):
        
        if whence == io.SEEK_SET:
            pass
        elif whence == io.SEEK_CUR:
            offset = self._pos + offset
        elif whence == io.SEEK_END:
            
            if self._size < 0:
                while self.read(io.DEFAULT_BUFFER_SIZE):
                    pass
            offset = self._size + offset
        else:
            raise ValueError("Invalid value for whence: {}".format(whence))

        
        if offset < self._pos:
            self._rewind()
        else:
            offset -= self._pos

        
        while offset > 0:
            data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset))
            if not data:
                break
            offset -= len(data)

        return self._pos

    def tell(self):
        
        return self._pos