from __future__ import annotations from .mixins import ImmutableDictMixin from .mixins import UpdateDictMixin def cache_control_property(key, empty, type): """Return a new property object for a cache header. Useful if you want to add support for a cache extension in a subclass. .. versionchanged:: 2.0 Renamed from ``cache_property``. """ return property( lambda x: x._get_cache_value(key, empty, type), lambda x, v: x._set_cache_value(key, v, type), lambda x: x._del_cache_value(key), f"accessor for {key!r}", ) class _CacheControl(UpdateDictMixin, dict): """Subclass of a dict that stores values for a Cache-Control header. It has accessors for all the cache-control directives specified in RFC 2616. The class does not differentiate between request and response directives. Because the cache-control directives in the HTTP header use dashes the python descriptors use underscores for that. To get a header of the :class:`CacheControl` object again you can convert the object into a string or call the :meth:`to_header` method. If you plan to subclass it and add your own items have a look at the sourcecode for that class. .. versionchanged:: 2.1.0 Setting int properties such as ``max_age`` will convert the value to an int. .. versionchanged:: 0.4 Setting `no_cache` or `private` to boolean `True` will set the implicit none-value which is ``*``: >>> cc = ResponseCacheControl() >>> cc.no_cache = True >>> cc >>> cc.no_cache '*' >>> cc.no_cache = None >>> cc In versions before 0.5 the behavior documented here affected the now no longer existing `CacheControl` class. """ no_cache = cache_control_property("no-cache", "*", None) no_store = cache_control_property("no-store", None, bool) max_age = cache_control_property("max-age", -1, int) no_transform = cache_control_property("no-transform", None, None) def __init__(self, values=(), on_update=None): dict.__init__(self, values or ()) self.on_update = on_update self.provided = values is not None def _get_cache_value(self, key, empty, type): """Used internally by the accessor properties.""" if type is bool: return key in self if key in self: value = self[key] if value is None: return empty elif type is not None: try: value = type(value) except ValueError: pass return value return None def _set_cache_value(self, key, value, type): """Used internally by the accessor properties.""" if type is bool: if value: self[key] = None else: self.pop(key, None) else: if value is None: self.pop(key, None) elif value is True: self[key] = None else: if type is not None: self[key] = type(value) else: self[key] = value def _del_cache_value(self, key): """Used internally by the accessor properties.""" if key in self: del self[key] def to_header(self): """Convert the stored values into a cache control header.""" return http.dump_header(self) def __str__(self): return self.to_header() def __repr__(self): kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items())) return f"<{type(self).__name__} {kv_str}>" cache_property = staticmethod(cache_control_property) class RequestCacheControl(ImmutableDictMixin, _CacheControl): """A cache control for requests. This is immutable and gives access to all the request-relevant cache control headers. To get a header of the :class:`RequestCacheControl` object again you can convert the object into a string or call the :meth:`to_header` method. If you plan to subclass it and add your own items have a look at the sourcecode for that class. .. versionchanged:: 2.1.0 Setting int properties such as ``max_age`` will convert the value to an int. .. versionadded:: 0.5 In previous versions a `CacheControl` class existed that was used both for request and response. """ max_stale = cache_control_property("max-stale", "*", int) min_fresh = cache_control_property("min-fresh", "*", int) only_if_cached = cache_control_property("only-if-cached", None, bool) class ResponseCacheControl(_CacheControl): """A cache control for responses. Unlike :class:`RequestCacheControl` this is mutable and gives access to response-relevant cache control headers. To get a header of the :class:`ResponseCacheControl` object again you can convert the object into a string or call the :meth:`to_header` method. If you plan to subclass it and add your own items have a look at the sourcecode for that class. .. versionchanged:: 2.1.1 ``s_maxage`` converts the value to an int. .. versionchanged:: 2.1.0 Setting int properties such as ``max_age`` will convert the value to an int. .. versionadded:: 0.5 In previous versions a `CacheControl` class existed that was used both for request and response. """ public = cache_control_property("public", None, bool) private = cache_control_property("private", "*", None) must_revalidate = cache_control_property("must-revalidate", None, bool) proxy_revalidate = cache_control_property("proxy-revalidate", None, bool) s_maxage = cache_control_property("s-maxage", None, int) immutable = cache_control_property("immutable", None, bool) # circular dependencies from .. import http