Source code for zipwire.backends._urllib3
"""Synchronous urllib3-based reader."""
from __future__ import annotations
import logging
import typing
import urllib3
from zipwire._constants import STREAM_CHUNK_SIZE, range_header
from zipwire._errors import RangeRequestUnsupported
if typing.TYPE_CHECKING:
from collections.abc import Iterator
from zipwire._types import Headers
logger = logging.getLogger(__name__)
[docs]
class Urllib3Reader:
"""SyncReader implementation using urllib3.PoolManager."""
def __init__(
self,
url: str,
*,
pool: urllib3.PoolManager | None = None,
allow_redirects: bool = True,
) -> None:
self._url = url
self._owns_pool = pool is None
self._pool = pool or urllib3.PoolManager()
self._allow_redirects = allow_redirects
def head(self) -> Headers:
logger.debug("HEAD %s", self._url)
resp = self._pool.request("HEAD", self._url, redirect=self._allow_redirects)
if resp.status >= 400:
raise OSError(f"HEAD request failed with status {resp.status}")
if resp.headers.get("accept-ranges", "").lower() != "bytes":
raise RangeRequestUnsupported(
f"Server does not support range requests for {self._url}"
)
return resp.headers
def read_range(
self,
offset: int,
length: int,
) -> tuple[bytes, Headers]:
logger.debug("GET %s %s (%d bytes)", self._url, range_header(offset, length), length)
resp = self._pool.request(
"GET",
self._url,
headers={"Range": range_header(offset, length)},
redirect=self._allow_redirects,
)
if resp.status >= 400:
raise OSError(f"Range request failed with status {resp.status}")
if resp.status != 206:
raise RangeRequestUnsupported(
f"Server does not support range requests for {self._url}"
)
return bytes(resp.data), resp.headers
def stream_range(self, offset: int, length: int) -> Iterator[bytes]:
logger.debug(
"GET stream %s %s (%d bytes)", self._url, range_header(offset, length), length
)
resp = self._pool.request(
"GET",
self._url,
headers={"Range": range_header(offset, length)},
preload_content=False,
redirect=self._allow_redirects,
)
if resp.status >= 400:
resp.release_conn()
raise OSError(f"Range request failed with status {resp.status}")
if resp.status != 206:
resp.release_conn()
raise RangeRequestUnsupported(
f"Server does not support range requests for {self._url}"
)
try:
yield from resp.stream(STREAM_CHUNK_SIZE)
finally:
resp.release_conn()
def close(self) -> None:
if self._owns_pool:
self._pool.clear()