1from __future__ import annotations
2
3from .mixins import ImmutableDictMixin
4from .mixins import UpdateDictMixin
5
6
7def cache_control_property(key, empty, type):
8 """Return a new property object for a cache header. Useful if you
9 want to add support for a cache extension in a subclass.
10
11 .. versionchanged:: 2.0
12 Renamed from ``cache_property``.
13 """
14 return property(
15 lambda x: x._get_cache_value(key, empty, type),
16 lambda x, v: x._set_cache_value(key, v, type),
17 lambda x: x._del_cache_value(key),
18 f"accessor for {key!r}",
19 )
20
21
22class _CacheControl(UpdateDictMixin, dict):
23 """Subclass of a dict that stores values for a Cache-Control header. It
24 has accessors for all the cache-control directives specified in RFC 2616.
25 The class does not differentiate between request and response directives.
26
27 Because the cache-control directives in the HTTP header use dashes the
28 python descriptors use underscores for that.
29
30 To get a header of the :class:`CacheControl` object again you can convert
31 the object into a string or call the :meth:`to_header` method. If you plan
32 to subclass it and add your own items have a look at the sourcecode for
33 that class.
34
35 .. versionchanged:: 2.1.0
36 Setting int properties such as ``max_age`` will convert the
37 value to an int.
38
39 .. versionchanged:: 0.4
40
41 Setting `no_cache` or `private` to boolean `True` will set the implicit
42 none-value which is ``*``:
43
44 >>> cc = ResponseCacheControl()
45 >>> cc.no_cache = True
46 >>> cc
47 <ResponseCacheControl 'no-cache'>
48 >>> cc.no_cache
49 '*'
50 >>> cc.no_cache = None
51 >>> cc
52 <ResponseCacheControl ''>
53
54 In versions before 0.5 the behavior documented here affected the now
55 no longer existing `CacheControl` class.
56 """
57
58 no_cache = cache_control_property("no-cache", "*", None)
59 no_store = cache_control_property("no-store", None, bool)
60 max_age = cache_control_property("max-age", -1, int)
61 no_transform = cache_control_property("no-transform", None, None)
62
63 def __init__(self, values=(), on_update=None):
64 dict.__init__(self, values or ())
65 self.on_update = on_update
66 self.provided = values is not None
67
68 def _get_cache_value(self, key, empty, type):
69 """Used internally by the accessor properties."""
70 if type is bool:
71 return key in self
72 if key in self:
73 value = self[key]
74 if value is None:
75 return empty
76 elif type is not None:
77 try:
78 value = type(value)
79 except ValueError:
80 pass
81 return value
82 return None
83
84 def _set_cache_value(self, key, value, type):
85 """Used internally by the accessor properties."""
86 if type is bool:
87 if value:
88 self[key] = None
89 else:
90 self.pop(key, None)
91 else:
92 if value is None:
93 self.pop(key, None)
94 elif value is True:
95 self[key] = None
96 else:
97 if type is not None:
98 self[key] = type(value)
99 else:
100 self[key] = value
101
102 def _del_cache_value(self, key):
103 """Used internally by the accessor properties."""
104 if key in self:
105 del self[key]
106
107 def to_header(self):
108 """Convert the stored values into a cache control header."""
109 return http.dump_header(self)
110
111 def __str__(self):
112 return self.to_header()
113
114 def __repr__(self):
115 kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items()))
116 return f"<{type(self).__name__} {kv_str}>"
117
118 cache_property = staticmethod(cache_control_property)
119
120
121class RequestCacheControl(ImmutableDictMixin, _CacheControl):
122 """A cache control for requests. This is immutable and gives access
123 to all the request-relevant cache control headers.
124
125 To get a header of the :class:`RequestCacheControl` object again you can
126 convert the object into a string or call the :meth:`to_header` method. If
127 you plan to subclass it and add your own items have a look at the sourcecode
128 for that class.
129
130 .. versionchanged:: 2.1.0
131 Setting int properties such as ``max_age`` will convert the
132 value to an int.
133
134 .. versionadded:: 0.5
135 In previous versions a `CacheControl` class existed that was used
136 both for request and response.
137 """
138
139 max_stale = cache_control_property("max-stale", "*", int)
140 min_fresh = cache_control_property("min-fresh", "*", int)
141 only_if_cached = cache_control_property("only-if-cached", None, bool)
142
143
144class ResponseCacheControl(_CacheControl):
145 """A cache control for responses. Unlike :class:`RequestCacheControl`
146 this is mutable and gives access to response-relevant cache control
147 headers.
148
149 To get a header of the :class:`ResponseCacheControl` object again you can
150 convert the object into a string or call the :meth:`to_header` method. If
151 you plan to subclass it and add your own items have a look at the sourcecode
152 for that class.
153
154 .. versionchanged:: 2.1.1
155 ``s_maxage`` converts the value to an int.
156
157 .. versionchanged:: 2.1.0
158 Setting int properties such as ``max_age`` will convert the
159 value to an int.
160
161 .. versionadded:: 0.5
162 In previous versions a `CacheControl` class existed that was used
163 both for request and response.
164 """
165
166 public = cache_control_property("public", None, bool)
167 private = cache_control_property("private", "*", None)
168 must_revalidate = cache_control_property("must-revalidate", None, bool)
169 proxy_revalidate = cache_control_property("proxy-revalidate", None, bool)
170 s_maxage = cache_control_property("s-maxage", None, int)
171 immutable = cache_control_property("immutable", None, bool)
172
173
174# circular dependencies
175from .. import http