1+ import hashlib
12import logging
23from datetime import datetime , timedelta , timezone
34from itertools import chain
1516 HttpResponseBadRequest ,
1617 HttpResponseForbidden ,
1718 HttpResponseNotFound ,
18- StreamingHttpResponse ,
1919)
2020from django .shortcuts import redirect
21+ from django .utils .decorators import method_decorator
22+ from django .views .decorators .cache import cache_control
23+ from django .views .decorators .http import condition
2124from drf_spectacular .utils import extend_schema
2225from dynaconf import settings
2326from packaging .utils import canonicalize_name
3134from pulpcore .plugin .viewsets import OperationPostponedResponse
3235
3336from pulp_python .app import tasks
37+ from pulp_python .app .cache import PythonApiCache , find_base_path_cached
3438from pulp_python .app .models import (
3539 PackageProvenance ,
3640 PythonDistribution ,
6569PYPI_SIMPLE_V1_JSON = "application/vnd.pypi.simple.v1+json"
6670
6771
72+ def _etag_func (request , path , ** kwargs ):
73+ """Compute unquoted ETag for the condition decorator. Returns None if no repo."""
74+ try :
75+ distro = PyPIMixin .get_distribution (path )
76+ repo_ver = PyPIMixin .get_repository_version (distro )
77+ except Http404 :
78+ return None
79+ raw = f"{ repo_ver .number } :{ repo_ver .pulp_created .isoformat ()} "
80+ return hashlib .sha256 (raw .encode ()).hexdigest ()[:16 ]
81+
82+
6883class PyPISimpleHTMLRenderer (TemplateHTMLRenderer ):
6984 media_type = PYPI_SIMPLE_V1_HTML
7085
@@ -298,6 +313,9 @@ def get_provenance_url(self, package, version, filename):
298313 )
299314
300315 @extend_schema (summary = "Get index simple page" )
316+ @method_decorator (cache_control (max_age = 600 , public = True ))
317+ @method_decorator (condition (etag_func = _etag_func ))
318+ @PythonApiCache (base_key = find_base_path_cached )
301319 def list (self , request , path ):
302320 """Gets the simple api html page for the index."""
303321 repo_version , content = self .get_rvc ()
@@ -316,9 +334,9 @@ def list(self, request, path):
316334 index_data = write_simple_index_json (names )
317335 return Response (index_data , headers = headers )
318336 else :
319- index_data = write_simple_index (names , streamed = True )
337+ index_data = write_simple_index (names )
320338 kwargs = {"content_type" : media_type , "headers" : headers }
321- return StreamingHttpResponse (index_data , ** kwargs )
339+ return HttpResponse (index_data , ** kwargs )
322340
323341 def pull_through_package_simple (self , package , path , remote ):
324342 """Gets the package's simple page from remote."""
@@ -355,6 +373,9 @@ def parse_package(release_package):
355373 }
356374
357375 @extend_schema (operation_id = "pypi_simple_package_read" , summary = "Get package simple page" )
376+ @method_decorator (cache_control (max_age = 600 , public = True ))
377+ @method_decorator (condition (etag_func = _etag_func ))
378+ @PythonApiCache (base_key = find_base_path_cached )
358379 def retrieve (self , request , path , package ):
359380 """Retrieves the simple api html/json page for a package."""
360381 repo_ver , content = self .get_rvc ()
0 commit comments