A file-like object that reads only the first part of a file.
| 311 | |
| 312 | |
| 313 | class PartialFileIO(BytesIO): |
| 314 | """A file-like object that reads only the first part of a file.""" |
| 315 | |
| 316 | def __init__(self, file_path: Union[str, Path], size_limit: int) -> None: |
| 317 | self._file_path = Path(file_path) |
| 318 | self._file = None |
| 319 | self._size_limit = size_limit |
| 320 | self.open() |
| 321 | |
| 322 | def open(self) -> None: |
| 323 | """Open the file and initialize size limit.""" |
| 324 | if self._file is not None: |
| 325 | return |
| 326 | try: |
| 327 | self._file = self._file_path.open('rb') |
| 328 | self._size_limit = min( |
| 329 | self._size_limit or float('inf'), |
| 330 | os.fstat(self._file.fileno()).st_size) |
| 331 | except OSError as e: |
| 332 | logger.error(f'Failed to open file {self._file_path}: {e}') |
| 333 | raise |
| 334 | |
| 335 | def close(self) -> None: |
| 336 | """Close the file if it's open.""" |
| 337 | if self._file is not None: |
| 338 | self._file.close() |
| 339 | self._file = None |
| 340 | |
| 341 | def __del__(self) -> None: |
| 342 | self.close() |
| 343 | return super().__del__() |
| 344 | |
| 345 | def __repr__(self) -> str: |
| 346 | return f'<PartialFileIO file_path={self._file_path} size_limit={self._size_limit}>' |
| 347 | |
| 348 | def __len__(self) -> int: |
| 349 | return self._size_limit |
| 350 | |
| 351 | def __getattribute__(self, name: str): |
| 352 | if name.startswith('_') or name in { |
| 353 | 'read', 'tell', 'seek', 'close', 'open' |
| 354 | }: # only 5 public methods supported |
| 355 | return super().__getattribute__(name) |
| 356 | raise NotImplementedError(f"PartialFileIO does not support '{name}'.") |
| 357 | |
| 358 | def tell(self) -> int: |
| 359 | return self._file.tell() |
| 360 | |
| 361 | def seek(self, __offset: int, __whence: int = SEEK_SET) -> int: |
| 362 | """Seek to a position in the file, but never beyond size_limit.""" |
| 363 | if __whence == SEEK_END: |
| 364 | __offset = len(self) + __offset |
| 365 | __whence = SEEK_SET |
| 366 | |
| 367 | pos = self._file.seek(__offset, __whence) |
| 368 | if pos > self._size_limit: |
| 369 | return self._file.seek(self._size_limit) |
| 370 | return pos |
no outgoing calls
searching dependent graphs…