Source code for httk.core.datastream.bytestream_file_view

import io
from collections.abc import Iterator
from typing import Any, NoReturn, Self

from ..views import unwrap
from .bytestream_api import BytestreamAPI
from .bytestream_backend import BytestreamBackend
from .bytestream_like import BytestreamLike
from .bytestream_view import BytestreamView


[docs] class BytestreamFileView(BytestreamView, io.IOBase, BytestreamAPI): r""" A view presenting an underlying data streaming backend via an io.IOBase-like API. :param obj: Byte-stream source to present through the file API. :param \**hints: Backend-selection and compression hints. """ _backend: BytestreamBackend _readline_buffer: bytes def __new__(cls, obj: BytestreamLike, **hints: Any) -> Self: if isinstance(obj, cls): return obj backend = cls._prepare_backend(obj, hints) instance = super().__new__(cls) instance._backend = backend instance._readline_buffer = b"" return instance def __init__(self, obj: BytestreamLike, **hints: Any) -> None: pass
[docs] def unwrap(self) -> Any: """Return the raw representation of the wrapped backend. :return: The backend's most raw available representation. """ return unwrap(self._backend)
@property
[docs] def name(self) -> str | None: """Return the backend's source name when one exists.""" return self._backend.name
@property
[docs] def closed(self) -> bool: """Report whether the backend is closed.""" return self._backend.closed
[docs] def close(self) -> None: """Close the underlying backend.""" self._backend.close()
[docs] def readable(self) -> bool: """Report that the view supports reading. :return: Always ``True``. """ return True
[docs] def writable(self) -> bool: """Report that the view does not support writing. :return: Always ``False``. """ return False
[docs] def seekable(self) -> bool: """Report whether the backend supports seeking and telling. :return: Whether both operations are available. """ return hasattr(self._backend, "seek") and hasattr(self._backend, "tell")
[docs] def flush(self) -> None: """Flush the backend when it provides flushing. :raises ValueError: If the view is closed. """ if self.closed: raise ValueError("I/O operation on closed file.") flush = getattr(self._backend, "flush", None) if flush: flush()
[docs] def read(self, size: int | None = -1) -> bytes: """Read up to ``size`` bytes, or all remaining bytes when ``size`` is negative. :param size: Maximum number of bytes to read; ``None`` also means all remaining bytes. :return: The bytes read from the stream. :raises ValueError: If the view is closed. """ if self.closed: raise ValueError("I/O operation on closed file.") if size is None or size < 0: if self._readline_buffer: prefix = self._readline_buffer self._readline_buffer = b"" return prefix + self._backend.read() return self._backend.read() if size == 0: return b"" if self._readline_buffer: if len(self._readline_buffer) >= size: out = self._readline_buffer[:size] self._readline_buffer = self._readline_buffer[size:] return out prefix = self._readline_buffer self._readline_buffer = b"" return prefix + self._backend.read(size - len(prefix)) return self._backend.read(size)
[docs] def readline(self, size: int | None = -1) -> bytes: """Read one line, optionally limited to ``size`` bytes. :param size: Maximum number of bytes to read; ``None`` means no limit. :return: The line read, including its newline when present. :raises ValueError: If the view is closed. """ if self.closed: raise ValueError("I/O operation on closed file.") if size == 0: return b"" if size is None: size = -1 parts: list[bytes] = [] total = 0 while True: if self._readline_buffer: chunk = self._readline_buffer self._readline_buffer = b"" else: to_read = 8192 if size >= 0: remaining = size - total if remaining <= 0: break to_read = min(to_read, remaining) chunk = self._backend.read(to_read) if chunk == b"": break newline_pos = chunk.find(b"\n") if newline_pos != -1: newline_pos += 1 take = chunk[:newline_pos] rest = chunk[newline_pos:] if size >= 0 and total + len(take) > size: cutoff = size - total parts.append(take[:cutoff]) self._readline_buffer = take[cutoff:] + rest break parts.append(take) self._readline_buffer = rest break if size >= 0 and total + len(chunk) > size: cutoff = size - total parts.append(chunk[:cutoff]) self._readline_buffer = chunk[cutoff:] break parts.append(chunk) total += len(chunk) return b"".join(parts)
[docs] def readlines(self, hint: int = -1) -> list[bytes]: """Read lines until EOF or until the accumulated size reaches ``hint``. :param hint: Approximate minimum number of bytes to collect, or a negative value for no limit. :return: The lines read from the stream. :raises ValueError: If the view is closed. """ if self.closed: raise ValueError("I/O operation on closed file.") lines: list[bytes] = [] total = 0 while True: line = self.readline() if line == b"": break lines.append(line) total += len(line) if hint >= 0 and total >= hint: break return lines
def __iter__(self) -> Iterator[bytes]: """Iterate over the stream one line at a time. :return: This stream as its line iterator. """ return self def __next__(self) -> bytes: """Return the next line from the stream. :return: The next line. :raises StopIteration: When the stream is exhausted. """ line = self.readline() if line == b"": raise StopIteration return line
[docs] def seek(self, offset: int, whence: int = io.SEEK_SET) -> int: """Move the stream position and discard buffered line data. :param offset: Position adjustment interpreted according to ``whence``. :param whence: Reference point for ``offset``. :return: The resulting absolute stream position. :raises ValueError: If the view is closed. :raises io.UnsupportedOperation: If the backend is not seekable. """ if self.closed: raise ValueError("I/O operation on closed file.") seek = getattr(self._backend, "seek", None) if not seek: raise io.UnsupportedOperation("underlying stream is not seekable") pos = seek(offset, whence) self._readline_buffer = b"" return pos
[docs] def tell(self) -> int: """Return the logical stream position before buffered line data. :return: The logical absolute stream position. :raises ValueError: If the view is closed. :raises io.UnsupportedOperation: If the backend does not support telling. """ if self.closed: raise ValueError("I/O operation on closed file.") tell = getattr(self._backend, "tell", None) if not tell: raise io.UnsupportedOperation("underlying stream does not support tell()") pos = tell() return pos - len(self._readline_buffer)
[docs] def detach(self) -> NoReturn: """Reject detaching because the view owns its backend interface. :raises io.UnsupportedOperation: Always, because detaching is unsupported. """ raise io.UnsupportedOperation("detach")