comparison env/lib/python3.9/site-packages/cachecontrol/heuristics.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import calendar
2 import time
3
4 from email.utils import formatdate, parsedate, parsedate_tz
5
6 from datetime import datetime, timedelta
7
8 TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT"
9
10
11 def expire_after(delta, date=None):
12 date = date or datetime.now()
13 return date + delta
14
15
16 def datetime_to_header(dt):
17 return formatdate(calendar.timegm(dt.timetuple()))
18
19
20 class BaseHeuristic(object):
21
22 def warning(self, response):
23 """
24 Return a valid 1xx warning header value describing the cache
25 adjustments.
26
27 The response is provided too allow warnings like 113
28 http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need
29 to explicitly say response is over 24 hours old.
30 """
31 return '110 - "Response is Stale"'
32
33 def update_headers(self, response):
34 """Update the response headers with any new headers.
35
36 NOTE: This SHOULD always include some Warning header to
37 signify that the response was cached by the client, not
38 by way of the provided headers.
39 """
40 return {}
41
42 def apply(self, response):
43 updated_headers = self.update_headers(response)
44
45 if updated_headers:
46 response.headers.update(updated_headers)
47 warning_header_value = self.warning(response)
48 if warning_header_value is not None:
49 response.headers.update({'Warning': warning_header_value})
50
51 return response
52
53
54 class OneDayCache(BaseHeuristic):
55 """
56 Cache the response by providing an expires 1 day in the
57 future.
58 """
59 def update_headers(self, response):
60 headers = {}
61
62 if 'expires' not in response.headers:
63 date = parsedate(response.headers['date'])
64 expires = expire_after(timedelta(days=1),
65 date=datetime(*date[:6]))
66 headers['expires'] = datetime_to_header(expires)
67 headers['cache-control'] = 'public'
68 return headers
69
70
71 class ExpiresAfter(BaseHeuristic):
72 """
73 Cache **all** requests for a defined time period.
74 """
75
76 def __init__(self, **kw):
77 self.delta = timedelta(**kw)
78
79 def update_headers(self, response):
80 expires = expire_after(self.delta)
81 return {
82 'expires': datetime_to_header(expires),
83 'cache-control': 'public',
84 }
85
86 def warning(self, response):
87 tmpl = '110 - Automatically cached for %s. Response might be stale'
88 return tmpl % self.delta
89
90
91 class LastModified(BaseHeuristic):
92 """
93 If there is no Expires header already, fall back on Last-Modified
94 using the heuristic from
95 http://tools.ietf.org/html/rfc7234#section-4.2.2
96 to calculate a reasonable value.
97
98 Firefox also does something like this per
99 https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
100 http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397
101 Unlike mozilla we limit this to 24-hr.
102 """
103 cacheable_by_default_statuses = set([
104 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501
105 ])
106
107 def update_headers(self, resp):
108 headers = resp.headers
109
110 if 'expires' in headers:
111 return {}
112
113 if 'cache-control' in headers and headers['cache-control'] != 'public':
114 return {}
115
116 if resp.status not in self.cacheable_by_default_statuses:
117 return {}
118
119 if 'date' not in headers or 'last-modified' not in headers:
120 return {}
121
122 date = calendar.timegm(parsedate_tz(headers['date']))
123 last_modified = parsedate(headers['last-modified'])
124 if date is None or last_modified is None:
125 return {}
126
127 now = time.time()
128 current_age = max(0, now - date)
129 delta = date - calendar.timegm(last_modified)
130 freshness_lifetime = max(0, min(delta / 10, 24 * 3600))
131 if freshness_lifetime <= current_age:
132 return {}
133
134 expires = date + freshness_lifetime
135 return {'expires': time.strftime(TIME_FMT, time.gmtime(expires))}
136
137 def warning(self, resp):
138 return None