Repository: pypa/pipenv

Issue:
pipenv lock --keep-outdated doesn't update dependencies with extra details
Running `pipenv lock --keep-outdated` when a dependency with extras has changed in the Pipfile doesn't update the Pipfile.lock.

**Steps to replicate:**

Use these initial files:
<details>
<summary>Pipfile</summary>

```
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "==1.0.*"
raven = {extras = ["flask"], version = "==5.27.1"}
```

</details>

<details>
<summary>Pipfile.lock</summary>

```
{
    "_meta": {
        "hash": {
            "sha256": "4366926dcf71ad4f0e877f5ad9bb7de1a02e1177d0c2d7d1c4ad699f5bf25bdb"
        },
        "pipfile-spec": 6,
        "requires": {},
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "blinker": {
            "hashes": [
                "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
            ],
            "version": "==1.4"
        },
        "click": {
            "hashes": [
                "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
                "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
            ],
            "version": "==6.7"
        },
        "contextlib2": {
            "hashes": [
                "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
                "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
            ],
            "version": "==0.5.5"
        },
        "flask": {
            "hashes": [
                "sha256:cfc15b45622f9cfee6b5803723070fd0f489b3bd662179195e702cb95fd924c8",
                "sha256:dbe2a9f539f4d0fe26fa44c08d6e556e2a4a4dd3a3fb0550f39954cf57571363"
            ],
            "index": "pypi",
            "version": "==1.0.1"
        },
        "itsdangerous": {
            "hashes": [
                "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
            ],
            "version": "==0.24"
        },
        "jinja2": {
            "hashes": [
                "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
                "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
            ],
            "version": "==2.10"
        },
        "markupsafe": {
            "hashes": [
                "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
            ],
            "version": "==1.0"
        },
        "raven": {
            "hashes": [
                "sha256:1a7b595391c4557d8a9d9245138158130e43dcad02757f18d0a458e55c1e7162",
                "sha256:6d98c03c789e2d15dc61992e5e2c1ef0018c467d3ce7b9aa5830e7940a77b69a"
            ],
            "index": "pypi",
            "version": "==5.27.1"
        },
        "werkzeug": {
            "hashes": [
                "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
                "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
            ],
            "version": "==0.14.1"
        }
    },
    "develop": {}
}
```

</details>

<br>

Edit the raven declaration in the Pipfile to read `raven = {extras = ["flask"], version = "==6.7.0"}` and run `pipenv lock --keep-outdated`. The version of raven in the Pipfile.lock will not have updated.

The behaviour works as expected for dependencies with a string version - editing the Pipfile to read `raven = "==6.7.0"` and running `pipenv lock --keep-outdated` updates the lockfile as expected.

<details><summary>$ python -m pipenv.help output</summary>

Pipenv version: `'11.10.1'`

Pipenv location: `'/Users/greysteil/code/pipenv/pipenv'`

Python location: `'/usr/local/opt/python/bin/python3.6'`

Other Python installations in `PATH`:

  - `2.6`: `/usr/bin/python2.6`
  - `2.6`: `/usr/bin/python2.6`
  - `2.7`: `/Users/greysteil/.pyenv/shims/python2.7`
  - `2.7`: `/Users/greysteil/.pyenv/shims/python2.7`
  - `2.7`: `/usr/bin/python2.7`
  - `3.5`: `/Users/greysteil/.pyenv/shims/python3.5`
  - `3.6`: `/usr/local/bin/python3.6m`
  - `3.6`: `/usr/local/bin/python3.6`
  - `3.6`: `/Users/greysteil/.pyenv/shims/python3.6`
  - `3.6`: `/usr/local/bin/python3.6`
  - `3.6`: `/usr/local/bin/python3.6`

  - `3.6.5`: `/usr/local/bin/python`
  - `2.7.14`: `/Users/greysteil/.pyenv/shims/python`
  - `3.6.5`: `/usr/local/bin/python`
  - `3.6.5`: `/usr/local/bin/python`
  - `2.7.10`: `/usr/bin/python`
  - `2.7.14`: `/Users/greysteil/.pyenv/shims/python2`
  - `3.6.5`: `/usr/local/bin/python3`
  - `3.6.5`: `/Users/greysteil/.pyenv/shims/python3`
  - `3.6.5`: `/usr/local/bin/python3`
  - `3.6.5`: `/usr/local/bin/python3`

PEP 508 Information:

```
{'implementation_name': 'cpython',
 'implementation_version': '3.6.5',
 'os_name': 'posix',
 'platform_machine': 'x86_64',
 'platform_python_implementation': 'CPython',
 'platform_release': '16.7.0',
 'platform_system': 'Darwin',
 'platform_version': 'Darwin Kernel Version 16.7.0: Wed Oct  4 00:17:00 PDT '
                     '2017; root:xnu-3789.71.6~1/RELEASE_X86_64',
 'python_full_version': '3.6.5',
 'python_version': '3.6',
 'sys_platform': 'darwin'}
```

System environment variables:

  - `TERM_PROGRAM`
  - `PYENV_ROOT`
  - `SHELL`
  - `TERM`
  - `CLICOLOR`
  - `TMPDIR`
  - `Apple_PubSub_Socket_Render`
  - `TERM_PROGRAM_VERSION`
  - `TERM_SESSION_ID`
  - `PYENV_VERSION`
  - `USER`
  - `SSH_AUTH_SOCK`
  - `PYENV_DIR`
  - `__CF_USER_TEXT_ENCODING`
  - `LSCOLORS`
  - `PATH`
  - `PWD`
  - `EDITOR`
  - `LANG`
  - `PYENV_HOOK_PATH`
  - `XPC_FLAGS`
  - `RBENV_SHELL`
  - `XPC_SERVICE_NAME`
  - `SHLVL`
  - `HOME`
  - `PYENV_SHELL`
  - `LOGNAME`
  - `SECURITYSESSIONID`
  - `PYTHONDONTWRITEBYTECODE`
  - `PIP_PYTHON_PATH`

Pipenv–specific environment variables:


Debug–specific environment variables:

  - `PATH`: `/usr/local/bin:/usr/local/Cellar/pyenv/1.2.3/libexec:/Users/greysteil/.pyenv/plugins/pyenv-virtualenv/bin:/Users/greysteil/.pyenv/plugins/pyenv-update/bin:/Users/greysteil/.pyenv/plugins/pyenv-installer/bin:/Users/greysteil/.pyenv/plugins/pyenv-doctor/bin:/Users/greysteil/.pyenv/shims:/Users/greysteil/.pyenv/bin:/Users/greysteil/.cargo/bin:/usr/local/heroku/bin:/Users/greysteil/.rbenv/shims:/usr/local/bin:./node_modules/.bin:.bundle/binstubs:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/git/bin:/Library/TeX/texbin:/usr/local/sbin`
  - `SHELL`: `/bin/bash`
  - `EDITOR`: `subl -w`
  - `LANG`: `en_GB.UTF-8`
  - `PWD`: `/Users/greysteil/code/python-test`


---------------------------

Contents of `Pipfile` ('/Users/greysteil/code/python-test/Pipfile'):

```toml
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "==1.0.*"
raven = {extras = ["flask"], version = "==5.27.1"}

```


Contents of `Pipfile.lock` ('/Users/greysteil/code/python-test/Pipfile.lock'):

```json
{
    "_meta": {
        "hash": {
            "sha256": "4366926dcf71ad4f0e877f5ad9bb7de1a02e1177d0c2d7d1c4ad699f5bf25bdb"
        },
        "pipfile-spec": 6,
        "requires": {},
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "blinker": {
            "hashes": [
                "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
            ],
            "version": "==1.4"
        },
        "click": {
            "hashes": [
                "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
                "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
            ],
            "version": "==6.7"
        },
        "contextlib2": {
            "hashes": [
                "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
                "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
            ],
            "version": "==0.5.5"
        },
        "flask": {
            "hashes": [
                "sha256:cfc15b45622f9cfee6b5803723070fd0f489b3bd662179195e702cb95fd924c8",
                "sha256:dbe2a9f539f4d0fe26fa44c08d6e556e2a4a4dd3a3fb0550f39954cf57571363"
            ],
            "index": "pypi",
            "version": "==1.0.1"
        },
        "itsdangerous": {
            "hashes": [
                "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
            ],
            "version": "==0.24"
        },
        "jinja2": {
            "hashes": [
                "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
                "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
            ],
            "version": "==2.10"
        },
        "markupsafe": {
            "hashes": [
                "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
            ],
            "version": "==1.0"
        },
        "raven": {
            "hashes": [
                "sha256:1a7b595391c4557d8a9d9245138158130e43dcad02757f18d0a458e55c1e7162",
                "sha256:6d98c03c789e2d15dc61992e5e2c1ef0018c467d3ce7b9aa5830e7940a77b69a"
            ],
            "index": "pypi",
            "version": "==5.27.1"
        },
        "werkzeug": {
            "hashes": [
                "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
                "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
            ],
            "version": "==0.14.1"
        }
    },
    "develop": {}
}

```
</details>


Discussion:
See #966 - we have this rescheduled for 11.11.0, not sure when it was regressed but if you figure out the fix I’d be happy to review ;)
Might keep this open for tracking purposes 

Resolving patch:
diff --git a/pipenv/utils.py b/pipenv/utils.py
--- a/pipenv/utils.py
+++ b/pipenv/utils.py
@@ -47,6 +47,12 @@ def detach(self):
 from .pep508checker import lookup
 from .environments import PIPENV_MAX_ROUNDS, PIPENV_CACHE_DIR
 
+from prettytoml.elements.abstracttable import AbstractTable
+try:
+    from collections.abc import Mapping
+except ImportError:
+    from collections import Mapping
+
 if six.PY2:
 
     class ResourceWarning(Warning):
@@ -639,6 +645,8 @@ def is_star(val):
 
 
 def is_pinned(val):
+    if isinstance(val, Mapping) or isinstance(val, AbstractTable):
+        val = val.get('version')
     return isinstance(val, six.string_types) and val.startswith('==')
 
 


Test patch:
diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py
--- a/tests/integration/test_lock.py
+++ b/tests/integration/test_lock.py
@@ -50,6 +50,43 @@ def test_lock_requirements_file(PipenvInstance, pypi):
             assert req in d.out
 
 
+@pytest.mark.lock
+def test_lock_keep_outdated(PipenvInstance, pypi):
+
+    with PipenvInstance(pypi=pypi) as p:
+        with open(p.pipfile_path, 'w') as f:
+            contents = """
+[packages]
+requests = {version = "==2.14.0"}
+PyTest = "==3.1.0"
+            """.strip()
+            f.write(contents)
+
+        c = p.pipenv('lock')
+        assert c.return_code == 0
+        lock = p.lockfile
+        assert 'requests' in lock['default']
+        assert lock['default']['requests']['version'] == "==2.14.0"
+        assert 'pytest' in lock['default']
+        assert lock['default']['pytest']['version'] == "==3.1.0"
+
+        with open(p.pipfile_path, 'w') as f:
+            updated_contents = """
+[packages]
+requests = {version = "==2.18.4"}
+PyTest = "*"
+            """.strip()
+            f.write(updated_contents)
+
+        c = p.pipenv('lock --keep-outdated')
+        assert c.return_code == 0
+        lock = p.lockfile
+        assert 'requests' in lock['default']
+        assert lock['default']['requests']['version'] == "==2.18.4"
+        assert 'pytest' in lock['default']
+        assert lock['default']['pytest']['version'] == "==3.1.0"
+
+
 @pytest.mark.lock
 @pytest.mark.complex
 @pytest.mark.needs_internet