Repository: pypa/pip

Issue:
meta-package causes IndexError when converted to, then installed from, a .whl file
I ran across this with the [celery-with-redis](https://pypi.python.org/pypi/celery-with-redis) package, which consistens of a setup.py with an `install_requires` clause. 
Using `pip wheel`, the whl file is generated just fine just fine, but when the wheel file istelf is used for installation as a dependency I get the following exception:

```
Traceback (most recent call last):
  File "venv/src/pip/pip/basecommand.py", line 134, in main
    status = self.run(options, args)
  File "venv/src/pip/pip/commands/wheel.py", line 151, in run
    wb.build()
  File "venv/src/pip/pip/wheel.py", line 293, in build
    self.requirement_set.prepare_files(self.finder)
  File "venv/src/pip/pip/req.py", line 1105, in prepare_files
    dist = list(pkg_resources.find_distributions(location))[0]
IndexError: list index out of range
```

Where the `pkg_resources.find_distributions(location)`  returns a generator 
that yields 0 items.



Discussion:
cc @dholth 

This is a better described duplicate of another issue. The reason is that pip's unarchiver "flattens" the archive by removing lone top-level directories, which in this case is the .dist-info directory and is wrong. The code that unzips the wheel should detect that it is handling a wheel and not flatten.


Resolving patch:
diff --git a/pip/util.py b/pip/util.py
--- a/pip/util.py
+++ b/pip/util.py
@@ -589,8 +589,9 @@ def unpack_file(filename, location, content_type, link):
     if (content_type == 'application/zip'
         or filename.endswith('.zip')
         or filename.endswith('.pybundle')
+        or filename.endswith('.whl')
         or zipfile.is_zipfile(filename)):
-        unzip_file(filename, location, flatten=not filename.endswith('.pybundle'))
+        unzip_file(filename, location, flatten=not filename.endswith(('.pybundle', 'whl')))
     elif (content_type == 'application/x-gzip'
           or tarfile.is_tarfile(filename)
           or splitext(filename)[1].lower() in ('.tar', '.tar.gz', '.tar.bz2', '.tgz', '.tbz')):


Test patch:
diff --git a/tests/data/packages/README.txt b/tests/data/packages/README.txt
--- a/tests/data/packages/README.txt
+++ b/tests/data/packages/README.txt
@@ -79,8 +79,9 @@ Upper-[12].0.tar.gz and requiresuppper-1.0.tar.gz
 'requiresupper' requires 'upper'
 used for testing case mismatch case for url requirements
 
-
-
+meta-1.0-py2.py3-none-any.whl
+--------------------------------------------------
+Is an empty package which install_requires the simple and simple2 packages.
 
 
 
diff --git a/tests/data/packages/meta-1.0-py2.py3-none-any.whl b/tests/data/packages/meta-1.0-py2.py3-none-any.whl
new file mode 100644
Binary files /dev/null and b/tests/data/packages/meta-1.0-py2.py3-none-any.whl differ
diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py
--- a/tests/unit/test_wheel.py
+++ b/tests/unit/test_wheel.py
@@ -137,3 +137,20 @@ def test_support_index_min_none(self):
         w = wheel.Wheel('simple-0.1-py2-none-any.whl')
         assert w.support_index_min() == None
 
+    def test_unpack_wheel_no_flatten(self):
+        from pip import util
+        from tempfile import mkdtemp
+        from shutil import rmtree
+        import os
+        from nose import SkipTest
+
+        filepath = '../data/packages/meta-1.0-py2.py3-none-any.whl'
+        if not os.path.exists(filepath):
+            raise SkipTest
+        try:
+            tmpdir = mkdtemp()
+            util.unpack_file(filepath, tmpdir, 'application/zip', None )
+            assert os.path.isdir(os.path.join(tmpdir,'meta-1.0.dist-info'))
+        finally:
+            rmtree(tmpdir)
+            pass