Skip to content

Commit

Permalink
Show a progress of a hashes generation process in a verbose mode
Browse files Browse the repository at this point in the history
  • Loading branch information
atugushev committed Apr 6, 2019
1 parent 9d0a91a commit ede863b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 5 deletions.
18 changes: 18 additions & 0 deletions piptools/_compat/contextlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Ported from python 3.7 contextlib.py
class nullcontext(object):
"""Context manager that does no additional processing.
Used as a stand-in for a normal context manager, when a particular
block of code is only sometimes used with a normal context manager:
cm = optional_cm if condition else nullcontext()
with cm:
# Perform operation, using optional_cm if condition is True
"""

def __init__(self, enter_result=None):
self.enter_result = enter_result

def __enter__(self):
return self.enter_result

def __exit__(self, *excinfo):
pass
39 changes: 34 additions & 5 deletions piptools/repositories/pypi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# coding: utf-8
from __future__ import absolute_import, division, print_function, unicode_literals

import collections
import hashlib
import os
from contextlib import contextmanager
Expand All @@ -16,11 +17,14 @@
RequirementSet,
TemporaryDirectory,
Wheel,
contextlib,
is_file_url,
url_to_path,
)
from ..cache import CACHE_DIR
from ..click import progressbar
from ..exceptions import NoCandidateFound
from ..logging import log
from ..utils import (
fs_str,
is_pinned_requirement,
Expand All @@ -43,6 +47,9 @@ def RequirementTracker():
except ImportError:
from pip.wheel import WheelCache

FILE_CHUNK_SIZE = 4096
FileStream = collections.namedtuple("File", "stream size")


class PyPIRepository(BaseRepository):
DEFAULT_INDEX_URL = PyPI.simple_url
Expand Down Expand Up @@ -278,15 +285,30 @@ def get_hashes(self, ireq):
)
matching_candidates = candidates_by_version[matching_versions[0]]

log.debug(" {}".format(ireq.name))

return {
self._get_file_hash(candidate.location) for candidate in matching_candidates
}

def _get_file_hash(self, location):
log.debug(" Hashing {}".format(location.url_without_fragment))
h = hashlib.new(FAVORITE_HASH)
with open_local_or_remote_file(location, self.session) as fp:
for chunk in iter(lambda: fp.read(8096), b""):
h.update(chunk)
with open_local_or_remote_file(location, self.session) as f:
# Chunks to iterate
chunks = iter(lambda: f.stream.read(FILE_CHUNK_SIZE), b"")

# Choose a context manager depending on verbosity
if log.verbosity >= 1:
iter_length = f.size / FILE_CHUNK_SIZE if f.size else None
context_manager = progressbar(chunks, length=iter_length, label=" ")
else:
context_manager = contextlib.nullcontext(chunks)

# Iterate over the chosen context manager
with context_manager as bar:
for chunk in bar:
h.update(chunk)
return ":".join([FAVORITE_HASH, h.hexdigest()])

@contextmanager
Expand Down Expand Up @@ -340,13 +362,20 @@ def open_local_or_remote_file(link, session):
if os.path.isdir(local_path):
raise ValueError("Cannot open directory for read: {}".format(url))
else:
st = os.stat(local_path)
with open(local_path, "rb") as local_file:
yield local_file
yield FileStream(stream=local_file, size=st.st_size)
else:
# Remote URL
headers = {"Accept-Encoding": "identity"}
response = session.get(url, headers=headers, stream=True)

# Content length must be int or None
content_length = response.headers.get("content-length")
if content_length is not None:
content_length = int(content_length)

try:
yield response.raw
yield FileStream(stream=response.raw, size=content_length)
finally:
response.close()
2 changes: 2 additions & 0 deletions piptools/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def resolve_hashes(self, ireqs):
"""
Finds acceptable hashes for all of the given InstallRequirements.
"""
log.debug("")
log.debug("Generating hashes:")
with self.repository.allow_all_wheels():
return {ireq: self.repository.get_hashes(ireq) for ireq in ireqs}

Expand Down
13 changes: 13 additions & 0 deletions tests/test_cli_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,19 @@ def test_generate_hashes_with_editable(runner):
assert expected in out.output


def test_generate_hashes_verbose(runner):
"""
The hashes generation process should show a progress.
"""
with open("requirements.in", "w") as fp:
fp.write("pytz==2017.2")

out = runner.invoke(cli, ["--generate-hashes", "-v"])

expected_verbose_text = "Generating hashes:\n pytz\n"
assert expected_verbose_text in out.output


@fail_below_pip9
def test_filter_pip_markers(runner):
"""
Expand Down

0 comments on commit ede863b

Please sign in to comment.