Repository: pypi/warehouse

Issue:
Warehouse file order differs from legacy PyPI file list
Tonight, while load testing of pypi.org was ongoing, we saw some failures in automated systems that use `--require-hashes` with `pip install`, as ordering on the package file list page changed.

The specific package we saw break was `pandas` at version `0.12.0`. We had a single hash for `pandas-0.12.0.tar.gz`. A few of our hosts were served from the legacy PyPI service, which succeeded as normal. The Warehouse endpoint, however, failed, since `pandas-0.12.0.zip` now preceded `pandas-0.12.0.tar.gz` in the file list.

At the moment, you can see that https://pypi.org/simple/pandas/ and https://pypi.python.org/simple/pandas/ differ by searching for `pandas-0.12.0.tar.gz` and `pandas-0.12.0.zip` and comparing the position.


Discussion:
Could you tell us what version of `pip` was used, and if you were using an intermediary or mirrored index? The order of the distributions shouldn't cause an issue.
We were using `pip` version 9.0.1 with no explicit index specified, so the default `https://pypi.python.org/simple`.

I can reproduce this currently with a local Python 2.7 install:
```
$ pip --version
pip 9.0.1
```
```
$ pip install --index-url https://pypi.python.org/simple pandas==0.12.0
Collecting pandas==0.12.0
  Downloading pandas-0.12.0.tar.gz (3.2MB)
```
```
$ pip install --index-url https://pypi.org/simple pandas==0.12.0
Collecting pandas==0.12.0
  Downloading https://files.pythonhosted.org/packages/49/32/61120c7350105faa1533211fe3226ca6e71cd08a50e6e137e0baee50d186/pandas-0.12.0.zip (3.4MB)
```

From a cursory look at [_link_package_versions()](https://github.com/pypa/pip/blob/9.0.1/pip/index.py#L605) it seems that the ordering between `.zip` and `.tar.gz` ultimately does come down to the order on the links page.
Yea, pip respects the order files are given to it by the index as the final tie breaker when it sorts files, so the order in ``/simple/`` can change what file gets installed.

Resolving patch:
diff --git a/warehouse/legacy/api/simple.py b/warehouse/legacy/api/simple.py
--- a/warehouse/legacy/api/simple.py
+++ b/warehouse/legacy/api/simple.py
@@ -87,7 +87,7 @@ def simple_detail(project, request):
             )
         )
         .all(),
-        key=lambda f: (parse(f.version), f.packagetype)
+        key=lambda f: (parse(f.version), f.filename)
     )
 
     return {"project": project, "files": files}


Test patch:
diff --git a/tests/unit/legacy/api/test_simple.py b/tests/unit/legacy/api/test_simple.py
--- a/tests/unit/legacy/api/test_simple.py
+++ b/tests/unit/legacy/api/test_simple.py
@@ -202,7 +202,7 @@ def test_with_files_with_version_multi_digit(self, db_request):
 
         files = []
         for files_release in \
-                zip(egg_files, wheel_files, tar_files):
+                zip(egg_files, tar_files, wheel_files):
             files += files_release
 
         db_request.matchdict["name"] = project.normalized_name
@@ -212,9 +212,6 @@ def test_with_files_with_version_multi_digit(self, db_request):
         # Make sure that we get any changes made since the JournalEntry was
         # saved.
         db_request.db.refresh(project)
-        import pprint
-        pprint.pprint(simple.simple_detail(project, db_request)['files'])
-        pprint.pprint(files)
 
         assert simple.simple_detail(project, db_request) == {
             "project": project,