Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/datastructures/cache_control.py: 52%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

137 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import typing as t 

5from inspect import cleandoc 

6 

7from .._internal import _plain_int 

8from ..http import dump_header 

9from ..http import parse_dict_header 

10 

11 

12def _deprecated_cache_control_property( 

13 key: str, 

14 empty: t.Any, 

15 type: type[t.Any] | None, 

16 *, 

17 doc: str | None = None, 

18) -> t.Any: 

19 """Create a property for a ``Cache-Control`` directive. 

20 

21 :param key: The directive name. 

22 :param empty: The value when the directive is present without a value. 

23 :param type: The type to convert the value to. A ``ValueError`` returns ``None``. 

24 :param doc: The docstring for the property. If not given, it is generated 

25 based on the other params. 

26 

27 .. deprecated:: 3.2 

28 Will be removed in Werkzeug 4.0. Use indexing ``cc[key]`` for unknown 

29 directives. 

30 

31 .. versionchanged:: 3.1 

32 Added the ``doc`` parameter. 

33 

34 .. versionchanged:: 2.0 

35 Renamed from ``cache_property``. 

36 """ 

37 import warnings 

38 

39 warnings.warn( 

40 "The 'cache_property' method is deprecated and will be removed in Werkzeug 4.0." 

41 " Use indexing 'cc[key]' for unknown directives.", 

42 DeprecationWarning, 

43 stacklevel=2, 

44 ) 

45 return _cache_control_property(key, type, empty=empty, mutable=True, doc=doc) 

46 

47 

48def _cache_control_property( 

49 key: str, 

50 convert: type[t.Any] | None = None, 

51 *, 

52 empty: t.Any = None, 

53 mutable: bool = False, 

54 doc: str | None = None, 

55) -> t.Any: 

56 """Create a property for a ``Cache-Control`` directive. 

57 

58 :meta private: 

59 

60 :param key: The directive name. 

61 :param empty: The value when the directive is present without a value. 

62 :param convert: The type to convert the value to. A ``ValueError`` returns ``None``. 

63 :param doc: The docstring for the property. If not given, it is generated 

64 based on the other params. 

65 :param mutable: Whether the property has a setter and a deleter. 

66 """ 

67 if doc is None: 

68 parts = [f"The ``{key}`` directive."] 

69 

70 if convert is bool: 

71 parts.append("A ``bool``, ``True`` if present, ``False`` if not.") 

72 else: 

73 if convert is None: 

74 parts.append("A ``str``,") 

75 else: 

76 parts.append(f"A ``{convert.__name__}``,") 

77 

78 if empty is not None: 

79 parts.append(f"or ``{empty!r}`` if present with no value,") 

80 

81 parts.append("or ``None`` if not present.") 

82 

83 doc = " ".join(parts) 

84 

85 doc = cleandoc(doc) 

86 get_convert = _plain_int if convert is int else convert 

87 

88 if not mutable: 

89 return property(lambda x: x._get_directive(key, empty, get_convert), doc=doc) 

90 

91 return property( 

92 lambda x: x._get_directive(key, empty, get_convert), 

93 lambda x, v: x._set_directive(key, v, convert), 

94 lambda x: x._del_directive(key), 

95 doc=doc, 

96 ) 

97 

98 

99class _CacheControl(cabc.Mapping[str, str | None]): 

100 _data: cabc.Mapping[str, str | None] 

101 

102 def __getitem__(self, key: str, /) -> str | None: 

103 return self._data[key] 

104 

105 def __len__(self) -> int: 

106 return len(self._data) 

107 

108 def __iter__(self) -> cabc.Iterator[str]: 

109 return iter(self._data) 

110 

111 def _get_directive( 

112 self, key: str, empty: t.Any, convert: t.Callable[[str], t.Any] | None 

113 ) -> t.Any: 

114 if convert is bool: 

115 return key in self 

116 

117 if key not in self: 

118 return None 

119 

120 if (value := self[key]) is None: 

121 return empty 

122 

123 if convert is None: 

124 return value 

125 

126 try: 

127 return convert(value) 

128 except ValueError: 

129 return None 

130 

131 @classmethod 

132 def from_header(cls, value: str | None) -> t.Self: 

133 """Parse a ``Cache-Control`` header value and create an instance of this 

134 class. 

135 

136 .. versionadded:: 3.2 

137 """ 

138 if not value: 

139 return cls() 

140 

141 return cls(parse_dict_header(value)) # type: ignore[call-arg] 

142 

143 def to_header(self) -> str: 

144 """Convert to a ``Cache-Control`` header value.""" 

145 return dump_header(self._data) 

146 

147 def __str__(self) -> str: 

148 return self.to_header() 

149 

150 def __repr__(self) -> str: 

151 kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self._data.items())) 

152 return f"<{type(self).__name__} {kv_str}>" 

153 

154 cache_property = staticmethod(_deprecated_cache_control_property) 

155 

156 

157class RequestCacheControl(_CacheControl): 

158 """A parsed ``Cache-Control`` request header. 

159 :attr:`.Request.cache_control` returns an instance. 

160 

161 Typically, you'll access the various directive properties. It also allows 

162 indexing `cc[directive]` to access unknown directives that do not have 

163 corresponding properties. 

164 

165 :param values: Values parsed from the request header. 

166 

167 .. versionchanged:: 3.2 

168 Inherits ``Mapping`` instead of ``ImmutableDict``. 

169 

170 The ``on_update`` parameter was removed. 

171 

172 The ``cache_property`` method is deprecated and will be removed in 

173 Werkzeug 4.0. Use indexing ``cc[key]`` for unknown directives. 

174 

175 .. versionchanged:: 3.1 

176 Dict values are always ``str | None``. Setting properties will 

177 convert the value to a string. Setting a non-bool property to 

178 ``False`` is equivalent to setting it to ``None``. Getting typed 

179 properties will return ``None`` if conversion raises 

180 ``ValueError``, rather than the string. 

181 

182 ``max_age`` is ``None`` if present without a value, rather 

183 than ``-1``. 

184 

185 ``no_cache`` is a boolean, it is ``True`` instead of ``"*"`` 

186 when present. 

187 

188 ``max_stale`` is ``True`` if present without a value, rather 

189 than ``"*"``. 

190 

191 ``no_transform`` is a boolean. Previously it was mistakenly 

192 always ``None``. 

193 

194 ``min_fresh`` is ``None`` if present without a value, rather 

195 than ``"*"``. 

196 

197 .. versionchanged:: 2.1 

198 Setting int properties such as ``max_age`` will convert the 

199 value to an int. 

200 

201 .. versionadded:: 0.5 

202 Response-only properties are not present on this request class. 

203 """ 

204 

205 def __init__( 

206 self, 

207 values: cabc.Mapping[str, str | None] 

208 | cabc.Iterable[tuple[str, str | None]] 

209 | None = None, 

210 ) -> None: 

211 if values is None: 

212 values = {} 

213 elif not isinstance(values, cabc.Mapping): 

214 import warnings 

215 

216 warnings.warn( 

217 "Passing an iterable instead of a mapping is deprecated and" 

218 " will be removed in Werkzeug 4.0.", 

219 DeprecationWarning, 

220 stacklevel=2, 

221 ) 

222 values = dict(values) 

223 

224 self._data = values 

225 

226 max_age = _cache_control_property("max-age", int) 

227 max_stale: int | t.Literal[True] | None = _cache_control_property( 

228 "max-stale", int, empty=True 

229 ) 

230 min_fresh: int | None = _cache_control_property("min-fresh", int) 

231 no_cache: bool = _cache_control_property("no-cache", bool) 

232 no_store: bool = _cache_control_property("no-store", bool) 

233 no_transform: bool = _cache_control_property("no-transform", bool) 

234 only_if_cached: bool = _cache_control_property("only-if-cached", bool) 

235 stale_if_error: int | None = _cache_control_property("stale-if-error", int) 

236 

237 

238class ResponseCacheControl(cabc.MutableMapping[str, str | None], _CacheControl): 

239 """A parsed ``Cache-Control`` response header. 

240 

241 Set :attr:`.Response.cache_control` to an instance to set the header. 

242 Modifying the instance will update the header. 

243 

244 Typically, you'll use the various directive properties. It also allows 

245 indexing `cc[directive]` to get, set, or delete unknown directives that do 

246 not have corresponding properties. 

247 

248 :param values: Initial values to set. 

249 

250 .. versionchanged:: 3.2 

251 Inherits ``MutableMapping`` instead of ``dict``. 

252 

253 The ``on_update`` parameter was removed. 

254 

255 The ``cache_property`` method is deprecated and will be removed in 

256 Werkzeug 4.0. Use indexing ``cc[key]`` for unknown directives. 

257 

258 .. versionchanged:: 3.1 

259 Dict values are always ``str | None``. Setting properties will 

260 convert the value to a string. Setting a non-bool property to 

261 ``False`` is equivalent to setting it to ``None``. Getting typed 

262 properties will return ``None`` if conversion raises 

263 ``ValueError``, rather than the string. 

264 

265 ``no_cache`` is ``True`` if present without a value, rather than 

266 ``"*"``. 

267 

268 ``private`` is ``True`` if present without a value, rather than 

269 ``"*"``. 

270 

271 ``no_transform`` is a boolean. Previously it was mistakenly 

272 always ``None``. 

273 

274 Added the ``must_understand``, ``stale_while_revalidate``, and 

275 ``stale_if_error`` properties. 

276 

277 .. versionchanged:: 2.1.1 

278 ``s_maxage`` converts the value to an int. 

279 

280 .. versionchanged:: 2.1 

281 Setting int properties such as ``max_age`` will convert the 

282 value to an int. 

283 

284 .. versionadded:: 0.5 

285 Request-only properties are not present on this response class. 

286 

287 .. versionchanged:: 0.4 

288 Setting ``no_cache`` or ``private`` to ``True`` will set the 

289 implicit value ``"*"``. 

290 """ 

291 

292 _data: cabc.MutableMapping[str, str | None] 

293 

294 def __init__( 

295 self, 

296 values: cabc.Mapping[str, str | None] 

297 | cabc.Iterable[tuple[str, str | None]] 

298 | None = None, 

299 ) -> None: 

300 if values is None: 

301 values = {} 

302 elif isinstance(values, cabc.Mapping): 

303 if not isinstance(values, cabc.MutableMapping): 

304 values = dict(values) 

305 else: 

306 import warnings 

307 

308 warnings.warn( 

309 "Passing an iterable instead of a mapping is deprecated and" 

310 " will be removed in Werkzeug 4.0.", 

311 DeprecationWarning, 

312 stacklevel=2, 

313 ) 

314 values = dict(values) 

315 

316 self._data = values 

317 self._on_update: t.Callable[[ResponseCacheControl], None] | None = None 

318 

319 def _trigger_on_update(self) -> None: 

320 if self._on_update is not None: 

321 self._on_update(self) 

322 

323 def __setitem__(self, key: str, value: str | None, /) -> None: 

324 self._data[key] = value 

325 self._trigger_on_update() 

326 

327 def __delitem__(self, key: str, /) -> None: 

328 del self._data[key] 

329 self._trigger_on_update() 

330 

331 def _set_directive( 

332 self, key: str, value: t.Any, convert: type[t.Any] | None 

333 ) -> None: 

334 if convert is bool: 

335 if value: 

336 self._data[key] = None 

337 else: 

338 self._data.pop(key, None) 

339 elif value is None or value is False: 

340 self._data.pop(key, None) 

341 elif value is True: 

342 self._data[key] = None 

343 else: 

344 if convert is not None: 

345 value = convert(value) 

346 

347 self._data[key] = str(value) 

348 

349 self._trigger_on_update() 

350 

351 def _del_directive(self, key: str) -> None: 

352 self._data.pop(key, None) 

353 self._trigger_on_update() 

354 

355 max_age: int | None = _cache_control_property("max-age", int, mutable=True) 

356 s_maxage: int | None = _cache_control_property("s-maxage", int, mutable=True) 

357 # https://httpwg.org/specs/rfc9111.html#cache-response-directive.no-cache 

358 # This can be with or without a value, not mentioned on MDN. 

359 no_cache: str | t.Literal[True] | None = _cache_control_property( 

360 "no-cache", str, empty=True, mutable=True 

361 ) 

362 no_store: bool = _cache_control_property("no-store", bool, mutable=True) 

363 no_transform: bool = _cache_control_property("no-transform", bool, mutable=True) 

364 must_revalidate: bool = _cache_control_property( 

365 "must-revalidate", bool, mutable=True 

366 ) 

367 proxy_revalidate: bool = _cache_control_property( 

368 "proxy-revalidate", bool, mutable=True 

369 ) 

370 must_understand: bool = _cache_control_property( 

371 "must-understand", bool, mutable=True 

372 ) 

373 # https://httpwg.org/specs/rfc9111.html#cache-response-directive.private 

374 # This can be with or without a value, not mentioned on MDN. 

375 private: str | t.Literal[True] | None = _cache_control_property( 

376 "private", str, empty=True, mutable=True 

377 ) 

378 public: bool = _cache_control_property("public", bool, mutable=True) 

379 immutable: bool = _cache_control_property("immutable", bool, mutable=True) 

380 stale_while_revalidate: int | None = _cache_control_property( 

381 "stale-while-revalidate", int, mutable=True 

382 ) 

383 stale_if_error: int | None = _cache_control_property( 

384 "stale-if-error", int, mutable=True 

385 ) 

386 

387 

388if not t.TYPE_CHECKING: 

389 

390 def __getattr__(name: str) -> t.Any: 

391 if name == "cache_control_property": 

392 import warnings 

393 

394 warnings.warn( 

395 "The 'cache_control_property' function is deprecated and will" 

396 " be removed in Werkzeug 4.0. Use indexing 'cc[key]' for" 

397 " unknown directives.", 

398 DeprecationWarning, 

399 stacklevel=2, 

400 ) 

401 return _deprecated_cache_control_property 

402 

403 raise AttributeError(name)