Skip to content

Commit 1030249

Browse files
authored
Add CloudFront compatible cache control decorator and update documentation (#40)
1 parent 25879ad commit 1030249

5 files changed

Lines changed: 94 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Added `vary_by_cookies()` and `vary_by_headers()` decorators for Flask
13+
- Added `cacheable_duration_cloudfront()` decorator for adding `Cache-Control` headers that are Cloudfront compatible
1314

1415
### Changed
1516

docs/flask.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ A set of decorators to manage the `Cache-Control` response header of a route.
1010

1111
```python
1212
from flask import Flask
13-
from tna_utilities.flask import cacheable_duration, do_not_cache, set_cache_control
13+
from tna_utilities.flask import (
14+
cacheable_duration,
15+
do_not_cache,
16+
set_cache_control,
17+
cacheable_duration_cloudfront
18+
)
1419

1520
app = Flask(__name__)
1621

@@ -32,6 +37,11 @@ def not_cachable():
3237
@set_cache_control("private, max-age=120")
3338
def custom_cache():
3439
return "Cache me in private caches for up to 2 minutes"
40+
41+
@app.route("/cloudfront-cache/")
42+
@cacheable_duration_cloudfront(3600, 86400)
43+
def cloudfront_cache():
44+
return "Cache me in client caches for up to an hour and in Cloudfront for up to a day"
3545
```
3646

3747
### Vary

tests/test_flask_cache_control.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from tna_utilities.flask import (
66
cacheable_duration,
7+
cacheable_duration_cloudfront,
78
do_not_cache,
89
set_cache_control,
910
vary_by_cookies,
@@ -115,3 +116,59 @@ def index():
115116
rv.headers["Vary"],
116117
"Accept-Encoding, User-Agent",
117118
)
119+
120+
def test_cache_control_and_vary_by_headers_route(self):
121+
@self.app.route("/")
122+
@set_cache_control("private, max-age=120")
123+
@vary_by_headers("Accept-Encoding, User-Agent")
124+
def index():
125+
return "OK"
126+
127+
rv = self.test_client.get("/")
128+
129+
self.assertEqual(rv.status_code, 200)
130+
self.assertIn("Vary", rv.headers)
131+
self.assertEqual(
132+
rv.headers["Vary"],
133+
"Accept-Encoding, User-Agent",
134+
)
135+
self.assertIn("Cache-Control", rv.headers)
136+
self.assertEqual(
137+
rv.headers["Cache-Control"],
138+
"private, max-age=120",
139+
)
140+
141+
def test_cacheable_duration_cloudfront_route(self):
142+
@self.app.route("/")
143+
@cacheable_duration_cloudfront(client_seconds=60, cloudfront_seconds=120)
144+
def index():
145+
return "OK"
146+
147+
rv = self.test_client.get("/")
148+
149+
self.assertEqual(rv.status_code, 200)
150+
self.assertIn("Cache-Control", rv.headers)
151+
self.assertEqual(
152+
rv.headers["Cache-Control"],
153+
"public, max-age=60, s-maxage=120",
154+
)
155+
156+
def test_cacheable_duration_cloudfront_route_extras(self):
157+
@self.app.route("/")
158+
@cacheable_duration_cloudfront(
159+
client_seconds=60,
160+
cloudfront_seconds=120,
161+
stale_while_revalidate_seconds=30,
162+
stale_if_error_seconds=15,
163+
)
164+
def index():
165+
return "OK"
166+
167+
rv = self.test_client.get("/")
168+
169+
self.assertEqual(rv.status_code, 200)
170+
self.assertIn("Cache-Control", rv.headers)
171+
self.assertEqual(
172+
rv.headers["Cache-Control"],
173+
"public, max-age=60, s-maxage=120, stale-while-revalidate=30, stale-if-error=15",
174+
)

tna_utilities/flask/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from tna_utilities.flask.cache_control import (
22
cacheable_duration,
3+
cacheable_duration_cloudfront,
34
do_not_cache,
45
set_cache_control,
56
vary_by_cookies,

tna_utilities/flask/cache_control.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ def cacheable_duration(seconds: int = 3600):
1919
return set_cache_control(f"public, max-age={seconds}")
2020

2121

22+
def cacheable_duration_cloudfront(
23+
client_seconds: int = 3600,
24+
cloudfront_seconds: int = 3600,
25+
stale_while_revalidate_seconds: int = 0,
26+
stale_if_error_seconds: int = 0,
27+
):
28+
"""
29+
Decorator to set Cache-Control headers to allow caching of the response for a specified duration with consideration for CloudFront's caching behavior.
30+
See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html for details on CloudFront's caching behavior.
31+
"""
32+
33+
cache_control_value = f"public, max-age={client_seconds}"
34+
if cloudfront_seconds > 0 and cloudfront_seconds != client_seconds:
35+
cache_control_value += f", s-maxage={cloudfront_seconds}"
36+
if stale_while_revalidate_seconds > 0:
37+
cache_control_value += (
38+
f", stale-while-revalidate={stale_while_revalidate_seconds}"
39+
)
40+
if stale_if_error_seconds > 0:
41+
cache_control_value += f", stale-if-error={stale_if_error_seconds}"
42+
43+
return set_cache_control(cache_control_value)
44+
45+
2246
def set_cache_control(instructions: str):
2347
"""
2448
Decorator to set Cache-Control headers with custom instructions provided as a string.

0 commit comments

Comments
 (0)