Repository: pypa/pip

Issue:
pip fails to install packages with local versions.
`pip install apache_libcloud-0.16.0+clusterhq.0-py2.py3-none-any.whl` gives

``` python-traceback
Exception:
Traceback (most recent call last):
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/basecommand.py", line 210, in main
    status = self.run(options, args)
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/commands/install.py", line 304, in run
    name, None, isolated=options.isolated_mode,
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/req/req_install.py", line 179, in from_line
    isolated=isolated)
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/req/req_install.py", line 53, in __init__
    req = pkg_resources.Requirement.parse(req)
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/_vendor/pkg_resources.py", line 2842, in parse
    reqs = list(parse_requirements(s))
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/_vendor/pkg_resources.py", line 2789, in parse_requirements
    "version spec")
  File "/Users/cougar/dev/tmp-83d539b3e47fa7c3/lib/python2.7/site-packages/pip/_vendor/pkg_resources.py", line 2765, in scan_list
    raise ValueError(msg, line, "at", line[p:])
ValueError: ("Expected ',' or end-of-list in", 'apache-libcloud==0.16.0%2Bclusterhq.0', 'at', '%2Bclusterhq.0')
```



Discussion:
What works and what doesn't:
- **Works:** `pip install --no-index --find-links . myproject` with a tarball
- **Works:** `pip install myproject-0.0.dev0+dstufft.0.tar.gz`
- **Doesn't Work:** `pip install --no-index --find-links . myproect` with a Wheel.
- **Doesn't Work:** `pip install myproject-0.0.dev0+dstufft.0-py2.py3-none-any.whl`
- **Doesn't Work:** `pip install --no-index --find-links . myproject==0.0.dev0+dstufft.0` with either.
- **Works:** `pip install --no-index --find-links . myproject==0.0.dev0` with a tarball.
  *\* Doesn't Work:*\* `pip install --no-index --find-links . myproject==0.0.dev0` with a wheel.


Resolving patch:
diff --git a/pip/index.py b/pip/index.py
--- a/pip/index.py
+++ b/pip/index.py
@@ -1098,6 +1098,7 @@ def __hash__(self):
     def filename(self):
         _, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
         name = posixpath.basename(path.rstrip('/')) or netloc
+        name = urllib_parse.unquote(name)
         assert name, ('URL %r produced no filename' % self.url)
         return name
 


Test patch:
diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py
--- a/tests/unit/test_index.py
+++ b/tests/unit/test_index.py
@@ -58,9 +58,19 @@ class TestLink(object):
     def test_splitext(self):
         assert ('wheel', '.whl') == Link('http://yo/wheel.whl').splitext()
 
-    def test_filename(self):
-        assert 'wheel.whl' == Link('http://yo/wheel.whl').filename
-        assert 'wheel' == Link('http://yo/wheel').filename
+    @pytest.mark.parametrize(
+        ("url", "expected"),
+        [
+            ("http://yo/wheel.whl", "wheel.whl"),
+            ("http://yo/wheel", "wheel"),
+            (
+                "http://yo/myproject-1.0%2Bfoobar.0-py2.py3-none-any.whl",
+                "myproject-1.0+foobar.0-py2.py3-none-any.whl",
+            ),
+        ],
+    )
+    def test_filename(self, url, expected):
+        assert Link(url).filename == expected
 
     def test_no_ext(self):
         assert '' == Link('http://yo/wheel').ext