Repository: pypa/pipenv

Issue:
Non-editable install from vcs does not work
I would like to install a package from Github in non-editable mode, mainly to not pull in dependencies. When I set `editable = false` in the `Pipfile`, and run `pipenv install`, the package is installed editable anyway, and dependencies are pulled in.

My pipenv version is 11.10.1

------------

##### Expected result

Using Pipfile:

```
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
slicerator = {git = "https://github.com/soft-matter/slicerator.git", editable = false, ref = "master"}

[requires]
python_version = "2.7"
```

I expect a non editable install of `slicerator`, without dependencies.

##### Actual result

```
 ROUND 1                           
Current constraints:
  slicerator from git+https://github.com/soft-matter/slicerator.git@master#egg=slicerator

Finding the best candidates:
  found candidate -e git+https://github.com/soft-matter/slicerator.git@master#egg=slicerator (constraint was <any>)

Finding secondary dependencies:

New dependencies found in this round:
  adding [u'six', '', '[]']
Removed dependencies in this round:
Unsafe dependencies in this round:
------------------------------------------------------------
Result of round 1: not stable

                          ROUND 2                           
Current constraints:
  six
  slicerator from git+https://github.com/soft-matter/slicerator.git@master#egg=slicerator

Finding the best candidates:
  found candidate six==1.11.0 (constraint was <any>)
  found candidate -e git+https://github.com/soft-matter/slicerator.git@master#egg=slicerator (constraint was <any>)

Finding secondary dependencies:
  six==1.11.0               requires -
------------------------------------------------------------
Result of round 2: stable, done

Updated Pipfile.lock (89400d)!
```

Resulting lockfile:

```
{
    "_meta": {
        "hash": {
            "sha256": "c7173951bea190534637732bfe1da99d65f79ac699656354358940dd1289400d"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "2.7"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "six": {
            "hashes": [
                "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
                "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
            ],
            "version": "==1.11.0"
        },
        "slicerator": {
            "editable": false,
            "git": "https://github.com/soft-matter/slicerator.git",
            "ref": "master"
        }
    },
    "develop": {}
}
```

##### Steps to replicate

Use the Pipfile above, then `pipenv install`.

This issue is part of #2068 I raised earlier.



Discussion:
Erm so. Dependencies are _always_ installed. Editable just dictates how thoroughly the traversal is done and whether they land in your lockfile. It would be kind of pointless if you could install something and it didn’t include dependencies. Pip forces you to explicitly pass `--no-deps` for that
@techalchemy  In the docs it is stated that sub-dependencies are not resolved if you set editable to false:

["Sub-dependencies are not added to the Pipfile.lock if you leave the -e option out."](
https://docs.pipenv.org/basics/#editable-dependencies-e-g-e)
["If editable is not true, sub–dependencies will not be resolved."](https://docs.pipenv.org/basics/#a-note-about-vcs-dependencies)

Instead, `pip` is called with `-e`, sub-dependencies (here: `six`) are resolved, and they end in the lockfile.

As far as I can see, there is no difference currently between `editable = true` and `editable = false`?
@caspervdw I see. There is possibly no difference between `editable = true` and `editable = false` but I have never seen this used this way. If you exclude the `editable` key entirely (because it’s presence is what toggles the flag for pip, we just need to assign a value to it) it works as described. I say this often, but this is among the reasons to let pipenv build your pipfiles. 
Also to improve our documentation and handling of these kinds of cases of course. This is a pretty dumb bug to have, and I can see why you would toggle it to false 
Great @techalchemy , thanks a lot, without `editable = false`, behaviour is as desired 

I edited the Pipfile directly, as `pipenv install git+git@github.com:soft-matter/slicerator.git#egg=slicerator`  gives me the error:

```
Invalid requirement: 'git+git@github.com:soft-matter/slicerator.git#egg=slicerator'
It looks like a path. File 'git+git@github.com:soft-matter/slicerator.git#egg=slicerator' does not exist.
```
May also be a bug? Not sure if I provided the VCS correctly.
Omg. Definitely a bug. This part of our codebase sucks. I probably introduced this last time I touched it.  Will have a look. Thanks for the report!
Happy to help! Thanks for the fast responses. Maybe good to reopen the issue?

Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -564,6 +564,10 @@ def multi_split(s, split):
 
 def convert_deps_from_pip(dep):
     """"Converts a pip-formatted dependency to a Pipfile-formatted one."""
+    try:
+        from collections.abc import Mapping
+    except ImportError:
+        from collections import Mapping
     dependency = {}
     req = get_requirement(dep)
     extras = {'extras': req.extras}
@@ -657,6 +661,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
     for dep in deps.keys():
         # Default (e.g. '>1.10').
         extra = deps[dep] if isinstance(deps[dep], six.string_types) else ''
+        editable = False
         extras = ''
         version = ''
         index = ''
@@ -712,6 +717,8 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
         vcs = maybe_vcs[0] if maybe_vcs else None
         if not any(key in deps[dep] for key in ['path', 'vcs', 'file']):
             extra += extras
+        if isinstance(deps[dep], Mapping):
+            editable = bool(deps[dep].get('editable', False))
         # Support for files.
         if 'file' in deps[dep]:
             dep_file = deps[dep]['file']
@@ -719,18 +726,12 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
                 dep_file += '#egg={0}'.format(dep)
             extra = '{0}{1}'.format(dep_file, extras).strip()
             # Flag the file as editable if it is a local relative path
-            if 'editable' in deps[dep]:
-                dep = '-e '
-            else:
-                dep = ''
+            dep = '-e ' if editable else ''
         # Support for paths.
         elif 'path' in deps[dep]:
             extra = '{1}{0}'.format(extras, deps[dep]['path']).strip()
             # Flag the file as editable if it is a local relative path
-            if 'editable' in deps[dep]:
-                dep = '-e '
-            else:
-                dep = ''
+            dep = '-e ' if editable else ''
         if vcs:
             extra = '{0}+{1}'.format(vcs, deps[dep][vcs])
             # Support for @refs.
@@ -741,11 +742,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
             if 'subdirectory' in deps[dep]:
                 extra += '&subdirectory={0}'.format(deps[dep]['subdirectory'])
             # Support for editable.
-            if 'editable' in deps[dep]:
-                # Support for --egg.
-                dep = '-e '
-            else:
-                dep = ''
+            dep = '-e ' if editable else ''
 
         s = '{0}{1}{2}{3}{4} {5}'.format(
             dep, extra, version, specs, hash, index


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -2,7 +2,7 @@
 import os
 import pytest
 from mock import patch, Mock
-
+from first import first
 import pipenv.utils
 
 
@@ -56,6 +56,7 @@
         {'requests': {
             'git': 'https://github.com/requests/requests.git',
             'ref': 'master', 'extras': ['security'],
+            'editable': False
         }},
         'git+https://github.com/requests/requests.git@master#egg=requests[security]',
     ),
@@ -108,6 +109,10 @@ def test_convert_deps_to_pip_unicode():
 @pytest.mark.utils
 @pytest.mark.parametrize('expected, requirement', DEP_PIP_PAIRS)
 def test_convert_from_pip(expected, requirement):
+    # We don't build requirements back up with the editable key, so lets drop it out
+    package = first(expected.keys())
+    if hasattr(expected[package], 'keys') and expected[package].get('editable') is False:
+        del expected[package]['editable']
     assert pipenv.utils.convert_deps_from_pip(requirement) == expected