From f051d348f58fa3509dc3cddaa691ac9300469790 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Wed, 29 Apr 2020 03:47:33 -0400 Subject: [PATCH] Skip satisfied dependencies during installation - Skip satisfied dependencies in the environment during install by checking whether the constraint of a specifier is satisfied - If there is no specifier and a dependency is installed, assume it is satisfied - For editable dependencies, if the dependency in the environment is an egg link and points at the same path as the given dep, assume it is satisfied - Fixes #3057 Signed-off-by: Dan Ryan --- news/3057.feature.rst | 1 + pipenv/core.py | 3 +++ pipenv/environment.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 news/3057.feature.rst 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..735a63bb95 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -19,6 +19,7 @@ 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 @@ -712,6 +713,23 @@ 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 self.find_egg(match): + return req.line_instance.path == match.location + 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