1from __future__ import annotations 
    2 
    3import collections.abc as cabc 
    4import typing as t 
    5from inspect import cleandoc 
    6 
    7from .mixins import ImmutableDictMixin 
    8from .structures import CallbackDict 
    9 
    10 
    11def cache_control_property( 
    12    key: str, empty: t.Any, type: type[t.Any] | None, *, doc: str | None = None 
    13) -> t.Any: 
    14    """Return a new property object for a cache header. Useful if you 
    15    want to add support for a cache extension in a subclass. 
    16 
    17    :param key: The attribute name present in the parsed cache-control header dict. 
    18    :param empty: The value to use if the key is present without a value. 
    19    :param type: The type to convert the string value to instead of a string. If 
    20        conversion raises a ``ValueError``, the returned value is ``None``. 
    21    :param doc: The docstring for the property. If not given, it is generated 
    22        based on the other params. 
    23 
    24    .. versionchanged:: 3.1 
    25        Added the ``doc`` param. 
    26 
    27    .. versionchanged:: 2.0 
    28        Renamed from ``cache_property``. 
    29    """ 
    30    if doc is None: 
    31        parts = [f"The ``{key}`` attribute."] 
    32 
    33        if type is bool: 
    34            parts.append("A ``bool``, either present or not.") 
    35        else: 
    36            if type is None: 
    37                parts.append("A ``str``,") 
    38            else: 
    39                parts.append(f"A ``{type.__name__}``,") 
    40 
    41            if empty is not None: 
    42                parts.append(f"``{empty!r}`` if present with no value,") 
    43 
    44            parts.append("or ``None`` if not present.") 
    45 
    46        doc = " ".join(parts) 
    47 
    48    return property( 
    49        lambda x: x._get_cache_value(key, empty, type), 
    50        lambda x, v: x._set_cache_value(key, v, type), 
    51        lambda x: x._del_cache_value(key), 
    52        doc=cleandoc(doc), 
    53    ) 
    54 
    55 
    56class _CacheControl(CallbackDict[str, t.Optional[str]]): 
    57    """Subclass of a dict that stores values for a Cache-Control header.  It 
    58    has accessors for all the cache-control directives specified in RFC 2616. 
    59    The class does not differentiate between request and response directives. 
    60 
    61    Because the cache-control directives in the HTTP header use dashes the 
    62    python descriptors use underscores for that. 
    63 
    64    To get a header of the :class:`CacheControl` object again you can convert 
    65    the object into a string or call the :meth:`to_header` method.  If you plan 
    66    to subclass it and add your own items have a look at the sourcecode for 
    67    that class. 
    68 
    69    .. versionchanged:: 3.1 
    70        Dict values are always ``str | None``. Setting properties will 
    71        convert the value to a string. Setting a non-bool property to 
    72        ``False`` is equivalent to setting it to ``None``. Getting typed 
    73        properties will return ``None`` if conversion raises 
    74        ``ValueError``, rather than the string. 
    75 
    76    .. versionchanged:: 2.1 
    77        Setting int properties such as ``max_age`` will convert the 
    78        value to an int. 
    79 
    80    .. versionchanged:: 0.4 
    81       Setting ``no_cache`` or ``private`` to ``True`` will set the 
    82       implicit value ``"*"``. 
    83    """ 
    84 
    85    no_store: bool = cache_control_property("no-store", None, bool) 
    86    max_age: int | None = cache_control_property("max-age", None, int) 
    87    no_transform: bool = cache_control_property("no-transform", None, bool) 
    88    stale_if_error: int | None = cache_control_property("stale-if-error", None, int) 
    89 
    90    def __init__( 
    91        self, 
    92        values: cabc.Mapping[str, t.Any] | cabc.Iterable[tuple[str, t.Any]] | None = (), 
    93        on_update: cabc.Callable[[_CacheControl], None] | None = None, 
    94    ): 
    95        super().__init__(values, on_update) 
    96        self.provided = values is not None 
    97 
    98    def _get_cache_value( 
    99        self, key: str, empty: t.Any, type: type[t.Any] | None 
    100    ) -> t.Any: 
    101        """Used internally by the accessor properties.""" 
    102        if type is bool: 
    103            return key in self 
    104 
    105        if key not in self: 
    106            return None 
    107 
    108        if (value := self[key]) is None: 
    109            return empty 
    110 
    111        if type is not None: 
    112            try: 
    113                value = type(value) 
    114            except ValueError: 
    115                return None 
    116 
    117        return value 
    118 
    119    def _set_cache_value( 
    120        self, key: str, value: t.Any, type: type[t.Any] | None 
    121    ) -> None: 
    122        """Used internally by the accessor properties.""" 
    123        if type is bool: 
    124            if value: 
    125                self[key] = None 
    126            else: 
    127                self.pop(key, None) 
    128        elif value is None or value is False: 
    129            self.pop(key, None) 
    130        elif value is True: 
    131            self[key] = None 
    132        else: 
    133            if type is not None: 
    134                value = type(value) 
    135 
    136            self[key] = str(value) 
    137 
    138    def _del_cache_value(self, key: str) -> None: 
    139        """Used internally by the accessor properties.""" 
    140        if key in self: 
    141            del self[key] 
    142 
    143    def to_header(self) -> str: 
    144        """Convert the stored values into a cache control header.""" 
    145        return http.dump_header(self) 
    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.items())) 
    152        return f"<{type(self).__name__} {kv_str}>" 
    153 
    154    cache_property = staticmethod(cache_control_property) 
    155 
    156 
    157class RequestCacheControl(ImmutableDictMixin[str, t.Optional[str]], _CacheControl):  # type: ignore[misc] 
    158    """A cache control for requests.  This is immutable and gives access 
    159    to all the request-relevant cache control headers. 
    160 
    161    To get a header of the :class:`RequestCacheControl` object again you can 
    162    convert the object into a string or call the :meth:`to_header` method.  If 
    163    you plan to subclass it and add your own items have a look at the sourcecode 
    164    for that class. 
    165 
    166    .. versionchanged:: 3.1 
    167        Dict values are always ``str | None``. Setting properties will 
    168        convert the value to a string. Setting a non-bool property to 
    169        ``False`` is equivalent to setting it to ``None``. Getting typed 
    170        properties will return ``None`` if conversion raises 
    171        ``ValueError``, rather than the string. 
    172 
    173    .. versionchanged:: 3.1 
    174       ``max_age`` is ``None`` if present without a value, rather 
    175       than ``-1``. 
    176 
    177    .. versionchanged:: 3.1 
    178        ``no_cache`` is a boolean, it is ``True`` instead of ``"*"`` 
    179        when present. 
    180 
    181    .. versionchanged:: 3.1 
    182        ``max_stale`` is ``True`` if present without a value, rather 
    183        than ``"*"``. 
    184 
    185    .. versionchanged:: 3.1 
    186       ``no_transform`` is a boolean. Previously it was mistakenly 
    187       always ``None``. 
    188 
    189    .. versionchanged:: 3.1 
    190       ``min_fresh`` is ``None`` if present without a value, rather 
    191       than ``"*"``. 
    192 
    193    .. versionchanged:: 2.1 
    194        Setting int properties such as ``max_age`` will convert the 
    195        value to an int. 
    196 
    197    .. versionadded:: 0.5 
    198        Response-only properties are not present on this request class. 
    199    """ 
    200 
    201    no_cache: bool = cache_control_property("no-cache", None, bool) 
    202    max_stale: int | t.Literal[True] | None = cache_control_property( 
    203        "max-stale", 
    204        True, 
    205        int, 
    206    ) 
    207    min_fresh: int | None = cache_control_property("min-fresh", None, int) 
    208    only_if_cached: bool = cache_control_property("only-if-cached", None, bool) 
    209 
    210 
    211class ResponseCacheControl(_CacheControl): 
    212    """A cache control for responses.  Unlike :class:`RequestCacheControl` 
    213    this is mutable and gives access to response-relevant cache control 
    214    headers. 
    215 
    216    To get a header of the :class:`ResponseCacheControl` object again you can 
    217    convert the object into a string or call the :meth:`to_header` method.  If 
    218    you plan to subclass it and add your own items have a look at the sourcecode 
    219    for that class. 
    220 
    221    .. versionchanged:: 3.1 
    222        Dict values are always ``str | None``. Setting properties will 
    223        convert the value to a string. Setting a non-bool property to 
    224        ``False`` is equivalent to setting it to ``None``. Getting typed 
    225        properties will return ``None`` if conversion raises 
    226        ``ValueError``, rather than the string. 
    227 
    228    .. versionchanged:: 3.1 
    229        ``no_cache`` is ``True`` if present without a value, rather than 
    230        ``"*"``. 
    231 
    232    .. versionchanged:: 3.1 
    233        ``private`` is ``True`` if present without a value, rather than 
    234        ``"*"``. 
    235 
    236    .. versionchanged:: 3.1 
    237       ``no_transform`` is a boolean. Previously it was mistakenly 
    238       always ``None``. 
    239 
    240    .. versionchanged:: 3.1 
    241        Added the ``must_understand``, ``stale_while_revalidate``, and 
    242        ``stale_if_error`` properties. 
    243 
    244    .. versionchanged:: 2.1.1 
    245        ``s_maxage`` converts the value to an int. 
    246 
    247    .. versionchanged:: 2.1 
    248        Setting int properties such as ``max_age`` will convert the 
    249        value to an int. 
    250 
    251    .. versionadded:: 0.5 
    252       Request-only properties are not present on this response class. 
    253    """ 
    254 
    255    no_cache: str | t.Literal[True] | None = cache_control_property( 
    256        "no-cache", True, None 
    257    ) 
    258    public: bool = cache_control_property("public", None, bool) 
    259    private: str | t.Literal[True] | None = cache_control_property( 
    260        "private", True, None 
    261    ) 
    262    must_revalidate: bool = cache_control_property("must-revalidate", None, bool) 
    263    proxy_revalidate: bool = cache_control_property("proxy-revalidate", None, bool) 
    264    s_maxage: int | None = cache_control_property("s-maxage", None, int) 
    265    immutable: bool = cache_control_property("immutable", None, bool) 
    266    must_understand: bool = cache_control_property("must-understand", None, bool) 
    267    stale_while_revalidate: int | None = cache_control_property( 
    268        "stale-while-revalidate", None, int 
    269    ) 
    270 
    271 
    272# circular dependencies 
    273from .. import http