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 26, 2020
1 parent 3eb85a0 commit e1438d0
Showing 1 changed file with 32 additions and 15 deletions.
47 changes: 32 additions & 15 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,44 @@

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.
This uses HTTP range requests to only fetch the potion of the wheel
containing metadata, just enough for the object to be constructed.
If such requests are not supported, RuntimeError is raised.
"""
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(object):
"""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.
which is supposed to be fed to ZipFile. If such requests are not
supported by the server, raise RuntimeError during initialization.
"""

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 +59,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 +72,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 +142,7 @@ def writable(self):
return False

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

Expand All @@ -141,21 +163,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 e1438d0

Please sign in to comment.