Repository: pypa/pipenv

Issue:
Pipenv with an interpreter such that `python --version` is 2.7 instead of 2.7.14 (e.g.) does not work
Be sure to check the existing issues (both open and closed!).

Describe the issue briefly here.

Please run `$ python -m pipenv.help`, and paste the results here.

<details><summary>$ python -m pipenv.help output</summary>

Pipenv version: `'11.9.0'`

Pipenv location: `'C:\\Python36\\lib\\site-packages\\pipenv'`

Python location: `'C:\\Python36\\python.exe'`

Other Python installations in `PATH`:

  - `3.6`: `C:\Python36\python.exe`

  - `3.6.3`: `C:\Python36\python.exe`
  - `2.7.14`: `C:\Windows\py.exe`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.6.3',
 'os_name': 'nt',
 'platform_machine': 'AMD64',
 'platform_python_implementation': 'CPython',
 'platform_release': '7',
 'platform_system': 'Windows',
 'platform_version': '6.1.7601',
 'python_full_version': '3.6.3',
 'python_version': '3.6',
 'sys_platform': 'win32'}
```

System environment variables:

  - `ALLUSERSPROFILE`
  - `APPDATA`
  - `APPLOGSC`
  - `AWS_ACCESS_KEY_ID`
  - `AWS_PROFILE`
  - `AWS_SECRET_ACCESS_KEY`
  - `APPLOGSU`
  - `BASH_IT`
  - `COMMONPROGRAMFILES`
  - `COMPUTERNAME`
  - `COMSPEC`
  - `COMMONPROGRAMFILES(X86)`
  - `COMMONPROGRAMW6432`
  - `DEFAULTSCREENSAVER`
  - `DESKTOP`
  - `DISTRIBUTIONPOINT`
  - `EXEPATH`
  - `FP_NO_HOST_CHECK`
  - `HADOOP_HOME`
  - `HOME`
  - `HOMEDRIVE`
  - `HOMEPATH`
  - `HTTPS_PROXY`
  - `HTTP_PROXY`
  - `JAVA_HOME`
  - `LOCALAPPDATA`
  - `LOGONSERVER`
  - `LOGPATH`
  - `MSYS2_PATH_TYPE`
  - `MSYSTEM`
  - `MOSAIC_SWPROFILE`
  - `NO_PROXY`
  - `NUMBER_OF_PROCESSORS`
  - `OLDPWD`
  - `OS`
  - `PATH`
  - `PATHEXT`
  - `PIPELINE_OUTPUT_STORAGE`
  - `PLINK_PROTOCOL`
  - `PROCESSOR_ARCHITECTURE`
  - `PROCESSOR_IDENTIFIER`
  - `PROCESSOR_LEVEL`
  - `PROCESSOR_REVISION`
  - `PROGRAMFILES`
  - `PROMPT`
  - `PS1`
  - `PSMODULEPATH`
  - `PUBLIC`
  - `PWD`
  - `PYTHONPATH`
  - `PROGRAMDATA`
  - `PROGRAMFILES(X86)`
  - `PROGRAMW6432`
  - `SESSIONNAME`
  - `SHLVL`
  - `SMSSITECODE`
  - `SMS_SERVERLOCATORPOINT`
  - `SPARK_HOME`
  - `SYSTEMDRIVE`
  - `SYSTEMROOT`
  - `SITESERVER`
  - `TEMP`
  - `TERM`
  - `TMP`
  - `UATDATA`
  - `UCMSVERSION`
  - `USERDNSDOMAIN`
  - `USERDOMAIN`
  - `USERDOMAIN_ROAMINGPROFILE`
  - `USERNAME`
  - `USERPROFILE`
  - `VIRTUAL_ENV`
  - `WINDIR`
  - `_`
  - `_OLD_VIRTUAL_PATH`
  - `_OLD_VIRTUAL_PROMPT`
  - `GITPASSWORD`
  - `GITUSERNAME`
  - `WINDOWS_TRACING_FLAGS`
  - `WINDOWS_TRACING_LOGFILE`
  - `PYTHONDONTWRITEBYTECODE`
  - `PIP_PYTHON_PATH`
  - `PYTHONUNBUFFERED`

Pipenv–specific environment variables:


Debug–specific environment variables:

  - `PATH`: `C:\Python36\Scripts\;C:\Python36\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\system32\wbem;c:\program files\microsoft vs code\bin;c:\program files\git\bin;c:\users\z003wedu\appdata\local\cont
inuum\anaconda3;c:\users\z003wedu\appdata\local\continuum\anaconda3\scripts;c:\users\z003wedu\appdata\roaming\python\python36\scripts;c:\spark-2.2.1-bin-hadoop2.7\bin;c:\hadoop\bin;c:\users\z003wedu\.bab
un;C:\Program Files\Java\jre1.8.0_161\bin;c:\Python27\Scripts;C:\Python36\Scripts\;C:\Python36\;C:\Program Files\Microsoft VS Code\bin;C:\Program Files\Git\bin;c:\users\z003wedu\appdata\roaming\python\py
thon36\Scripts;C:\spark-2.2.1-bin-hadoop2.7\bin;C:\hadoop\bin;C:\Users\Z003WEDU\.babun`
  - `PWD`: `C:/Users/Z003WEDU/Documents/Project/cdc-etl/appcode`
  - `VIRTUAL_ENV`: `C:\Users\Z003WEDU\.virtualenvs\appcode-zMMxqxoJ`


---------------------------

Contents of `Pipfile` ('C:\\Users\\Z003WEDU\\Documents\\Project\\cdc-etl\\appcode\\Pipfile'):

```toml
[[source]]
name = "pypi"
url = "https://pypi.python.org/simple"
verify_ssl = true

[requires]
python_version = "2.7"

[dev-packages]
pyyaml = "*"
nose = "*"
numpy = "*"
faker = "*"

[packages]
nose = "*"
"py4j" = "*"
awscli = "*"
boto = "*"
"boto3" = "*"
botocore = "*"
python-dateutil = "*"
six = "*"
jmespath = "*"
pyspark = "==2.2.1"

```

</details>

------------

##### Expected result

Using a python interpreter whose version is just `2.7` instead of `2.7.14` (e.g.) should work just fine.

##### Actual result

I was using an Python distribution with version [2.7](https://www.python.org/download/releases/2.7/).

This python interpreter's version is such that the parsing logic inside pipenv does not work.

When I create a virtual environment using the previous interpreter (through the flag `--python`), the execution throws an error because the parsing logic returns `None` for this interpreter's version.

##### Steps to replicate

* Requirements:
  * Having a python interpreter from [here](https://www.python.org/download/releases/2.7/)

```sh
$ c:/Python270/python.exe --version
Python 2.7
```

* Steps:
  * Create a virtual environment with pipenv, using this Python interpreter, e.g.:

```sh
pipenv --python c:/Python270/python.exe
```

Output:

```sh
Creating a virtualenv for this project▒
Using c:/Python270/python.exe (None) to create virtualenv▒
Running virtualenv with interpreter c:/Python270/python.exe
New python executable in C:\Users\Z003WEDU\.virtualenvs\test_-Ol8GhxN4\Scripts\python.exe
Installing setuptools, pip, wheel...done.

Virtualenv location: C:\Users\Z003WEDU\.virtualenvs\test_-Ol8GhxN4
Creating a Pipfile for this project▒
Traceback (most recent call last):
  File "C:\Python36\Scripts\pipenv-script.py", line 11, in <module>
    load_entry_point('pipenv==11.9.0', 'console_scripts', 'pipenv')()
  File "c:\python36\lib\site-packages\pipenv\vendor\click\core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "c:\python36\lib\site-packages\pipenv\vendor\click\core.py", line 697, in main
    rv = self.invoke(ctx)
  File "c:\python36\lib\site-packages\pipenv\vendor\click\core.py", line 1043, in invoke
    return Command.invoke(self, ctx)
  File "c:\python36\lib\site-packages\pipenv\vendor\click\core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\python36\lib\site-packages\pipenv\vendor\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "c:\python36\lib\site-packages\pipenv\vendor\click\decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "c:\python36\lib\site-packages\pipenv\cli.py", line 235, in cli
    three=three, python=python, warn=True, site_packages=site_packages
  File "c:\python36\lib\site-packages\pipenv\core.py", line 636, in ensure_project
    ensure_pipfile(validate=validate, skip_requirements=skip_requirements)
  File "c:\python36\lib\site-packages\pipenv\core.py", line 289, in ensure_pipfile
    project.create_pipfile(python=python)
  File "c:\python36\lib\site-packages\pipenv\project.py", line 518, in create_pipfile
    'python_version': python_version(required_python)[: len('2.7')]
TypeError: 'NoneType' object is not subscriptable
```



Discussion:
I am not following the issue here, are you using python 2.7.0? Do major releases not report minor version numbers in general? Is that always true or is this just broken for you
@techalchemy Yes he is using 2.7.0. Well actually it calls itself *2.7* (without the patch number).

```
$ python -c "import sys; print sys.version_info"
sys.version_info(major=2, minor=7, micro=0, releaselevel='final', serial=0)
$ python --version
Python 2.7
```

Compare this to, say, 3.5.0:

```
$ python -c "import sys; print(sys.version_info)"
sys.version_info(major=3, minor=5, micro=0, releaselevel='final', serial=0)
$ python --version
Python 3.5.0
```
@uranusjr, that's exactly that. Sorry if I was not clear enough.

@techalchemy and @uranusjr, I suppose with version 3.5.0 the same would happened. I haven't tried.

I looked into the code for a bit and I noticed that the version parser cannot parse Python interpreters with versions like the ones we are talking about here.
@jonsnowseven Modern Python versions always attach a patch/micro number in the output, so this won’t happen with say 3.5.0. I honestly didn’t even know Python 2.7 behaves differently until now.
@uranusjr, you are absolutely right and I back you up on that one.

The reason I bumped in this error was just that: I though all Python versions had the micro version in the output. And Python 2.7(.0) actually behaves differently.

Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 import errno
 import os
+import re
 import hashlib
 import tempfile
 import sys
@@ -175,29 +176,44 @@ def cleanup_toml(tml):
     return toml
 
 
+def parse_python_version(output):
+    """Parse a Python version output returned by `python --version`.
+
+    Return a dict with three keys: major, minor, and micro. Each value is a
+    string containing a version part.
+
+    Note: The micro part would be `'0'` if it's missing from the input string.
+    """
+    version_pattern = re.compile(r'''
+        ^                   # Beginning of line.
+        Python              # Literally "Python".
+        \s                  # Space.
+        (?P<major>\d+)      # Major = one or more digits.
+        \.                  # Dot.
+        (?P<minor>\d+)      # Minor = one or more digits.
+        (?:                 # Unnamed group for dot-micro.
+            \.              # Dot.
+            (?P<micro>\d+)  # Micro = one or more digit.
+        )?                  # Micro is optional because pypa/pipenv#1893.
+        .*                  # Trailing garbage.
+        $                   # End of line.
+    ''', re.VERBOSE)
+
+    match = version_pattern.match(output)
+    if not match:
+        return None
+    return match.groupdict(default='0')
+
+
 def python_version(path_to_python):
     if not path_to_python:
         return None
-
     try:
         c = delegator.run([path_to_python, '--version'], block=False)
     except Exception:
         return None
-
-    output = c.out.strip() or c.err.strip()
-
-    @parse.with_pattern(r'.*')
-    def allow_empty(text):
-        return text
-
-    TEMPLATE = 'Python {}.{}.{:d}{:AllowEmpty}'
-    parsed = parse.parse(TEMPLATE, output, dict(AllowEmpty=allow_empty))
-    if parsed:
-        parsed = parsed.fixed
-    else:
-        return None
-
-    return u"{v[0]}.{v[1]}.{v[2]}".format(v=parsed)
+    version = parse_python_version(c.out.strip() or c.err.strip())
+    return u'{major}.{minor}.{micro}'.format(**version)
 
 
 def escape_grouped_arguments(s):


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -401,3 +401,23 @@ def test_get_requirements(self):
     )
     def test_prepare_pip_source_args(self, sources, expected_args):
         assert pipenv.utils.prepare_pip_source_args(sources, pip_args=None) == expected_args
+
+    @pytest.mark.utils
+    def test_parse_python_version(self):
+        ver = pipenv.utils.parse_python_version('Python 3.6.5\n')
+        assert ver == {'major': '3', 'minor': '6', 'micro': '5'}
+
+    @pytest.mark.utils
+    def test_parse_python_version_suffix(self):
+        ver = pipenv.utils.parse_python_version('Python 3.6.5rc1\n')
+        assert ver == {'major': '3', 'minor': '6', 'micro': '5'}
+
+    @pytest.mark.utils
+    def test_parse_python_version_270(self):
+        ver = pipenv.utils.parse_python_version('Python 2.7\n')
+        assert ver == {'major': '2', 'minor': '7', 'micro': '0'}
+
+    @pytest.mark.utils
+    def test_parse_python_version_270_garbage(self):
+        ver = pipenv.utils.parse_python_version('Python 2.7+\n')
+        assert ver == {'major': '2', 'minor': '7', 'micro': '0'}