diff --git a/CHANGES.rst b/CHANGES.rst index fdc52692a..0b7889237 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,10 @@ Version 3.1.0 Unreleased - Support Cookie CHIPS (Partitioned Cookies). :issue:`2797` +- ``CacheControl.no_transform`` is a boolean when present. ``min_fresh`` is + ``None`` when not present. Added the ``must_understand`` attribute. Fixed + some typing issues on cache control. :issue:`2881` + Version 3.0.3 ------------- diff --git a/src/werkzeug/datastructures/cache_control.py b/src/werkzeug/datastructures/cache_control.py index bff4c18bb..6ff4eceeb 100644 --- a/src/werkzeug/datastructures/cache_control.py +++ b/src/werkzeug/datastructures/cache_control.py @@ -32,6 +32,10 @@ class _CacheControl(UpdateDictMixin, dict): to subclass it and add your own items have a look at the sourcecode for that class. + .. versionchanged:: 3.1 + + ``no_transform`` is a boolean when present. + .. versionchanged:: 2.1.0 Setting int properties such as ``max_age`` will convert the value to an int. @@ -58,7 +62,7 @@ class _CacheControl(UpdateDictMixin, dict): 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) + no_transform = cache_control_property("no-transform", None, bool) def __init__(self, values=(), on_update=None): dict.__init__(self, values or ()) @@ -127,6 +131,12 @@ class RequestCacheControl(ImmutableDictMixin, _CacheControl): you plan to subclass it and add your own items have a look at the sourcecode for that class. + .. versionchanged:: 3.1 + ``no_transform`` is a boolean when present. + + .. versionchanged:: 3.1 + ``min_fresh`` is ``None`` if a value is not provided for the attribute. + .. versionchanged:: 2.1.0 Setting int properties such as ``max_age`` will convert the value to an int. @@ -137,7 +147,7 @@ class RequestCacheControl(ImmutableDictMixin, _CacheControl): """ max_stale = cache_control_property("max-stale", "*", int) - min_fresh = cache_control_property("min-fresh", "*", int) + min_fresh = cache_control_property("min-fresh", None, int) only_if_cached = cache_control_property("only-if-cached", None, bool) @@ -151,6 +161,12 @@ class ResponseCacheControl(_CacheControl): you plan to subclass it and add your own items have a look at the sourcecode for that class. + .. versionchanged:: 3.1 + ``no_transform`` is a boolean when present. + + .. versionchanged:: 3.1 + Added the ``must_understand`` attribute. + .. versionchanged:: 2.1.1 ``s_maxage`` converts the value to an int. @@ -169,6 +185,7 @@ class ResponseCacheControl(_CacheControl): 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) + must_understand = cache_control_property("must-understand", None, bool) # circular dependencies diff --git a/src/werkzeug/datastructures/cache_control.pyi b/src/werkzeug/datastructures/cache_control.pyi index 54ec02082..4c9f4df37 100644 --- a/src/werkzeug/datastructures/cache_control.pyi +++ b/src/werkzeug/datastructures/cache_control.pyi @@ -1,6 +1,7 @@ from collections.abc import Callable from collections.abc import Iterable from collections.abc import Mapping +from typing import Literal from typing import TypeVar from .mixins import ImmutableDictMixin @@ -24,13 +25,13 @@ class _CacheControl( on_update: Callable[[_CacheControl], None] | None = None, ) -> None: ... @property - def no_cache(self) -> bool | None: ... + def no_cache(self) -> str | None: ... @no_cache.setter - def no_cache(self, value: bool | None) -> None: ... + def no_cache(self, value: Literal[True] | str | None) -> None: ... @no_cache.deleter def no_cache(self) -> None: ... @property - def no_store(self) -> bool | None: ... + def no_store(self) -> bool: ... @no_store.setter def no_store(self, value: bool | None) -> None: ... @no_store.deleter @@ -42,7 +43,7 @@ class _CacheControl( @max_age.deleter def max_age(self) -> None: ... @property - def no_transform(self) -> bool | None: ... + def no_transform(self) -> bool: ... @no_transform.setter def no_transform(self, value: bool | None) -> None: ... @no_transform.deleter @@ -57,46 +58,42 @@ class _CacheControl( class RequestCacheControl( # type: ignore[misc] ImmutableDictMixin[str, str | int | bool | None], _CacheControl ): + @property # type: ignore + def no_cache(self) -> str | None: ... + @property # type: ignore + def no_store(self) -> bool: ... + @property # type: ignore + def max_age(self) -> int | None: ... + @property # type: ignore + def no_transform(self) -> bool: ... @property - def max_stale(self) -> int | None: ... - @max_stale.setter - def max_stale(self, value: int | None) -> None: ... - @max_stale.deleter - def max_stale(self) -> None: ... + def max_stale(self) -> int | Literal["*"] | None: ... @property def min_fresh(self) -> int | None: ... - @min_fresh.setter - def min_fresh(self, value: int | None) -> None: ... - @min_fresh.deleter - def min_fresh(self) -> None: ... @property def only_if_cached(self) -> bool | None: ... - @only_if_cached.setter - def only_if_cached(self, value: bool | None) -> None: ... - @only_if_cached.deleter - def only_if_cached(self) -> None: ... class ResponseCacheControl(_CacheControl): @property - def public(self) -> bool | None: ... + def public(self) -> bool: ... @public.setter def public(self, value: bool | None) -> None: ... @public.deleter def public(self) -> None: ... @property - def private(self) -> bool | None: ... + def private(self) -> str | None: ... @private.setter - def private(self, value: bool | None) -> None: ... + def private(self, value: Literal[True] | str | None) -> None: ... @private.deleter def private(self) -> None: ... @property - def must_revalidate(self) -> bool | None: ... + def must_revalidate(self) -> bool: ... @must_revalidate.setter def must_revalidate(self, value: bool | None) -> None: ... @must_revalidate.deleter def must_revalidate(self) -> None: ... @property - def proxy_revalidate(self) -> bool | None: ... + def proxy_revalidate(self) -> bool: ... @proxy_revalidate.setter def proxy_revalidate(self, value: bool | None) -> None: ... @proxy_revalidate.deleter @@ -108,8 +105,14 @@ class ResponseCacheControl(_CacheControl): @s_maxage.deleter def s_maxage(self) -> None: ... @property - def immutable(self) -> bool | None: ... + def immutable(self) -> bool: ... @immutable.setter def immutable(self, value: bool | None) -> None: ... @immutable.deleter def immutable(self) -> None: ... + @property + def must_understand(self) -> bool: ... + @must_understand.setter + def must_understand(self, value: bool | None) -> None: ... + @must_understand.deleter + def must_understand(self) -> None: ... diff --git a/src/werkzeug/utils.py b/src/werkzeug/utils.py index 59b97b732..b68ac18ca 100644 --- a/src/werkzeug/utils.py +++ b/src/werkzeug/utils.py @@ -497,7 +497,7 @@ def send_file( elif mtime is not None: rv.last_modified = mtime # type: ignore - rv.cache_control.no_cache = True + rv.cache_control.no_cache = True # type: ignore[assignment] # Flask will pass app.get_send_file_max_age, allowing its send_file # wrapper to not have to deal with paths. diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 64330e1e6..830dfefd5 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -953,6 +953,26 @@ def test_set_none(self): cc.no_cache = False assert cc.no_cache is False + def test_no_transform(self): + cc = ds.RequestCacheControl([("no-transform", None)]) + assert cc.no_transform is True + cc = ds.RequestCacheControl() + assert cc.no_transform is False + + def test_min_fresh(self): + cc = ds.RequestCacheControl([("min-fresh", "0")]) + assert cc.min_fresh == 0 + cc = ds.RequestCacheControl([("min-fresh", None)]) + assert cc.min_fresh is None + cc = ds.RequestCacheControl() + assert cc.min_fresh is None + + def test_must_understand(self): + cc = ds.ResponseCacheControl([("must-understand", None)]) + assert cc.must_understand is True + cc = ds.ResponseCacheControl() + assert cc.must_understand is False + class TestContentSecurityPolicy: def test_construct(self):