diff --git a/piptools/_compat/__init__.py b/piptools/_compat/__init__.py index 9d23ddfc1..acdf3db42 100644 --- a/piptools/_compat/__init__.py +++ b/piptools/_compat/__init__.py @@ -19,7 +19,9 @@ get_installed_distributions, install_req_from_editable, install_req_from_line, + is_dir_url, is_file_url, + is_vcs_url, parse_requirements, path_to_url, stdlib_pkgs, diff --git a/piptools/_compat/pip_compat.py b/piptools/_compat/pip_compat.py index 0259ab5bd..0a199c5f4 100644 --- a/piptools/_compat/pip_compat.py +++ b/piptools/_compat/pip_compat.py @@ -30,6 +30,8 @@ def do_import(module_path, subimport=None, old_path=None): user_cache_dir = do_import("utils.appdirs", "user_cache_dir") FAVORITE_HASH = do_import("utils.hashes", "FAVORITE_HASH") is_file_url = do_import("download", "is_file_url") +is_dir_url = do_import("download", "is_dir_url") +is_vcs_url = do_import("download", "is_vcs_url") path_to_url = do_import("download", "path_to_url") url_to_path = do_import("download", "url_to_path") PackageFinder = do_import("index", "PackageFinder") diff --git a/piptools/repositories/pypi.py b/piptools/repositories/pypi.py index 1397b97cf..d60d37536 100644 --- a/piptools/repositories/pypi.py +++ b/piptools/repositories/pypi.py @@ -19,7 +19,9 @@ TemporaryDirectory, Wheel, contextlib, + is_dir_url, is_file_url, + is_vcs_url, path_to_url, url_to_path, ) @@ -282,21 +284,29 @@ def get_dependencies(self, ireq): def get_hashes(self, ireq): """ Given an InstallRequirement, return a set of hashes that represent all - of the files for a given requirement. Editable requirements return an + of the files for a given requirement. Unhashable requirements return an empty set. Unpinned requirements raise a TypeError. """ - if ireq.editable: - return set() - - if is_url_requirement(ireq): - # url requirements may have been previously downloaded and cached - # locally by self.resolve_reqs() - cached_path = os.path.join(self._download_dir, ireq.link.filename) - if os.path.exists(cached_path): - cached_link = Link(path_to_url(cached_path)) - else: - cached_link = ireq.link - return {self._get_file_hash(cached_link)} + + if ireq.link: + link = ireq.link + + if is_vcs_url(link) or (is_file_url(link) and is_dir_url(link)): + # Return empty set for unhashable requirements. + # Unhashable logic modeled on pip's + # RequirementPreparer.prepare_linked_requirement + return set() + + if is_url_requirement(ireq): + # Directly hash URL requirements. + # URL requirements may have been previously downloaded and cached + # locally by self.resolve_reqs() + cached_path = os.path.join(self._download_dir, link.filename) + if os.path.exists(cached_path): + cached_link = Link(path_to_url(cached_path)) + else: + cached_link = link + return {self._get_file_hash(cached_link)} if not is_pinned_requirement(ireq): raise TypeError("Expected pinned requirement, got {}".format(ireq)) diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index 55af6c61e..5fb45ea43 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -335,12 +335,15 @@ def test_locally_available_editable_package_is_not_archived_in_cache_dir( ), ], ) -def test_url_package(runner, line, dependency, rewritten_line): +@mark.parametrize(("generate_hashes",), [(True,), (False,)]) +def test_url_package(runner, line, dependency, rewritten_line, generate_hashes): if rewritten_line is None: rewritten_line = line with open("requirements.in", "w") as req_in: req_in.write(line) - out = runner.invoke(cli, ["-n", "--rebuild"]) + out = runner.invoke( + cli, ["-n", "--rebuild"] + (["--generate-hashes"] if generate_hashes else []) + ) assert out.exit_code == 0 assert rewritten_line in out.output assert dependency in out.output