|
import requests |
|
from typing import * |
|
|
|
__all__ = ["WebFile"] |
|
|
|
|
|
class WebFile: |
|
def __init__(self, url: str, session: Optional[requests.Session] = None, headers: Optional[Dict[str, str]] = None, size: Optional[int] = None): |
|
self.url = url |
|
self.session = session or requests.Session() |
|
self.session.headers.update(headers or {}) |
|
self._offset = 0 |
|
self.size = size if size is not None else self._fetch_size() |
|
|
|
def _fetch_size(self): |
|
with self.session.get(self.url, stream=True) as response: |
|
response.raise_for_status() |
|
content_length = response.headers.get("Content-Length") |
|
if content_length is None: |
|
raise ValueError("Missing Content-Length in header") |
|
return int(content_length) |
|
|
|
def _fetch_data(self, offset: int, n: int) -> bytes: |
|
headers = {"Range": f"bytes={offset}-{min(offset + n - 1, self.size)}"} |
|
response = self.session.get(self.url, headers=headers) |
|
response.raise_for_status() |
|
return response.content |
|
|
|
def seekable(self) -> bool: |
|
return True |
|
|
|
def tell(self) -> int: |
|
return self._offset |
|
|
|
def available(self) -> int: |
|
return self.size - self._offset |
|
|
|
def seek(self, offset: int, whence: int = 0) -> None: |
|
if whence == 0: |
|
new_offset = offset |
|
elif whence == 1: |
|
new_offset = self._offset + offset |
|
elif whence == 2: |
|
new_offset = self.size + offset |
|
else: |
|
raise ValueError("Invalid value for whence") |
|
|
|
self._offset = max(0, min(new_offset, self.size)) |
|
|
|
def read(self, n: Optional[int] = None) -> bytes: |
|
if n is None or n < 0: |
|
n = self.available() |
|
else: |
|
n = min(n, self.available()) |
|
|
|
if n == 0: |
|
return b'' |
|
|
|
data = self._fetch_data(self._offset, n) |
|
self._offset += len(data) |
|
|
|
return data |
|
|
|
def close(self) -> None: |
|
pass |
|
|
|
def __enter__(self): |
|
return self |
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
pass |
|
|
|
|