comparison env/lib/python3.9/site-packages/pip/_vendor/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.utcnow()
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
60 def update_headers(self, response):
61 headers = {}
62
63 if "expires" not in response.headers:
64 date = parsedate(response.headers["date"])
65 expires = expire_after(timedelta(days=1), 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 {"expires": datetime_to_header(expires), "cache-control": "public"}
82
83 def warning(self, response):
84 tmpl = "110 - Automatically cached for %s. Response might be stale"
85 return tmpl % self.delta
86
87
88 class LastModified(BaseHeuristic):
89 """
90 If there is no Expires header already, fall back on Last-Modified
91 using the heuristic from
92 http://tools.ietf.org/html/rfc7234#section-4.2.2
93 to calculate a reasonable value.
94
95 Firefox also does something like this per
96 https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
97 http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397
98 Unlike mozilla we limit this to 24-hr.
99 """
100 cacheable_by_default_statuses = {
101 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501
102 }
103
104 def update_headers(self, resp):
105 headers = resp.headers
106
107 if "expires" in headers:
108 return {}
109
110 if "cache-control" in headers and headers["cache-control"] != "public":
111 return {}
112
113 if resp.status not in self.cacheable_by_default_statuses:
114 return {}
115
116 if "date" not in headers or "last-modified" not in headers:
117 return {}
118
119 date = calendar.timegm(parsedate_tz(headers["date"]))
120 last_modified = parsedate(headers["last-modified"])
121 if date is None or last_modified is None:
122 return {}
123
124 now = time.time()
125 current_age = max(0, now - date)
126 delta = date - calendar.timegm(last_modified)
127 freshness_lifetime = max(0, min(delta / 10, 24 * 3600))
128 if freshness_lifetime <= current_age:
129 return {}
130
131 expires = date + freshness_lifetime
132 return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))}
133
134 def warning(self, resp):
135 return None