Repository: pypi/warehouse

Issue:
Expose requires_python for each release in JSON API
The following JSON:

https://pypi.python.org/pypi/astropy/json

or:

https://pypi.io/pypi/astropy/json

includes the following for the latest release:

```
        "requires_python": ">=3.5", 
```

but it would be really helpful to have this for all releases listed in the ``releases`` section, to avoid having to hit the server for each release to find out the ``requires_python``.


Discussion:
Thanks for your suggestion, and sorry for the slow response! I like this idea and am grateful to you for bringing it up.

The folks working on Warehouse have gotten [funding to concentrate on improving and deploying Warehouse](https://pyfound.blogspot.com/2017/11/the-psf-awarded-moss-grant-pypi.html), and are concentrating on work towards [our development roadmap](https://wiki.python.org/psf/WarehouseRoadmap) -- the most urgent task is to improve Warehouse to the point where we can redirect pypi.python.org to pypi.org so the site is more sustainable and reliable. Since (as far as I can gather) this feature isn't something that the legacy site has, I've moved it to a future milestone.

Thanks and sorry again for the wait.
Just noting that the current bulk API for accessing the `Requires-Python` metadata is PEP 503's `data-requires-python` link attribute: https://www.python.org/dev/peps/pep-0503/#specification

For AstroPy specifically, that appears at https://pypi.org/simple/astropy/ and https://pypi.python.org/simple/astropy/

Having a project level JSON listing *would* still be a nice improvement, but in the meantime, querying the link attributes in the PEP 503 API is preferred to querying each release individually.

Resolving patch:
diff --git a/warehouse/legacy/api/json.py b/warehouse/legacy/api/json.py
--- a/warehouse/legacy/api/json.py
+++ b/warehouse/legacy/api/json.py
@@ -138,6 +138,7 @@ def json_release(release, request):
                 "downloads": -1,
                 "upload_time": f.upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
                 "url": request.route_url("packaging.file", path=f.path),
+                "requires_python": r.requires_python if r.requires_python else None,
             }
             for f in fs
         ]


Test patch:
diff --git a/tests/unit/legacy/api/test_json.py b/tests/unit/legacy/api/test_json.py
--- a/tests/unit/legacy/api/test_json.py
+++ b/tests/unit/legacy/api/test_json.py
@@ -263,6 +263,7 @@ def test_detail_renders(self, pyramid_config, db_request, db_session):
                             "%Y-%m-%dT%H:%M:%S"
                         ),
                         "url": "/the/fake/url/",
+                        "requires_python": None,
                     }
                 ],
                 "2.0": [
@@ -283,6 +284,7 @@ def test_detail_renders(self, pyramid_config, db_request, db_session):
                             "%Y-%m-%dT%H:%M:%S"
                         ),
                         "url": "/the/fake/url/",
+                        "requires_python": None,
                     }
                 ],
                 "3.0": [
@@ -303,6 +305,7 @@ def test_detail_renders(self, pyramid_config, db_request, db_session):
                             "%Y-%m-%dT%H:%M:%S"
                         ),
                         "url": "/the/fake/url/",
+                        "requires_python": None,
                     }
                 ],
             },
@@ -322,6 +325,7 @@ def test_detail_renders(self, pyramid_config, db_request, db_session):
                     "size": 200,
                     "upload_time": files[2].upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
                     "url": "/the/fake/url/",
+                    "requires_python": None,
                 }
             ],
             "last_serial": je.id,
@@ -402,6 +406,7 @@ def test_minimal_renders(self, pyramid_config, db_request):
                         "size": 200,
                         "upload_time": file.upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
                         "url": "/the/fake/url/",
+                        "requires_python": None,
                     }
                 ]
             },
@@ -418,6 +423,7 @@ def test_minimal_renders(self, pyramid_config, db_request):
                     "size": 200,
                     "upload_time": file.upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
                     "url": "/the/fake/url/",
+                    "requires_python": None,
                 }
             ],
             "last_serial": je.id,