Repository: pypa/pipenv

Issue:
extras_require and length of name > 5 fails.
I have a setup.py that looks like this:

```python3
from setuptools import setup, find_packages

setup(
    name='graphene_utils',
    version='1.0',
    packages=find_packages(),
    include_package_data=True,
    install_requires=[
        'graphene>=2.0.dev',
    ],
    extras_require={
        'sqlalchemy': [
            'sqlalchemy',
        ],
        'test': [
            'pylint',
        ],
    },
    entry_points='''
    [console_scripts]
    run=server:run
    ''',
)
```

When attempting `pipenv install "-e .[sqlalchemy]"` it fails with:
```bash
~/code/graphene_utils (virtualenvs/graphene_utils-Dw3J0BOS)
$ pipenv install "-e .[sqlalchemy]"
Courtesy Notice: Pipenv found itself running within a virtual environment, so it will automatically use that environment, instead of creating its own for any project.
Discovering imports from local codebase…
Installing -e .[sqlal…
⠸
Error:  An error occurred while installing -e .[sqlal!
.[sqlal should either be a path to a local project or a VCS url beginning with svn+, git+, hg+, or bzr+
```

However, renaming the extra_requires to `sqla` works fine.


Discussion:
Yeah, this is definitely a bug. I haven't had a chance to look into it yet, but I'll try to set aside some time this weekend. If anyone else would like to pick this up before then, feel free.
Are there any workarounds for this?
We still haven't root caused the issue, so the only suggestion at this time is you'll have to use pip or a non-wheel (I wouldn't recommend with something like tensorflow).
What are the consequences of using pip inside `pipenv shell`?

Does this just screw with the lock file?
@spott yes, it will install but nothing will be added to the lockfile or Pipfile. This means you'd have to run this step on every install.
@nateprewitt: I'll have to run which step?  pipenv lock?
Tensorflow won't be added to the Pipfile when you install it via pip. This means it will also not be included in the lockfile when locking.

You'll need to run pipenv shell and install Tensorflow via pip anytime you run pipenv install.
@nateprewitt:  Ah.  Thanks.  hopefully this bug will be fixed when I next need to do that.
@nateprewitt I've got the bug and part of the solution (it's the same as the local file installation issues, relating to passing relative paths to the Requirements library which can't parse anything with slashes) -- just need to fix the piece for markers as well.  Edited because I read the actual issue-- will add that to todos

Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -277,15 +277,33 @@ def get_requirement(dep):
     remote URIs, and package names, and that we pass only valid requirement strings
     to the requirements parser. Performs necessary modifications to requirements
     object if the user input was a local relative path.
+    
+    :param str dep: A requirement line
+    :returns: :class:`requirements.Requirement` object
     """
     path = None
+    # Split out markers if they are present - similar to how pip does it
+    # See pip.req.req_install.InstallRequirement.from_line
+    if not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
+        marker_sep = ';'
+    else:
+        marker_sep = '; '
+    if marker_sep in dep:
+        dep, markers = dep.split(marker_sep, 1)
+        markers = markers.strip()
+        if not markers:
+            markers = None
+    else:
+        markers = None
+    # Strip extras from the requirement so we can make a properly parseable req
+    dep, extras = pip.req.req_install._strip_extras(dep)
     # Only operate on local, existing, non-URI formatted paths
     if (is_file(dep) and isinstance(dep, six.string_types) and
             not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST)):
         dep_path = Path(dep)
         # Only parse if it is a file or an installable dir
         if dep_path.is_file() or (dep_path.is_dir() and pip.utils.is_installable_dir(dep)):
-            if dep_path.is_absolute():
+            if dep_path.is_absolute() or dep_path.as_posix() == '.':
                 path = dep
             else:
                 path = get_converted_relative_path(dep)
@@ -296,6 +314,11 @@ def get_requirement(dep):
     if req.local_file and req.uri and not req.path and path:
         req.path = path
         req.uri = None
+    if markers:
+        req.markers = markers
+    if extras:
+        # Bizarrely this is also what pip does...
+        req.extras = [r for r in requirements.parse('fakepkg{0}'.format(extras))][0].extras
     return req
 
 
@@ -611,28 +634,26 @@ def convert_deps_from_pip(dep):
         hashable_path = req.uri if req.uri else req.path
         req.name = hashlib.sha256(hashable_path.encode('utf-8')).hexdigest()
         req.name = req.name[len(req.name) - 7:]
-
         # {path: uri} TOML (spec 4 I guess...)
         if req.uri:
             dependency[req.name] = {'file': hashable_path}
         else:
             dependency[req.name] = {'path': hashable_path}
 
+        if req.extras:
+            dependency[req.name].update(extras)
+
         # Add --editable if applicable
         if req.editable:
             dependency[req.name].update({'editable': True})
 
     # VCS Installs. Extra check for unparsed git over SSH
-    if req.vcs or is_vcs(req.path):
+    elif req.vcs or is_vcs(req.path):
         if req.name is None:
             raise ValueError('pipenv requires an #egg fragment for version controlled '
                              'dependencies. Please install remote dependency '
                              'in the form {0}#egg=<package-name>.'.format(req.uri))
 
-        # Extras: e.g. #egg=requests[security]
-        if req.extras:
-            dependency[req.name] = extras
-
         # Set up this requirement as a proper VCS requirement if it was not
         if not req.vcs and req.path.startswith(VCS_LIST):
             req.vcs = [vcs for vcs in VCS_LIST if req.path.startswith(vcs)][0]
@@ -654,6 +675,10 @@ def convert_deps_from_pip(dep):
         if req.revision:
             dependency[req.name].update({'ref': req.revision})
 
+        # Extras: e.g. #egg=requests[security]
+        if req.extras:
+            dependency[req.name].update({'extras': req.extras})
+
     elif req.extras or req.specs:
 
         specs = None
@@ -679,7 +704,6 @@ def convert_deps_from_pip(dep):
         for key in dependency.copy():
             if not hasattr(dependency[key], 'keys'):
                 del dependency[key]
-
     return dependency
 
 


Test patch:
diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py
--- a/tests/test_pipenv.py
+++ b/tests/test_pipenv.py
@@ -334,6 +334,39 @@ def test_extras_install(self):
             assert 'urllib3' in p.lockfile['default']
             assert 'pysocks' in p.lockfile['default']
 
+    @pytest.mark.extras
+    @pytest.mark.install
+    @pytest.mark.local
+    def test_local_extras_install(self):
+        with PipenvInstance() as p:
+            setup_py = os.path.join(p.path, 'setup.py')
+            with open(setup_py, 'w') as fh:
+                contents = """
+from setuptools import setup, find_packages
+
+setup(
+    name='test_pipenv',
+    version='0.1',
+    description='Pipenv Test Package',
+    author='Pipenv Test',
+    author_email='test@pipenv.package',
+    license='PIPENV',
+    packages=find_packages(),
+    install_requires=['tablib'],
+    extras_require={'dev': ['flake8', 'pylint']},
+    zip_safe=False
+)
+                """.strip()
+                fh.write(contents)
+            c = p.pipenv('install .[dev]')
+            assert c.return_code == 0
+            key = [k for k in p.pipfile['packages'].keys()][0]
+            dep = p.pipfile['packages'][key]
+            assert dep['path'] == '.'
+            assert dep['extras'] == ['dev']
+            assert key in p.lockfile['default']
+            assert 'dev' in p.lockfile['default'][key]['extras']
+
     @pytest.mark.vcs
     @pytest.mark.install
     def test_basic_vcs_install(self):