"""Internal classes used by the gzip, lzma and bz2 modules"""
BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size
class BaseStream(io.BufferedIOBase):
"""Mode-checking helper functions."""
def _check_not_closed(self):
raise ValueError("I/O operation on closed file")
def _check_can_read(self):
raise io.UnsupportedOperation("File not open for reading")
def _check_can_write(self):
raise io.UnsupportedOperation("File not open for writing")
def _check_can_seek(self):
raise io.UnsupportedOperation("Seeking is only supported "
"on files open for reading")
raise io.UnsupportedOperation("The underlying file object "
"does not support seeking")
class DecompressReader(io.RawIOBase):
"""Adapts the decompressor API to a RawIOBase reader API"""
def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args):
self._pos = 0 # 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().
self._decomp_factory = decomp_factory
self._decomp_args = decomp_args
self._decompressor = self._decomp_factory(**self._decomp_args)
# Exception class to catch from decompressor signifying invalid
# trailing data to ignore
self._trailing_error = trailing_error
self._decompressor = None
return self._fp.seekable()
with memoryview(b) as view, view.cast("B") as byte_view:
data = self.read(len(byte_view))
byte_view[:len(data)] = data
if not size or self._eof:
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.
if self._decompressor.eof:
rawblock = (self._decompressor.unused_data or
self._fp.read(BUFFER_SIZE))
# Continue to next stream.
self._decompressor = self._decomp_factory(
data = self._decompressor.decompress(rawblock, size)
except self._trailing_error:
# Trailing data isn't a valid compressed stream; ignore it.
if self._decompressor.needs_input:
rawblock = self._fp.read(BUFFER_SIZE)
raise EOFError("Compressed file ended before the "
"end-of-stream marker was reached")
data = self._decompressor.decompress(rawblock, size)
# Rewind the file to the beginning of the data stream.
self._decompressor = self._decomp_factory(**self._decomp_args)
def seek(self, offset, whence=io.SEEK_SET):
# Recalculate offset as an absolute file position.
if whence == io.SEEK_SET:
elif whence == io.SEEK_CUR:
offset = self._pos + offset
elif whence == io.SEEK_END:
# Seeking relative to EOF - we need to know the file's size.
while self.read(io.DEFAULT_BUFFER_SIZE):
offset = self._size + offset
raise ValueError("Invalid value for whence: {}".format(whence))
# Make it so that offset is the number of bytes to skip forward.
# Read and discard data until we reach the desired position.
data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset))
"""Return the current file position."""