Skip to content

Commit 89824d1

Browse files
committed
feat: allow section-based Surrogate-Key to purge releases and other things without nukiung the whole cache
1 parent 6afea27 commit 89824d1

2 files changed

Lines changed: 73 additions & 7 deletions

File tree

pydotorg/middleware.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,45 @@ def __call__(self, request):
1818

1919
class GlobalSurrogateKey:
2020
"""
21-
Middleware to insert a Surrogate-Key for purging in Fastly or other caches
21+
Middleware to insert a Surrogate-Key for purging in Fastly or other caches.
22+
23+
Adds both a global key (for full site purges) and section-based keys
24+
derived from the URL path (for targeted purges like /downloads/).
2225
"""
2326

2427
def __init__(self, get_response):
2528
self.get_response = get_response
2629

30+
def _get_section_key(self, path):
31+
"""
32+
Extract section surrogate key from URL path.
33+
34+
Examples:
35+
/downloads/ -> downloads
36+
/downloads/release/python-3141/ -> downloads
37+
/events/python-events/ -> events
38+
/ -> None
39+
"""
40+
parts = path.strip("/").split("/")
41+
if parts and parts[0]:
42+
return parts[0]
43+
return None
44+
2745
def __call__(self, request):
2846
response = self.get_response(request)
47+
keys = []
2948
if hasattr(settings, "GLOBAL_SURROGATE_KEY"):
30-
response["Surrogate-Key"] = " ".join(
31-
filter(
32-
None, [settings.GLOBAL_SURROGATE_KEY, response.get("Surrogate-Key")]
33-
)
34-
)
49+
keys.append(settings.GLOBAL_SURROGATE_KEY)
50+
51+
section_key = self._get_section_key(request.path)
52+
if section_key:
53+
keys.append(section_key)
54+
55+
existing = response.get("Surrogate-Key")
56+
if existing:
57+
keys.append(existing)
58+
59+
if keys:
60+
response["Surrogate-Key"] = " ".join(keys)
61+
3562
return response

pydotorg/tests/test_middleware.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from django.test import TestCase
1+
from django.test import TestCase, override_settings
22

33
from django.contrib.sites.models import Site
44
from django.contrib.redirects.models import Redirect
55

6+
from pydotorg.middleware import GlobalSurrogateKey
7+
68

79
class MiddlewareTests(TestCase):
810

@@ -25,3 +27,40 @@ def test_redirects(self):
2527
response = self.client.get(url)
2628
self.assertEqual(response.status_code, 301)
2729
self.assertEqual(response['Location'], redirect.new_path)
30+
31+
32+
class GlobalSurrogateKeyTests(TestCase):
33+
34+
def test_get_section_key(self):
35+
"""Test section key extraction from URL paths."""
36+
middleware = GlobalSurrogateKey(lambda r: None)
37+
38+
self.assertEqual(middleware._get_section_key('/downloads/'), 'downloads')
39+
self.assertEqual(middleware._get_section_key('/downloads/release/python-3141/'), 'downloads')
40+
self.assertEqual(middleware._get_section_key('/events/'), 'events')
41+
self.assertEqual(middleware._get_section_key('/events/python-events/123/'), 'events')
42+
self.assertEqual(middleware._get_section_key('/sponsors/'), 'sponsors')
43+
44+
# returns None
45+
self.assertIsNone(middleware._get_section_key('/'))
46+
47+
self.assertEqual(middleware._get_section_key('/downloads'), 'downloads')
48+
self.assertEqual(middleware._get_section_key('downloads/'), 'downloads')
49+
50+
@override_settings(GLOBAL_SURROGATE_KEY='pydotorg-app')
51+
def test_surrogate_key_header_includes_section(self):
52+
"""Test that Surrogate-Key header includes both global and section keys."""
53+
response = self.client.get('/downloads/')
54+
self.assertTrue(response.has_header('Surrogate-Key'))
55+
surrogate_key = response['Surrogate-Key']
56+
57+
self.assertIn('pydotorg-app', surrogate_key)
58+
self.assertIn('downloads', surrogate_key)
59+
60+
@override_settings(GLOBAL_SURROGATE_KEY='pydotorg-app')
61+
def test_surrogate_key_header_homepage(self):
62+
"""Test that homepage only has global surrogate key."""
63+
response = self.client.get('/')
64+
self.assertTrue(response.has_header('Surrogate-Key'))
65+
surrogate_key = response['Surrogate-Key']
66+
self.assertIn('pydotorg-app', surrogate_key)

0 commit comments

Comments
 (0)