Repository: pypa/pip

Issue:
-e PATH in constraints.txt is ignored
`echo "-e file:///path/to/thing#egg=thing" > constraints.txt`
`pip install -c constraints.txt thing`

End result is the same as doing

`pip install thing`

it should install thing from /path/to/thing as an editable.



Discussion:

Resolving patch:
diff --git a/pip/req/req_install.py b/pip/req/req_install.py
--- a/pip/req/req_install.py
+++ b/pip/req/req_install.py
@@ -1065,6 +1065,8 @@ def parse_editable(editable_req, default_vcs=None):
         .[some_extra]
     """
 
+    from pip.index import Link
+
     url = editable_req
     extras = None
 
@@ -1086,9 +1088,10 @@ def parse_editable(editable_req, default_vcs=None):
         url_no_extras = path_to_url(url_no_extras)
 
     if url_no_extras.lower().startswith('file:'):
+        package_name = Link(url_no_extras).egg_fragment
         if extras:
             return (
-                None,
+                package_name,
                 url_no_extras,
                 pkg_resources.Requirement.parse(
                     '__placeholder__' + extras
@@ -1096,7 +1099,7 @@ def parse_editable(editable_req, default_vcs=None):
                 {},
             )
         else:
-            return None, url_no_extras, None, {}
+            return package_name, url_no_extras, None, {}
 
     for version_control in vcs:
         if url.lower().startswith('%s:' % version_control):
diff --git a/pip/req/req_set.py b/pip/req/req_set.py
--- a/pip/req/req_set.py
+++ b/pip/req/req_set.py
@@ -253,6 +253,13 @@ def add_requirement(self, install_req, parent_req_name=None):
                     # scanning.
                     result = []
                 elif not install_req.constraint:
+                    if (install_req.link and not (existing_req.link and
+                       install_req.link.path == existing_req.link.path)):
+                        self.reqs_to_cleanup.append(install_req)
+                        raise InstallationError(
+                            "Could not satisfy constraints for '%s': "
+                            "installation from path or url cannot be "
+                            "constrained to a version" % name)
                     # If we're now installing a constraint, mark the existing
                     # object for real installation.
                     existing_req.constraint = False
@@ -267,9 +274,12 @@ def add_requirement(self, install_req, parent_req_name=None):
             return result
 
     def has_requirement(self, project_name):
-        for name in project_name, project_name.lower():
-            if name in self.requirements or name in self.requirement_aliases:
-                return True
+        name = project_name.lower()
+        if (name in self.requirements and
+           not self.requirements[name].constraint or
+           name in self.requirement_aliases and
+           not self.requirements[self.requirement_aliases[name]].constraint):
+            return True
         return False
 
     @property


Test patch:
diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py
--- a/tests/functional/test_install_reqs.py
+++ b/tests/functional/test_install_reqs.py
@@ -256,3 +256,60 @@ def test_constraints_only_causes_error(script, data):
         'install', '--no-index', '-f', data.find_links, '-c',
         script.scratch_path / 'c.txt', expect_error=True)
     assert 'installed requiresupper' not in result.stdout
+
+
+def test_constraints_local_editable_install_causes_error(script, data):
+    script.scratch_path.join("constraints.txt").write(
+        "singlemodule==0.0.0"
+    )
+    to_install = data.src.join("singlemodule")
+    result = script.pip(
+        'install', '--no-index', '-f', data.find_links, '-c',
+        script.scratch_path / 'constraints.txt', '-e',
+        to_install, expect_error=True)
+    assert 'Could not satisfy constraints for' in result.stderr
+
+
+def test_constraints_local_install_causes_error(script, data):
+    script.scratch_path.join("constraints.txt").write(
+        "singlemodule==0.0.0"
+    )
+    to_install = data.src.join("singlemodule")
+    result = script.pip(
+        'install', '--no-index', '-f', data.find_links, '-c',
+        script.scratch_path / 'constraints.txt',
+        to_install, expect_error=True)
+    assert 'Could not satisfy constraints for' in result.stderr
+
+
+def test_constraints_constrain_to_local_editable(script, data):
+    to_install = data.src.join("singlemodule")
+    script.scratch_path.join("constraints.txt").write(
+        "-e file://%s#egg=singlemodule" % to_install
+    )
+    result = script.pip(
+        'install', '--no-index', '-f', data.find_links, '-c',
+        script.scratch_path / 'constraints.txt', 'singlemodule')
+    assert 'Running setup.py develop for singlemodule' in result.stdout
+
+
+def test_constraints_constrain_to_local(script, data):
+    to_install = data.src.join("singlemodule")
+    script.scratch_path.join("constraints.txt").write(
+        "file://%s#egg=singlemodule" % to_install
+    )
+    result = script.pip(
+        'install', '--no-index', '-f', data.find_links, '-c',
+        script.scratch_path / 'constraints.txt', 'singlemodule')
+    assert 'Running setup.py install for singlemodule' in result.stdout
+
+
+def test_constrained_to_url_install_same_url(script, data):
+    to_install = data.src.join("singlemodule")
+    script.scratch_path.join("constraints.txt").write(
+        "file://%s#egg=singlemodule" % to_install
+    )
+    result = script.pip(
+        'install', '--no-index', '-f', data.find_links, '-c',
+        script.scratch_path / 'constraints.txt', to_install)
+    assert 'Running setup.py install for singlemodule' in result.stdout