Repository: pypa/pipenv

Issue:
Local file with name of package causes strange Pipfile contents
When there is a file in the local directory that matches the name of a PyPI package, `pipenv install` of that package name does install the packages in to the virtualenv, but then writes confusing content in to Pipfile (presumably what it would do with a local repo).

##### Describe your environment

1. OS Type:

Ubuntu 17.10

1. Python version: `$ python -V`

Python 3.6.3

1. Pipenv version: `$ pipenv --version`

pipenv, version 9.0.1

##### Expected result

The package from PyPI installed and appropriately added to Pipfile.

##### Actual result

The Pipfile ends up with content pointing to the local directory.

##### Steps to replicate

In an empty directory:

```sh
touch influxdb
pipenv install influxdb
cat Pipfile
```

When I run this, I get:

```
% touch influxdb
% pipenv install influxdb
Creating a virtualenv for this project…
⠋Using base prefix '/usr'
New python executable in /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K/bin/python3
Also creating executable in /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /home/daniel/.local/share/virtualenvs/reproduce-FQ7MTL5K
Creating a Pipfile for this project…
Installing influxdb…
Collecting influxdb
  Using cached influxdb-5.0.0-py2.py3-none-any.whl
Collecting requests>=2.17.0 (from influxdb)
  Using cached requests-2.18.4-py2.py3-none-any.whl
Collecting python-dateutil>=2.6.0 (from influxdb)
  Using cached python_dateutil-2.6.1-py2.py3-none-any.whl
Collecting six>=1.10.0 (from influxdb)
  Using cached six-1.11.0-py2.py3-none-any.whl
Collecting pytz (from influxdb)
  Using cached pytz-2017.3-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests>=2.17.0->influxdb)
  Using cached certifi-2017.11.5-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests>=2.17.0->influxdb)
  Using cached idna-2.6-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests>=2.17.0->influxdb)
  Using cached urllib3-1.22-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.17.0->influxdb)
  Using cached chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: certifi, idna, urllib3, chardet, requests, six, python-dateutil, pytz, influxdb
Successfully installed certifi-2017.11.5 chardet-3.0.4 idna-2.6 influxdb-5.0.0 python-dateutil-2.6.1 pytz-2017.3 requests-2.18.4 six-1.11.0 urllib3-1.22

Adding influxdb to Pipfile's [packages]…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (baaf70)!
% cat Pipfile
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

"6a95e85" = {path = "./influxdb"}


[dev-packages]
```


Discussion:
Thanks for the report, @OddBloke. This was supposed to be fixed in 9.0.0 but it looks like we may have missed something.

Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -35,7 +35,9 @@
 from piptools.scripts.compile import get_pip_command
 from piptools import logging
 from piptools.exceptions import NoCandidateFound
+from pip.download import is_archive_file
 from pip.exceptions import DistributionNotFound
+from pip.index import Link
 from requests.exceptions import HTTPError, ConnectionError
 
 from .pep508checker import lookup
@@ -303,17 +305,20 @@ def get_requirement(dep):
         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() or dep_path.as_posix() == '.':
-                path = dep_path.as_posix()
-            else:
-                path = get_converted_relative_path(dep)
-            dep = dep_path.resolve().as_uri()
+            dep_link = Link(dep_path.absolute().as_uri())
+            if dep_path.is_dir() or dep_link.is_wheel or is_archive_file(dep_path.as_posix()):
+                if dep_path.is_absolute() or dep_path.as_posix() == '.':
+                    path = dep_path.as_posix()
+                else:
+                    path = get_converted_relative_path(dep)
+                dep = dep_link.egg_fragment if dep_link.egg_fragment else dep_link.url_without_fragment
     req = [r for r in requirements.parse(dep)][0]
     # If the result is a local file with a URI and we have a local path, unset the URI
     # and set the path instead
-    if req.local_file and req.uri and not req.path and path:
+    if path and not req.path:
         req.path = path
         req.uri = None
+        req.local_file = True
     if markers:
         req.markers = markers
     if extras:
@@ -626,7 +631,7 @@ def convert_deps_from_pip(dep):
     extras = {'extras': req.extras}
 
     # File installs.
-    if (req.uri or req.path or (os.path.isfile(req.name) if req.name else False)) and not req.vcs:
+    if (req.uri or req.path or (is_installable_file(req.name) if req.name else False)) and not req.vcs:
         # Assign a package name to the file, last 7 of it's sha256 hex digest.
         if not req.uri and not req.path:
             req.path = os.path.abspath(req.name)
@@ -880,8 +885,12 @@ def is_installable_file(path):
         else:
             return False
     lookup_path = Path(path)
-    return lookup_path.is_file() or (lookup_path.is_dir() and
-            pip.utils.is_installable_dir(lookup_path.resolve().as_posix()))
+    if not lookup_path.exists():
+        return False
+    lookup_link = Link(lookup_path.resolve().as_uri())
+    absolute_path = '{0}'.format(lookup_path.absolute())
+    return ((lookup_path.is_file() and (is_archive_file(absolute_path) or lookup_link.is_wheel)) or
+                (lookup_path.is_dir() and pip.utils.is_installable_dir(lookup_path.as_posix())))
 
 
 def is_file(package):


Test patch:
diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py
--- a/tests/test_pipenv.py
+++ b/tests/test_pipenv.py
@@ -1102,3 +1102,17 @@ def test_relative_paths(self):
             assert 'path' in dep
             assert Path(os.path.join('.', artifact_dir, file_name)) == Path(dep['path'])
             assert c.return_code == 0
+
+    @pytest.mark.install
+    @pytest.mark.local_file
+    def test_install_local_file_collision(self):
+        with PipenvInstance() as p:
+            target_package = 'ansible'
+            fake_file = os.path.join(p.path, target_package)
+            with open(fake_file, 'w') as f:
+                f.write('')
+            c = p.pipenv('install {}'.format(target_package))
+            assert c.return_code == 0
+            assert 'ansible' in p.pipfile['packages']
+            assert p.pipfile['packages']['ansible'] == '*'
+            assert 'ansible' in p.lockfile['default']