Repository: pypa/bandersnatch (python)

Issue:
Ensure bandersnatch implements pep700 fields
https://peps.python.org/pep-0700/

- The api-version must specify version 1.1 or later.
- A new versions key is added at the top level.
- Two new “file information” keys, size and upload-time, are added to the files data.

Keys (at any level) with a leading underscore are reserved as private for index server use. No future standard will assign a meaning to any such key.

Pull request description:
Add in PEP700 simple API v1.1 support
- Move simple module to generate API 1.1 (JSON changes only)
  - Since there is only additions we don't really break 1.0 so just moving to 1.1
  - Seems pypi did this too
  - PEP700: https://peps.python.org/pep-0700/

Tests:
- Fix tests for v1.1 new fields based on our mocked metadata

Fixes #1482

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 0776235..fb6e904 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,6 @@
 # 6.4.0 (Unreleased)
 
+- Move JSON Simple API to version 1.1 (as per PEP700) `PR #1557`
 - Move to >= 3.10 project `PR #1457`
 
 ## Bug Fixes
diff --git a/src/bandersnatch/default.conf b/src/bandersnatch/default.conf
index ef7794f..510c89a 100644
--- a/src/bandersnatch/default.conf
+++ b/src/bandersnatch/default.conf
@@ -1,6 +1,7 @@
 [mirror]
 ; The directory where the mirror data will be stored.
 directory = /srv/pypi
+
 ; Save JSON metadata into the web tree:
 ; URL/pypi/PKG_NAME/json (Symlink) -> URL/json/PKG_NAME
 json = false
diff --git a/src/bandersnatch/simple.py b/src/bandersnatch/simple.py
index 3cf4619..c657eed 100644
--- a/src/bandersnatch/simple.py
+++ b/src/bandersnatch/simple.py
@@ -81,8 +81,9 @@ class SimpleAPI:
 
     # PEP620 Simple API Version
     pypi_repository_version = "1.0"
-    # PEP691 Simple API Version
-    pypi_simple_api_version = "1.0"
+    # PEP691 Simple API Version 1.0
+    # PEP700 defines 1.1
+    pypi_simple_api_version = "1.1"
 
     def __init__(
         self,
@@ -215,6 +216,8 @@ class SimpleAPI:
                 "_last-serial": str(package.last_serial),
             },
             "name": package.name,
+            # TODO: Just sorting by default sort - Maybe specify order in future PEP
+            "versions": sorted(package.releases.keys()),
         }
 
         release_files = package.release_files
@@ -229,6 +232,8 @@ class SimpleAPI:
                         self.digest_name: r["digests"][self.digest_name],
                     },
                     "requires-python": r.get("requires_python", ""),
+                    "size": r["size"],
+                    "upload-time": r.get("upload_time_iso_8601", ""),
                     "url": self._file_url_to_local_url(r["url"]),
                     "yanked": r.get("yanked", False),
                 }
@@ -263,7 +268,10 @@ class SimpleAPI:
         simple_json_path = simple_dir / "index.v1_json"
 
         simple_json: dict[str, Any] = {
-            "meta": {"_last-serial": serial, "api-version": "1.0"},
+            "meta": {
+                "_last-serial": serial,
+                "api-version": self.pypi_simple_api_version,
+            },
             "projects": [],
         }
 


Test patch:
diff --git a/src/bandersnatch/tests/test_simple.py b/src/bandersnatch/tests/test_simple.py
index 9972530..039ee81 100644
--- a/src/bandersnatch/tests/test_simple.py
+++ b/src/bandersnatch/tests/test_simple.py
@@ -18,8 +18,8 @@ from bandersnatch.simple import (
 from bandersnatch.storage import Storage
 from bandersnatch.tests.test_simple_fixtures import (
     EXPECTED_SIMPLE_GLOBAL_JSON_PRETTY,
-    EXPECTED_SIMPLE_SIXTYNINE_JSON,
-    EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY,
+    EXPECTED_SIMPLE_SIXTYNINE_JSON_1_1,
+    EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY_1_1,
     SIXTYNINE_METADATA,
 )
 from bandersnatch_storage_plugins.filesystem import FilesystemStorage
@@ -58,9 +58,9 @@ def test_json_package_page() -> None:
     s = SimpleAPI(Storage(), SimpleFormat.JSON, [], SimpleDigest.SHA256, False, None)
     p = Package("69")
     p._metadata = SIXTYNINE_METADATA
-    assert EXPECTED_SIMPLE_SIXTYNINE_JSON == s.generate_json_simple_page(p)
+    assert EXPECTED_SIMPLE_SIXTYNINE_JSON_1_1 == s.generate_json_simple_page(p)
     # Only testing pretty so it's easier for humans ...
-    assert EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY == s.generate_json_simple_page(
+    assert EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY_1_1 == s.generate_json_simple_page(
         p, pretty=True
     )
 
diff --git a/src/bandersnatch/tests/test_simple_fixtures.py b/src/bandersnatch/tests/test_simple_fixtures.py
index bc7058c..139fe7b 100644
--- a/src/bandersnatch/tests/test_simple_fixtures.py
+++ b/src/bandersnatch/tests/test_simple_fixtures.py
@@ -108,11 +108,11 @@ SIXTYNINE_METADATA = {
     "vulnerabilities": [],
 }
 
-EXPECTED_SIMPLE_SIXTYNINE_JSON = """\
-{"files": [{"filename": "69-0.69.tar.gz", "hashes": {"sha256": "5c11f48399f9b1bca802751513f1f97bff6ce97e6facb576b7729e1351453c10"}, "requires-python": ">=3.6", "url": "../../packages/d3/cc/95dc5434362bd333a1fec275231775d748315b26edf1e7e568e6f8660238/69-0.69.tar.gz", "yanked": false}, {"filename": "69-6.9.tar.gz", "hashes": {"sha256": "0c8deb7c8574787283c3fc08b714ee63fd6752a38d13515a9d8508798d428597"}, "requires-python": ">=3.6", "url": "../../packages/7b/6e/7c4ce77c6ca092e94e19b78282b459e7f8270362da655cbc6a75eeb9cdd7/69-6.9.tar.gz", "yanked": false}], "meta": {"api-version": "1.0", "_last-serial": "10333928"}, "name": "69"}\
+EXPECTED_SIMPLE_SIXTYNINE_JSON_1_1 = """\
+{"files": [{"filename": "69-0.69.tar.gz", "hashes": {"sha256": "5c11f48399f9b1bca802751513f1f97bff6ce97e6facb576b7729e1351453c10"}, "requires-python": ">=3.6", "size": 1078, "upload-time": "2018-05-17T03:37:19.330556Z", "url": "../../packages/d3/cc/95dc5434362bd333a1fec275231775d748315b26edf1e7e568e6f8660238/69-0.69.tar.gz", "yanked": false}, {"filename": "69-6.9.tar.gz", "hashes": {"sha256": "0c8deb7c8574787283c3fc08b714ee63fd6752a38d13515a9d8508798d428597"}, "requires-python": ">=3.6", "size": 1077, "upload-time": "2018-05-17T03:47:45.953704Z", "url": "../../packages/7b/6e/7c4ce77c6ca092e94e19b78282b459e7f8270362da655cbc6a75eeb9cdd7/69-6.9.tar.gz", "yanked": false}], "meta": {"api-version": "1.1", "_last-serial": "10333928"}, "name": "69", "versions": ["0.69", "6.9"]}\
 """
 
-EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY = """\
+EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY_1_1 = """\
 {
     "files": [
         {
@@ -121,6 +121,8 @@ EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY = """\
                 "sha256": "5c11f48399f9b1bca802751513f1f97bff6ce97e6facb576b7729e1351453c10"
             },
             "requires-python": ">=3.6",
+            "size": 1078,
+            "upload-time": "2018-05-17T03:37:19.330556Z",
             "url": "../../packages/d3/cc/95dc5434362bd333a1fec275231775d748315b26edf1e7e568e6f8660238/69-0.69.tar.gz",
             "yanked": false
         },
@@ -130,15 +132,21 @@ EXPECTED_SIMPLE_SIXTYNINE_JSON_PRETTY = """\
                 "sha256": "0c8deb7c8574787283c3fc08b714ee63fd6752a38d13515a9d8508798d428597"
             },
             "requires-python": ">=3.6",
+            "size": 1077,
+            "upload-time": "2018-05-17T03:47:45.953704Z",
             "url": "../../packages/7b/6e/7c4ce77c6ca092e94e19b78282b459e7f8270362da655cbc6a75eeb9cdd7/69-6.9.tar.gz",
             "yanked": false
         }
     ],
     "meta": {
-        "api-version": "1.0",
+        "api-version": "1.1",
         "_last-serial": "10333928"
     },
-    "name": "69"
+    "name": "69",
+    "versions": [
+        "0.69",
+        "6.9"
+    ]
 }\
 """
 
@@ -146,7 +154,7 @@ EXPECTED_SIMPLE_GLOBAL_JSON_PRETTY = """\
 {
     "meta": {
         "_last-serial": 12345,
-        "api-version": "1.0"
+        "api-version": "1.1"
     },
     "projects": [
         {