Skip to content

Commit

Permalink
Rename and wrap LazyZipOverHTTP
Browse files Browse the repository at this point in the history
  • Loading branch information
McSinyx committed Jun 24, 2020
1 parent 62d2c14 commit 4d938a4
Showing 1 changed file with 27 additions and 14 deletions.
41 changes: 27 additions & 14 deletions src/pip/_internal/network/lazy_wheel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Lazy ZIP over HTTP"""

__all__ = ['LazyZip']
__all__ = ['dist_from_wheel_url']

from bisect import bisect_left, bisect_right
from contextlib import contextmanager
Expand All @@ -12,24 +12,40 @@

from pip._internal.network.utils import HEADERS, response_chunks
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel

if MYPY_CHECK_RUNNING:
from typing import Any, Dict, Iterator, List, Optional, Tuple

from pip._vendor.pkg_resources import Distribution
from pip._vendor.requests.models import Response

from pip._internal.network.session import PipSession


class LazyZip:
def dist_from_wheel_url(name, url, session):
# type: (str, str, PipSession) -> Distribution
"""Return a pkg_resources.Distribution from the given wheel URL.
"""
with LazyZipOverHTTP(url, session) as wheel:
# For read-only ZIP files, ZipFile only needs methods read,
# seek, seekable and tell, not the whole IO protocol.
zip_file = ZipFile(wheel) # type: ignore
# After context manager exit, wheel.name
# is an invalid file by intention.
return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name)


class LazyZipOverHTTP:
"""File-like object mapped to a ZIP file over HTTP.
This uses HTTP range requests to lazily fetch the file's content,
which is supposed to be fed to ZipFile.
"""

def __init__(self, session, url, chunk_size=CONTENT_CHUNK_SIZE):
# type: (PipSession, str, int) -> None
def __init__(self, url, session, chunk_size=CONTENT_CHUNK_SIZE):
# type: (str, PipSession, int) -> None
head = session.head(url, headers=HEADERS)
head.raise_for_status()
assert head.status_code == 200
Expand All @@ -39,7 +55,9 @@ def __init__(self, session, url, chunk_size=CONTENT_CHUNK_SIZE):
self.truncate(self._length)
self._left = [] # type: List[int]
self._right = [] # type: List[int]
self._check_zip('bytes' in head.headers.get('Accept-Ranges', 'none'))
if 'bytes' not in head.headers.get('Accept-Ranges', 'none'):
raise RuntimeError('range request is not supported')
self._check_zip()

@property
def mode(self):
Expand All @@ -50,7 +68,7 @@ def mode(self):
@property
def name(self):
# type: () -> str
"""File name."""
"""Path to the underlying file."""
return self._file.name

def seekable(self):
Expand Down Expand Up @@ -120,7 +138,7 @@ def writable(self):
return False

def __enter__(self):
# type: () -> LazyZip
# type: () -> LazyZipOverHTTP
self._file.__enter__()
return self

Expand All @@ -141,21 +159,16 @@ def _stay(self):
finally:
self.seek(pos)

def _check_zip(self, range_request):
# type: (bool) -> None
def _check_zip(self):
# type: () -> None
"""Check and download until the file is a valid ZIP."""
end = self._length - 1
if not range_request:
self._download(0, end)
return
for start in reversed(range(0, end, self._chunk_size)):
self._download(start, end)
with self._stay():
try:
# For read-only ZIP files, ZipFile only needs
# methods read, seek, seekable and tell.
# The best way to type-hint in this case is to use
# Python 3.8+ typing.Protocol.
ZipFile(self) # type: ignore
except BadZipfile:
pass
Expand Down

0 comments on commit 4d938a4

Please sign in to comment.