Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accelerate locking by fetching hash from url fragment #4500

Merged
merged 12 commits into from
Oct 28, 2020
1 change: 1 addition & 0 deletions news/3827.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Retrieve package file hash from URL to accelerate the locking process.
3 changes: 3 additions & 0 deletions pipenv/patched/piptools/repositories/pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def get_hash(self, location):
if can_hash:
# hash url WITH fragment
hash_value = self.get(new_location.url)
if not hash_value:
hash_value = "{}:{}".format(new_location.hash_name, new_location.hash)
hash_value = hash_value.encode('utf8')
if not hash_value:
hash_value = self._get_file_hash(new_location) if not new_location.url.startswith("ssh") else None
hash_value = hash_value.encode('utf8') if hash_value else None
Expand Down
4 changes: 2 additions & 2 deletions tasks/vendoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
HARDCODED_LICENSE_URLS = {
"pytoml": "https://github.com/avakar/pytoml/raw/master/LICENSE",
"cursor": "https://raw.githubusercontent.com/GijsTimmers/cursor/master/LICENSE",
"delegator.py": "https://raw.githubusercontent.com/kennethreitz/delegator.py/master/LICENSE",
"delegator.py": "https://raw.githubusercontent.com/amitt001/delegator.py/master/LICENSE",
"click-didyoumean": "https://raw.githubusercontent.com/click-contrib/click-didyoumean/master/LICENSE",
"click-completion": "https://raw.githubusercontent.com/click-contrib/click-completion/master/LICENSE",
"parse": "https://raw.githubusercontent.com/techalchemy/parse/master/LICENSE",
"semver": "https://raw.githubusercontent.com/k-bx/python-semver/master/LICENSE.txt",
"crayons": "https://raw.githubusercontent.com/kennethreitz/crayons/master/LICENSE",
"crayons": "https://raw.githubusercontent.com/MasterOdin/crayons/master/LICENSE",
"pip-tools": "https://raw.githubusercontent.com/jazzband/pip-tools/master/LICENSE",
"pytoml": "https://github.com/avakar/pytoml/raw/master/LICENSE",
"webencodings": "https://github.com/SimonSapin/python-webencodings/raw/"
Expand Down
52 changes: 34 additions & 18 deletions tasks/vendoring/patches/patched/piptools.patch
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ index fda80d5..4f7efbf 100644
if six.PY2:
from .tempfile import TemporaryDirectory
diff --git a/pipenv/patched/piptools/_compat/pip_compat.py b/pipenv/patched/piptools/_compat/pip_compat.py
index 9508b75..103b831 100644
index 9508b75..ea51421 100644
--- a/pipenv/patched/piptools/_compat/pip_compat.py
+++ b/pipenv/patched/piptools/_compat/pip_compat.py
@@ -1,22 +1,72 @@
Expand Down Expand Up @@ -93,7 +93,8 @@ index 9508b75..103b831 100644

-
else:
from pip._internal.req.constructors import install_req_from_parsed_requirement
- from pip._internal.req.constructors import install_req_from_parsed_requirement
+ from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement

+InstallRequirement = pip_shims.shims.InstallRequirement
+InstallationError = pip_shims.shims.InstallationError
Expand Down Expand Up @@ -138,7 +139,7 @@ index 9b6bf55..983ddb6 100644
from .exceptions import PipToolsError
from .utils import as_tuple, key_from_req, lookup_table
diff --git a/pipenv/patched/piptools/locations.py b/pipenv/patched/piptools/locations.py
index 9ca0ffe..37125c9 100644
index 9ca0ffe..36cc538 100644
--- a/pipenv/patched/piptools/locations.py
+++ b/pipenv/patched/piptools/locations.py
@@ -1,12 +1,15 @@
Expand All @@ -150,8 +151,9 @@ index 9ca0ffe..37125c9 100644

from .click import secho

# The user_cache_dir helper comes straight from pip itself
-# The user_cache_dir helper comes straight from pip itself
-CACHE_DIR = user_cache_dir("pip-tools")
+# The user_cache_dir helper comes straight from pipenv.patched.notpip itself
+try:
+ from pipenv.environments import PIPENV_CACHE_DIR as CACHE_DIR
+except ImportError:
Expand Down Expand Up @@ -185,7 +187,7 @@ index ec3a796..1aa29f0 100644
else:
return self.repository.find_best_match(ireq, prereleases)
diff --git a/pipenv/patched/piptools/repositories/pypi.py b/pipenv/patched/piptools/repositories/pypi.py
index ef5ba4e..b96acf6 100644
index ef5ba4e..8f74271 100644
--- a/pipenv/patched/piptools/repositories/pypi.py
+++ b/pipenv/patched/piptools/repositories/pypi.py
@@ -2,28 +2,48 @@
Expand Down Expand Up @@ -250,7 +252,7 @@ index ef5ba4e..b96acf6 100644
fs_str,
is_pinned_requirement,
is_url_requirement,
@@ -32,10 +52,50 @@ from ..utils import (
@@ -32,10 +52,53 @@ from ..utils import (
)
from .base import BaseRepository

Expand Down Expand Up @@ -283,6 +285,9 @@ index ef5ba4e..b96acf6 100644
+ if can_hash:
+ # hash url WITH fragment
+ hash_value = self.get(new_location.url)
+ if not hash_value:
+ hash_value = "{}:{}".format(new_location.hash_name, new_location.hash)
+ hash_value = hash_value.encode('utf8')
+ if not hash_value:
+ hash_value = self._get_file_hash(new_location) if not new_location.url.startswith("ssh") else None
+ hash_value = hash_value.encode('utf8') if hash_value else None
Expand All @@ -301,7 +306,7 @@ index ef5ba4e..b96acf6 100644
class PyPIRepository(BaseRepository):
DEFAULT_INDEX_URL = PyPI.simple_url

@@ -46,21 +106,29 @@ class PyPIRepository(BaseRepository):
@@ -46,21 +109,29 @@ class PyPIRepository(BaseRepository):
changed/configured on the Finder.
"""

Expand Down Expand Up @@ -335,7 +340,7 @@ index ef5ba4e..b96acf6 100644
)

# Caches
@@ -73,6 +141,10 @@ class PyPIRepository(BaseRepository):
@@ -73,6 +144,10 @@ class PyPIRepository(BaseRepository):
# of all secondary dependencies for the given requirement, so we
# only have to go to disk once for each requirement
self._dependencies_cache = {}
Expand All @@ -346,7 +351,7 @@ index ef5ba4e..b96acf6 100644

# Setup file paths
self.freshen_build_caches()
@@ -114,13 +186,15 @@ class PyPIRepository(BaseRepository):
@@ -114,13 +189,15 @@ class PyPIRepository(BaseRepository):
if ireq.editable or is_url_requirement(ireq):
return ireq # return itself as the best match

Expand All @@ -366,7 +371,7 @@ index ef5ba4e..b96acf6 100644

# Reuses pip's internal candidate sort key to sort
matching_candidates = [candidates_by_version[ver] for ver in matching_versions]
@@ -136,9 +210,66 @@ class PyPIRepository(BaseRepository):
@@ -136,9 +213,66 @@ class PyPIRepository(BaseRepository):
best_candidate.name,
best_candidate.version,
ireq.extras,
Expand Down Expand Up @@ -433,7 +438,16 @@ index ef5ba4e..b96acf6 100644
def resolve_reqs(self, download_dir, ireq, wheel_cache):
with get_requirement_tracker() as req_tracker, TempDirectory(
kind="resolver"
@@ -173,10 +304,11 @@ class PyPIRepository(BaseRepository):
@@ -165,7 +299,7 @@ class PyPIRepository(BaseRepository):
wheel_cache=wheel_cache,
use_user_site=False,
ignore_installed=True,
- ignore_requires_python=False,
+ ignore_requires_python=True,
force_reinstall=False,
upgrade_strategy="to-satisfy-only",
)
@@ -173,10 +307,11 @@ class PyPIRepository(BaseRepository):

if PIP_VERSION[:2] <= (20, 0):
reqset.cleanup_files()
Expand All @@ -447,7 +461,7 @@ index ef5ba4e..b96acf6 100644
"""
Given a pinned, URL, or editable InstallRequirement, returns a set of
dependencies (also InstallRequirements, but not necessarily pinned).
@@ -212,9 +344,10 @@ class PyPIRepository(BaseRepository):
@@ -212,9 +347,10 @@ class PyPIRepository(BaseRepository):
wheel_cache = WheelCache(self._cache_dir, self.options.format_control)
prev_tracker = os.environ.get("PIP_REQ_TRACKER")
try:
Expand All @@ -459,7 +473,7 @@ index ef5ba4e..b96acf6 100644
finally:
if "PIP_REQ_TRACKER" in os.environ:
if prev_tracker:
@@ -252,7 +385,7 @@ class PyPIRepository(BaseRepository):
@@ -252,7 +388,7 @@ class PyPIRepository(BaseRepository):
cached_link = Link(path_to_url(cached_path))
else:
cached_link = link
Expand All @@ -468,7 +482,7 @@ index ef5ba4e..b96acf6 100644

if not is_pinned_requirement(ireq):
raise TypeError("Expected pinned requirement, got {}".format(ireq))
@@ -260,38 +393,28 @@ class PyPIRepository(BaseRepository):
@@ -260,38 +396,28 @@ class PyPIRepository(BaseRepository):
# We need to get all of the candidates that match our current version
# pin, these will represent all of the files that could possibly
# satisfy this constraint.
Expand Down Expand Up @@ -609,7 +623,7 @@ index 0116992..550069d 100644
]
return self.dependency_cache.reverse_dependencies(non_editable)
diff --git a/pipenv/patched/piptools/scripts/compile.py b/pipenv/patched/piptools/scripts/compile.py
index 03232a8..a7bfb4c 100755
index 03232a8..f83b13e 100755
--- a/pipenv/patched/piptools/scripts/compile.py
+++ b/pipenv/patched/piptools/scripts/compile.py
@@ -7,8 +7,8 @@ import sys
Expand All @@ -623,11 +637,13 @@ index 03232a8..a7bfb4c 100755

from .. import click
from .._compat import parse_requirements
@@ -25,7 +25,7 @@ DEFAULT_REQUIREMENTS_FILE = "requirements.in"
@@ -24,8 +24,8 @@ from ..writer import OutputWriter
DEFAULT_REQUIREMENTS_FILE = "requirements.in"
DEFAULT_REQUIREMENTS_OUTPUT_FILE = "requirements.txt"

# Get default values of the pip's options (including options from pip.conf).
-# Get default values of the pip's options (including options from pip.conf).
-install_command = create_command("install")
+# Get default values of the pip's options (including options from pipenv.patched.notpip.conf).
+install_command = InstallComand()
pip_defaults = install_command.parser.get_default_values()

Expand Down Expand Up @@ -671,7 +687,7 @@ index 430b4bb..015ff7a 100644
from . import click
from .exceptions import IncompatibleRequirements
diff --git a/pipenv/patched/piptools/utils.py b/pipenv/patched/piptools/utils.py
index 7733447..e6f232f 100644
index 7733447..1123fb6 100644
--- a/pipenv/patched/piptools/utils.py
+++ b/pipenv/patched/piptools/utils.py
@@ -1,14 +1,19 @@
Expand Down
2 changes: 1 addition & 1 deletion tests/pypi
Submodule pypi updated 276 files
16 changes: 10 additions & 6 deletions tests/pytest-pypi/pytest_pypi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from flask import Flask, redirect, abort, render_template, send_file, jsonify


ReleaseTuple = collections.namedtuple("ReleaseTuple", ["path", "requires_python"])
ReleaseTuple = collections.namedtuple("ReleaseTuple", ["path", "requires_python", "hash"])

app = Flask(__name__)
session = requests.Session()
Expand Down Expand Up @@ -86,14 +86,18 @@ def add_release(self, path_to_binary):
path_to_binary = os.path.abspath(path_to_binary)
path, release = os.path.split(path_to_binary)
requires_python = ""
hash_value = ""
if path_to_binary.endswith(".whl"):
pkg = distlib.wheel.Wheel(path_to_binary)
md_dict = pkg.metadata.todict()
requires_python = md_dict.get("requires_python", "")
if requires_python.count(".") > 1:
requires_python, _, _ = requires_python.rpartition(".")
self.releases[release] = ReleaseTuple(path_to_binary, requires_python)
self._package_dirs.add(ReleaseTuple(path, requires_python))
if os.path.isfile(path_to_binary + ".sha256"):
with open(path_to_binary + ".sha256") as f:
hash_value = f.read().strip()
self.releases[release] = ReleaseTuple(path_to_binary, requires_python, hash_value)
self._package_dirs.add(ReleaseTuple(path, requires_python, hash_value))


class Artifact(object):
Expand Down Expand Up @@ -155,9 +159,9 @@ def prepare_packages(path):
packages[package_name] = Package(package_name)

packages[package_name].add_release(os.path.join(root, file))
remaining = get_pypi_package_names() - set(list(packages.keys()))
for pypi_pkg in remaining:
packages[pypi_pkg] = Package(pypi_pkg)
# remaining = get_pypi_package_names() - set(list(packages.keys()))
# for pypi_pkg in remaining:
# packages[pypi_pkg] = Package(pypi_pkg)


@app.route('/')
Expand Down
2 changes: 1 addition & 1 deletion tests/pytest-pypi/pytest_pypi/templates/package.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<body>
<h1>Links for {{ package.name }}</h1>
{% for release, value in package.releases.items() %}
<a href="/{{ package.name }}/{{ release }}"{%- if value.requires_python %} data-requires-python="{{ value.requires_python }}"{% endif %}>{{ release }}</a>
<a href="/{{ package.name }}/{{ release }}{%- if value.hash %}#sha256={{ value.hash }}{% endif %}"{%- if value.requires_python %} data-requires-python="{{ value.requires_python }}"{% endif %}>{{ release }}</a>
<br>
{% endfor %}
</body>
Expand Down