Repository: pypa/pipenv

Issue:
Running pipenv in `/` causes a stacktrace `AssertionError` on `assert self.name`
### Issue description

A stacktrace is produced when running in `/`, this same stacktrace appears in #2306 but there was no resolution/fix there.

### Expected result

No stacktrace, perhaps a useful error message, or perhaps when in `/` it could use a `name` of `'root'` and "just work".

### Actual result

```
$ docker run --rm -ti python:3.5-slim bash -c 'pip install pipenv && pipenv install pytest'
Collecting pipenv
  Downloading https://files.pythonhosted.org/packages/13/b4/3ffa55f77161cff9a5220f162670f7c5eb00df52e00939e203f601b0f579/pipenv-2018.11.26-py3-none-any.whl (5.2MB)
    100% |████████████████████████████████| 5.2MB 6.9MB/s 
Collecting virtualenv-clone>=0.2.5 (from pipenv)
  Downloading https://files.pythonhosted.org/packages/16/9d/6419a4f0fe4350db7fdc01e9d22e949779b6f2d2650e4884aa8aededc5ae/virtualenv_clone-0.4.0-py2.py3-none-any.whl
Collecting certifi (from pipenv)
  Downloading https://files.pythonhosted.org/packages/9f/e0/accfc1b56b57e9750eba272e24c4dddeac86852c2bebd1236674d7887e8a/certifi-2018.11.29-py2.py3-none-any.whl (154kB)
    100% |████████████████████████████████| 163kB 16.2MB/s 
Requirement already satisfied: pip>=9.0.1 in /usr/local/lib/python3.5/site-packages (from pipenv) (18.1)
Requirement already satisfied: setuptools>=36.2.1 in /usr/local/lib/python3.5/site-packages (from pipenv) (40.6.3)
Collecting virtualenv (from pipenv)
  Downloading https://files.pythonhosted.org/packages/6a/d1/e0d142ce7b8a5c76adbfad01d853bca84c7c0240e35577498e20bc2ade7d/virtualenv-16.2.0-py2.py3-none-any.whl (1.9MB)
    100% |████████████████████████████████| 1.9MB 13.6MB/s 
Installing collected packages: virtualenv-clone, certifi, virtualenv, pipenv
Successfully installed certifi-2018.11.29 pipenv-2018.11.26 virtualenv-16.2.0 virtualenv-clone-0.4.0
Creating a virtualenv for this project…
Pipfile: /Pipfile
Using /usr/local/bin/python (3.5.6) to create virtualenv…
⠸ Creating virtual environment...Already using interpreter /usr/local/bin/python
Using base prefix '/usr/local'
New python executable in /root/.local/share/virtualenvs/-x-v5uFv0/bin/python
Installing setuptools, pip, wheel...
done.

✔ Successfully created virtual environment! 
Virtualenv location: /root/.local/share/virtualenvs/-x-v5uFv0
Creating a Pipfile for this project…
Traceback (most recent call last):
  File "/usr/local/bin/pipenv", line 11, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.5/site-packages/pipenv/vendor/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/usr/local/lib/python3.5/site-packages/pipenv/cli/command.py", line 254, in install
    editable_packages=state.installstate.editables,
  File "/usr/local/lib/python3.5/site-packages/pipenv/core.py", line 1741, in do_install
    pypi_mirror=pypi_mirror,
  File "/usr/local/lib/python3.5/site-packages/pipenv/core.py", line 609, in ensure_project
    validate=validate, skip_requirements=skip_requirements, system=system
  File "/usr/local/lib/python3.5/site-packages/pipenv/core.py", line 320, in ensure_pipfile
    project.create_pipfile(python=python)
  File "/usr/local/lib/python3.5/site-packages/pipenv/project.py", line 697, in create_pipfile
    config_parser = ConfigOptionParser(name=self.name)
  File "/usr/local/lib/python3.5/site-packages/pipenv/patched/notpip/_internal/cli/parser.py", line 142, in __init__
    assert self.name
AssertionError
```

### Steps to replicate

The actual result above contains a single command which replicates this issue

-------------------------------------------------------------------------------

<details><summary>$ pipenv --support</summary>

Pipenv version: `'2018.11.26'`

Pipenv location: `'/usr/local/lib/python3.5/site-packages/pipenv'`

Python location: `'/usr/local/bin/python'`

Python installations found:

  - `3.5.6`: `/usr/local/bin/python3.5m`
  - `3.5.6`: `/usr/local/bin/python3`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.5.6',
 'os_name': 'posix',
 'platform_machine': 'x86_64',
 'platform_python_implementation': 'CPython',
 'platform_release': '4.9.125-linuxkit',
 'platform_system': 'Linux',
 'platform_version': '#1 SMP Fri Sep 7 08:20:28 UTC 2018',
 'python_full_version': '3.5.6',
 'python_version': '3.5',
 'sys_platform': 'linux'}
```

System environment variables:

  - `PYTHON_VERSION`
  - `TERM`
  - `_`
  - `PYTHONFINDER_IGNORE_UNSUPPORTED`
  - `PYTHON_PIP_VERSION`
  - `PIP_SHIMS_BASE_MODULE`
  - `PYTHONDONTWRITEBYTECODE`
  - `PWD`
  - `PIP_PYTHON_PATH`
  - `HOSTNAME`
  - `HOME`
  - `PIP_DISABLE_PIP_VERSION_CHECK`
  - `SHLVL`
  - `PATH`
  - `GPG_KEY`
  - `LANG`

Pipenv–specific environment variables:


Debug–specific environment variables:

  - `PATH`: `/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
  - `LANG`: `C.UTF-8`
  - `PWD`: `/`


---------------------------

</details>


Discussion:

Resolving patch:
diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py
--- a/pipenv/cli/command.py
+++ b/pipenv/cli/command.py
@@ -152,7 +152,11 @@ def cli(
             # There is no virtualenv yet.
             if not project.virtualenv_exists:
                 echo(
-                    crayons.red("No virtualenv has been created for this project yet!"),
+                    "{}({}){}".format(
+                        crayons.red("No virtualenv has been created for this project"),
+                        crayons.white(project.project_directory, bold=True),
+                        crayons.red(" yet!")
+                    ),
                     err=True,
                 )
                 ctx.abort()
diff --git a/pipenv/core.py b/pipenv/core.py
--- a/pipenv/core.py
+++ b/pipenv/core.py
@@ -539,11 +539,16 @@ def ensure_project(
     # Automatically use an activated virtualenv.
     if PIPENV_USE_SYSTEM:
         system = True
-    if not project.pipfile_exists:
-        if deploy is True:
-            raise exceptions.PipfileNotFound
-        else:
-            project.touch_pipfile()
+    if not project.pipfile_exists and deploy:
+        raise exceptions.PipfileNotFound
+    # Fail if working under /
+    if not project.name:
+        click.echo(
+            "{0}: Pipenv is not intended to work under the root directory, "
+            "please choose another path.".format(crayons.red("ERROR")),
+            err=True
+        )
+        sys.exit(1)
     # Skip virtualenv creation when --system was used.
     if not system:
         ensure_virtualenv(
@@ -606,24 +611,23 @@ def shorten_path(location, bold=False):
 def do_where(virtualenv=False, bare=True):
     """Executes the where functionality."""
     if not virtualenv:
-        location = project.pipfile_location
-        # Shorten the virtual display of the path to the virtualenv.
-        if not bare:
-            location = shorten_path(location)
-        if not location:
+        if not project.pipfile_exists:
             click.echo(
                 "No Pipfile present at project home. Consider running "
                 "{0} first to automatically generate a Pipfile for you."
                 "".format(crayons.green("`pipenv install`")),
                 err=True,
             )
-        elif not bare:
+            return
+        location = project.pipfile_location
+        # Shorten the virtual display of the path to the virtualenv.
+        if not bare:
+            location = shorten_path(location)
             click.echo(
                 "Pipfile found at {0}.\n  Considering this to be the project home."
                 "".format(crayons.green(location)),
                 err=True,
             )
-            pass
         else:
             click.echo(project.project_directory)
     else:
diff --git a/pipenv/project.py b/pipenv/project.py
--- a/pipenv/project.py
+++ b/pipenv/project.py
@@ -54,7 +54,7 @@ def _normalized(p):
         path_str = matches and matches[0] or str(loc)
     else:
         path_str = str(loc)
-    return normalize_drive(path_str)
+    return normalize_drive(os.path.abspath(path_str))
 
 
 DEFAULT_NEWLINES = u"\n"
@@ -226,7 +226,7 @@ def name(self):
 
     @property
     def pipfile_exists(self):
-        return bool(self.pipfile_location)
+        return os.path.isfile(self.pipfile_location)
 
     @property
     def required_python_version(self):
@@ -241,11 +241,7 @@ def required_python_version(self):
 
     @property
     def project_directory(self):
-        if self.pipfile_location is not None:
-            return os.path.abspath(os.path.join(self.pipfile_location, os.pardir))
-
-        else:
-            return None
+        return os.path.abspath(os.path.join(self.pipfile_location, os.pardir))
 
     @property
     def requirements_exists(self):
@@ -259,8 +255,7 @@ def is_venv_in_project(self):
 
     @property
     def virtualenv_exists(self):
-        # TODO: Decouple project from existence of Pipfile.
-        if self.pipfile_exists and os.path.exists(self.virtualenv_location):
+        if os.path.exists(self.virtualenv_location):
             if os.name == "nt":
                 extra = ["Scripts", "activate.bat"]
             else:
@@ -478,7 +473,7 @@ def pipfile_location(self):
             try:
                 loc = pipfile.Pipfile.find(max_depth=PIPENV_MAX_DEPTH)
             except RuntimeError:
-                loc = None
+                loc = "Pipfile"
             self._pipfile_location = _normalized(loc)
         return self._pipfile_location
 
@@ -507,6 +502,8 @@ def parsed_pipfile(self):
 
     def read_pipfile(self):
         # Open the pipfile, read it into memory.
+        if not self.pipfile_exists:
+            return ""
         with io.open(self.pipfile_location) as f:
             contents = f.read()
             self._pipfile_newlines = preferred_newlines(f)
@@ -664,11 +661,6 @@ def dev_packages(self):
         """Returns a list of dev-packages, for pip-tools to consume."""
         return self._build_package_list("dev-packages")
 
-    def touch_pipfile(self):
-        """Simply touches the Pipfile, for later use."""
-        with open("Pipfile", "a"):
-            os.utime("Pipfile", None)
-
     @property
     def pipfile_is_empty(self):
         if not self.pipfile_exists:
@@ -685,8 +677,7 @@ def create_pipfile(self, python=None):
             ConfigOptionParser, make_option_group, index_group
         )
 
-        name = self.name if self.name is not None else "Pipfile"
-        config_parser = ConfigOptionParser(name=name)
+        config_parser = ConfigOptionParser(name=self.name)
         config_parser.add_option_group(make_option_group(index_group, config_parser))
         install = config_parser.option_groups[0]
         indexes = (
@@ -839,7 +830,7 @@ def write_lockfile(self, content):
 
     @property
     def pipfile_sources(self):
-        if "source" not in self.parsed_pipfile:
+        if self.pipfile_is_empty or "source" not in self.parsed_pipfile:
             return [DEFAULT_SOURCE]
         # We need to make copies of the source info so we don't
         # accidentally modify the cache. See #2100 where values are


Test patch: