33
44from aiohttp .client_exceptions import ClientError
55from rest_framework .viewsets import ViewSet
6+ from rest_framework .renderers import JSONRenderer , TemplateHTMLRenderer
67from rest_framework .response import Response
78from django .core .exceptions import ObjectDoesNotExist
89from django .shortcuts import redirect
1718 HttpResponseBadRequest ,
1819 StreamingHttpResponse ,
1920 HttpResponse ,
21+ JsonResponse ,
2022)
2123from drf_spectacular .utils import extend_schema
2224from dynaconf import settings
4345)
4446from pulp_python .app .utils import (
4547 write_simple_index ,
48+ write_simple_index_json ,
4649 write_simple_detail ,
50+ write_simple_detail_json ,
4751 python_content_to_json ,
4852 PYPI_LAST_SERIAL ,
4953 PYPI_SERIAL_CONSTANT ,
5761ORIGIN_HOST = settings .CONTENT_ORIGIN if settings .CONTENT_ORIGIN else settings .PYPI_API_HOSTNAME
5862BASE_CONTENT_URL = urljoin (ORIGIN_HOST , settings .CONTENT_PATH_PREFIX )
5963
64+ PYPI_TEXT_HTML = "text/html"
65+ PYPI_SIMPLE_V1_HTML = "application/vnd.pypi.simple.v1+html"
66+ PYPI_SIMPLE_V1_JSON = "application/vnd.pypi.simple.v1+json"
67+
68+
69+ class PyPISimpleHTMLRenderer (TemplateHTMLRenderer ):
70+ media_type = PYPI_SIMPLE_V1_HTML
71+
72+
73+ class PyPISimpleJSONRenderer (JSONRenderer ):
74+ media_type = PYPI_SIMPLE_V1_JSON
75+
76+
77+ def _select_content_type (request ):
78+ """Select content type based on Accept header."""
79+ accept_header = request .META .get ("HTTP_ACCEPT" , "" )
80+
81+ for content_type in (PYPI_TEXT_HTML , PYPI_SIMPLE_V1_HTML , PYPI_SIMPLE_V1_JSON ):
82+ if content_type in accept_header :
83+ return content_type
84+ if not accept_header :
85+ return PYPI_TEXT_HTML
86+ return None
87+
6088
6189class PyPIMixin :
6290 """Mixin to get index specific info."""
@@ -235,14 +263,32 @@ class SimpleView(PackageUploadMixin, ViewSet):
235263 ],
236264 }
237265
266+ renderer_classes = [
267+ TemplateHTMLRenderer ,
268+ PyPISimpleHTMLRenderer ,
269+ PyPISimpleJSONRenderer ,
270+ ]
271+
238272 @extend_schema (summary = "Get index simple page" )
239273 def list (self , request , path ):
240274 """Gets the simple api html page for the index."""
275+ content_type = _select_content_type (request )
276+ if content_type is None :
277+ return HttpResponse ("Not Acceptable Content-Type" , status = 406 )
278+
241279 repo_version , content = self .get_rvc ()
242280 if self .should_redirect (repo_version = repo_version ):
243281 return redirect (urljoin (self .base_content_url , f"{ path } /simple/" ))
244282 names = content .order_by ("name" ).values_list ("name" , flat = True ).distinct ().iterator ()
245- return StreamingHttpResponse (write_simple_index (names , streamed = True ))
283+
284+ if content_type == PYPI_SIMPLE_V1_JSON :
285+ names_list = list (names )
286+ data_dict = write_simple_index_json (names_list )
287+ headers = {"X-PyPI-Last-Serial" : str (PYPI_SERIAL_CONSTANT )}
288+ response = JsonResponse (data_dict , content_type = content_type , headers = headers )
289+ return response
290+ else :
291+ return StreamingHttpResponse (write_simple_index (names , streamed = True ))
246292
247293 def pull_through_package_simple (self , package , path , remote ):
248294 """Gets the package's simple page from remote."""
@@ -281,6 +327,10 @@ def parse_package(release_package):
281327 @extend_schema (operation_id = "pypi_simple_package_read" , summary = "Get package simple page" )
282328 def retrieve (self , request , path , package ):
283329 """Retrieves the simple api html page for a package."""
330+ content_type = _select_content_type (request )
331+ if content_type is None :
332+ return HttpResponse ("Not Acceptable Content-Type" , status = 406 )
333+
284334 repo_ver , content = self .get_rvc ()
285335 # Should I redirect if the normalized name is different?
286336 normalized = canonicalize_name (package )
@@ -301,7 +351,15 @@ def retrieve(self, request, path, package):
301351 packages = chain ([present ], packages )
302352 name = present [2 ]
303353 releases = ((f , urljoin (self .base_content_url , f"{ path } /{ f } " ), d ) for f , d , _ in packages )
304- return StreamingHttpResponse (write_simple_detail (name , releases , streamed = True ))
354+
355+ if content_type == PYPI_SIMPLE_V1_JSON :
356+ releases_list = list (releases )
357+ data_dict = write_simple_detail_json (name , releases_list )
358+ headers = {"X-PyPI-Last-Serial" : str (PYPI_SERIAL_CONSTANT )}
359+ response = JsonResponse (data_dict , content_type = content_type , headers = headers )
360+ return response
361+ else :
362+ return StreamingHttpResponse (write_simple_detail (name , releases , streamed = True ))
305363
306364 @extend_schema (
307365 request = PackageUploadSerializer ,
0 commit comments