diff --git a/news/3057.feature.rst b/news/3057.feature.rst new file mode 100644 index 0000000000..e13861e52c --- /dev/null +++ b/news/3057.feature.rst @@ -0,0 +1 @@ +``pipenv install`` and ``pipenv sync`` will no longer attempt to install satisfied dependencies during installation. diff --git a/pipenv/core.py b/pipenv/core.py index 1ab68bb3fc..b28af3491c 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -735,6 +735,9 @@ def batch_install(deps_list, procs, failed_deps_queue, deps_to_install = deps_list[:] deps_to_install.extend(sequential_deps) + deps_to_install = [ + dep for dep in deps_to_install if not project.environment.is_satisfied(dep) + ] sequential_dep_names = [d.name for d in sequential_deps] deps_list_bar = progress.bar( diff --git a/pipenv/environment.py b/pipenv/environment.py index 7538ea9efa..09ea73a3f4 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -19,12 +19,14 @@ import pipenv from .vendor.cached_property import cached_property +from .vendor.packaging.utils import canonicalize_name from .vendor import vistir from .utils import normalize_path, make_posix BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path) +# TODO: Unittests for this class class Environment(object): @@ -712,6 +714,33 @@ def is_installed(self, pkgname): return any(d for d in self.get_distributions() if d.project_name == pkgname) + def is_satisfied(self, req): + match = next( + iter( + d for d in self.get_distributions() + if canonicalize_name(d.project_name) == req.normalized_name + ), None + ) + if match is not None: + if req.editable and req.line_instance.is_local and self.find_egg(match): + requested_path = req.line_instance.path + return requested_path and vistir.compat.samefile(requested_path, match.location) + elif match.has_metadata("direct_url.json"): + direct_url_metadata = json.loads(match.get_metadata("direct_url.json")) + commit_id = direct_url_metadata.get("vcs_info", {}).get("commit_id", "") + vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "") + _, pipfile_part = req.as_pipfile().popitem() + return ( + vcs_type == req.vcs and commit_id == req.commit_hash + and direct_url_metadata["url"] == pipfile_part[req.vcs] + ) + elif req.line_instance.specifiers is not None: + return req.line_instance.specifiers.contains( + match.version, prereleases=True + ) + return True + return False + def run(self, cmd, cwd=os.curdir): """Run a command with :class:`~subprocess.Popen` in the context of the environment diff --git a/tasks/vendoring/patches/vendor/vistir-imports.patch b/tasks/vendoring/patches/vendor/vistir-imports.patch index 725e8a56d9..dc48bf3ab8 100644 --- a/tasks/vendoring/patches/vendor/vistir-imports.patch +++ b/tasks/vendoring/patches/vendor/vistir-imports.patch @@ -15,30 +15,32 @@ diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py index b5904bc7..a44aafbe 100644 --- a/pipenv/vendor/vistir/compat.py +++ b/pipenv/vendor/vistir/compat.py -@@ -43,7 +43,7 @@ __all__ = [ +@@ -55,7 +55,7 @@ __all__ = [ if sys.version_info >= (3, 5): # pragma: no cover from pathlib import Path else: # pragma: no cover - from pathlib2 import Path + from pipenv.vendor.pathlib2 import Path - if six.PY3: # pragma: no cover + if sys.version_info >= (3, 4): # pragma: no cover # Only Python 3.4+ is supported -@@ -53,14 +53,14 @@ if six.PY3: # pragma: no cover - from weakref import finalize +@@ -85,8 +85,8 @@ if sys.version_info >= (3, 4): # pragma: no cover + else: # pragma: no cover # Only Python 2.7 is supported - from backports.functools_lru_cache import lru_cache -+ from pipenv.vendor.backports.functools_lru_cache import lru_cache - from .backports.functools import partialmethod # type: ignore - from backports.shutil_get_terminal_size import get_terminal_size ++ from pipenv.vendor.backports.functools_lru_cache import lru_cache + from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size + from .backports.functools import partialmethod # type: ignore from .backports.surrogateescape import register_surrogateescape + from collections import ( +@@ -110,7 +110,7 @@ else: # pragma: no cover register_surrogateescape() NamedTemporaryFile = _NamedTemporaryFile - from backports.weakref import finalize # type: ignore + from pipenv.vendor.backports.weakref import finalize # type: ignore - try: - # Introduced Python 3.5 + try: + from os.path import samefile