274 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import collections.abc as cabc
 | 
						|
import typing as t
 | 
						|
from inspect import cleandoc
 | 
						|
 | 
						|
from .mixins import ImmutableDictMixin
 | 
						|
from .structures import CallbackDict
 | 
						|
 | 
						|
 | 
						|
def cache_control_property(
 | 
						|
    key: str, empty: t.Any, type: type[t.Any] | None, *, doc: str | None = None
 | 
						|
) -> t.Any:
 | 
						|
    """Return a new property object for a cache header. Useful if you
 | 
						|
    want to add support for a cache extension in a subclass.
 | 
						|
 | 
						|
    :param key: The attribute name present in the parsed cache-control header dict.
 | 
						|
    :param empty: The value to use if the key is present without a value.
 | 
						|
    :param type: The type to convert the string value to instead of a string. If
 | 
						|
        conversion raises a ``ValueError``, the returned value is ``None``.
 | 
						|
    :param doc: The docstring for the property. If not given, it is generated
 | 
						|
        based on the other params.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        Added the ``doc`` param.
 | 
						|
 | 
						|
    .. versionchanged:: 2.0
 | 
						|
        Renamed from ``cache_property``.
 | 
						|
    """
 | 
						|
    if doc is None:
 | 
						|
        parts = [f"The ``{key}`` attribute."]
 | 
						|
 | 
						|
        if type is bool:
 | 
						|
            parts.append("A ``bool``, either present or not.")
 | 
						|
        else:
 | 
						|
            if type is None:
 | 
						|
                parts.append("A ``str``,")
 | 
						|
            else:
 | 
						|
                parts.append(f"A ``{type.__name__}``,")
 | 
						|
 | 
						|
            if empty is not None:
 | 
						|
                parts.append(f"``{empty!r}`` if present with no value,")
 | 
						|
 | 
						|
            parts.append("or ``None`` if not present.")
 | 
						|
 | 
						|
        doc = " ".join(parts)
 | 
						|
 | 
						|
    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),
 | 
						|
        doc=cleandoc(doc),
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
class _CacheControl(CallbackDict[str, t.Optional[str]]):
 | 
						|
    """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:: 3.1
 | 
						|
        Dict values are always ``str | None``. Setting properties will
 | 
						|
        convert the value to a string. Setting a non-bool property to
 | 
						|
        ``False`` is equivalent to setting it to ``None``. Getting typed
 | 
						|
        properties will return ``None`` if conversion raises
 | 
						|
        ``ValueError``, rather than the string.
 | 
						|
 | 
						|
    .. versionchanged:: 2.1
 | 
						|
        Setting int properties such as ``max_age`` will convert the
 | 
						|
        value to an int.
 | 
						|
 | 
						|
    .. versionchanged:: 0.4
 | 
						|
       Setting ``no_cache`` or ``private`` to ``True`` will set the
 | 
						|
       implicit value ``"*"``.
 | 
						|
    """
 | 
						|
 | 
						|
    no_store: bool = cache_control_property("no-store", None, bool)
 | 
						|
    max_age: int | None = cache_control_property("max-age", None, int)
 | 
						|
    no_transform: bool = cache_control_property("no-transform", None, bool)
 | 
						|
    stale_if_error: int | None = cache_control_property("stale-if-error", None, int)
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        values: cabc.Mapping[str, t.Any] | cabc.Iterable[tuple[str, t.Any]] | None = (),
 | 
						|
        on_update: cabc.Callable[[_CacheControl], None] | None = None,
 | 
						|
    ):
 | 
						|
        super().__init__(values, on_update)
 | 
						|
        self.provided = values is not None
 | 
						|
 | 
						|
    def _get_cache_value(
 | 
						|
        self, key: str, empty: t.Any, type: type[t.Any] | None
 | 
						|
    ) -> t.Any:
 | 
						|
        """Used internally by the accessor properties."""
 | 
						|
        if type is bool:
 | 
						|
            return key in self
 | 
						|
 | 
						|
        if key not in self:
 | 
						|
            return None
 | 
						|
 | 
						|
        if (value := self[key]) is None:
 | 
						|
            return empty
 | 
						|
 | 
						|
        if type is not None:
 | 
						|
            try:
 | 
						|
                value = type(value)
 | 
						|
            except ValueError:
 | 
						|
                return None
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    def _set_cache_value(
 | 
						|
        self, key: str, value: t.Any, type: type[t.Any] | None
 | 
						|
    ) -> None:
 | 
						|
        """Used internally by the accessor properties."""
 | 
						|
        if type is bool:
 | 
						|
            if value:
 | 
						|
                self[key] = None
 | 
						|
            else:
 | 
						|
                self.pop(key, None)
 | 
						|
        elif value is None or value is False:
 | 
						|
            self.pop(key, None)
 | 
						|
        elif value is True:
 | 
						|
            self[key] = None
 | 
						|
        else:
 | 
						|
            if type is not None:
 | 
						|
                value = type(value)
 | 
						|
 | 
						|
            self[key] = str(value)
 | 
						|
 | 
						|
    def _del_cache_value(self, key: str) -> None:
 | 
						|
        """Used internally by the accessor properties."""
 | 
						|
        if key in self:
 | 
						|
            del self[key]
 | 
						|
 | 
						|
    def to_header(self) -> str:
 | 
						|
        """Convert the stored values into a cache control header."""
 | 
						|
        return http.dump_header(self)
 | 
						|
 | 
						|
    def __str__(self) -> str:
 | 
						|
        return self.to_header()
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        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[str, t.Optional[str]], _CacheControl):  # type: ignore[misc]
 | 
						|
    """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:: 3.1
 | 
						|
        Dict values are always ``str | None``. Setting properties will
 | 
						|
        convert the value to a string. Setting a non-bool property to
 | 
						|
        ``False`` is equivalent to setting it to ``None``. Getting typed
 | 
						|
        properties will return ``None`` if conversion raises
 | 
						|
        ``ValueError``, rather than the string.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
       ``max_age`` is ``None`` if present without a value, rather
 | 
						|
       than ``-1``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        ``no_cache`` is a boolean, it is ``True`` instead of ``"*"``
 | 
						|
        when present.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        ``max_stale`` is ``True`` if present without a value, rather
 | 
						|
        than ``"*"``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
       ``no_transform`` is a boolean. Previously it was mistakenly
 | 
						|
       always ``None``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
       ``min_fresh`` is ``None`` if present without a value, rather
 | 
						|
       than ``"*"``.
 | 
						|
 | 
						|
    .. versionchanged:: 2.1
 | 
						|
        Setting int properties such as ``max_age`` will convert the
 | 
						|
        value to an int.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
        Response-only properties are not present on this request class.
 | 
						|
    """
 | 
						|
 | 
						|
    no_cache: bool = cache_control_property("no-cache", None, bool)
 | 
						|
    max_stale: int | t.Literal[True] | None = cache_control_property(
 | 
						|
        "max-stale",
 | 
						|
        True,
 | 
						|
        int,
 | 
						|
    )
 | 
						|
    min_fresh: int | None = cache_control_property("min-fresh", None, int)
 | 
						|
    only_if_cached: bool = 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:: 3.1
 | 
						|
        Dict values are always ``str | None``. Setting properties will
 | 
						|
        convert the value to a string. Setting a non-bool property to
 | 
						|
        ``False`` is equivalent to setting it to ``None``. Getting typed
 | 
						|
        properties will return ``None`` if conversion raises
 | 
						|
        ``ValueError``, rather than the string.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        ``no_cache`` is ``True`` if present without a value, rather than
 | 
						|
        ``"*"``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        ``private`` is ``True`` if present without a value, rather than
 | 
						|
        ``"*"``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
       ``no_transform`` is a boolean. Previously it was mistakenly
 | 
						|
       always ``None``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        Added the ``must_understand``, ``stale_while_revalidate``, and
 | 
						|
        ``stale_if_error`` properties.
 | 
						|
 | 
						|
    .. versionchanged:: 2.1.1
 | 
						|
        ``s_maxage`` converts the value to an int.
 | 
						|
 | 
						|
    .. versionchanged:: 2.1
 | 
						|
        Setting int properties such as ``max_age`` will convert the
 | 
						|
        value to an int.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
       Request-only properties are not present on this response class.
 | 
						|
    """
 | 
						|
 | 
						|
    no_cache: str | t.Literal[True] | None = cache_control_property(
 | 
						|
        "no-cache", True, None
 | 
						|
    )
 | 
						|
    public: bool = cache_control_property("public", None, bool)
 | 
						|
    private: str | t.Literal[True] | None = cache_control_property(
 | 
						|
        "private", True, None
 | 
						|
    )
 | 
						|
    must_revalidate: bool = cache_control_property("must-revalidate", None, bool)
 | 
						|
    proxy_revalidate: bool = cache_control_property("proxy-revalidate", None, bool)
 | 
						|
    s_maxage: int | None = cache_control_property("s-maxage", None, int)
 | 
						|
    immutable: bool = cache_control_property("immutable", None, bool)
 | 
						|
    must_understand: bool = cache_control_property("must-understand", None, bool)
 | 
						|
    stale_while_revalidate: int | None = cache_control_property(
 | 
						|
        "stale-while-revalidate", None, int
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
# circular dependencies
 | 
						|
from .. import http
 |