mirror of
https://github.com/long2ice/fastapi-cache.git
synced 2026-03-25 04:57:54 +00:00
Add a cache status header to the response
The header name is configurable, and defaults to `X-FastAPI-Cache`, the value is either `HIT` or `MISS`. Note that the header is not set at all when the cache is disabled.
This commit is contained in:
committed by
Martijn Pieters
parent
29426de95f
commit
915f3dd8f2
@@ -99,6 +99,7 @@ namespace | str, namespace to use to store certain cache items
|
|||||||
coder | which coder to use, e.g. JsonCoder
|
coder | which coder to use, e.g. JsonCoder
|
||||||
key_builder | which key builder to use, default to builtin
|
key_builder | which key builder to use, default to builtin
|
||||||
injected_dependency_namespace | prefix for injected dependency keywords, defaults to `__fastapi_cache`.
|
injected_dependency_namespace | prefix for injected dependency keywords, defaults to `__fastapi_cache`.
|
||||||
|
cache_status_header | Name for the header on the response indicating if the request was served from cache; either `HIT` or `MISS`. Defaults to `X-FastAPI-Cache`.
|
||||||
|
|
||||||
You can also use `cache` as decorator like other cache tools to cache common function result.
|
You can also use `cache` as decorator like other cache tools to cache common function result.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class FastAPICache:
|
|||||||
_init: ClassVar[bool] = False
|
_init: ClassVar[bool] = False
|
||||||
_coder: ClassVar[Optional[Type[Coder]]] = None
|
_coder: ClassVar[Optional[Type[Coder]]] = None
|
||||||
_key_builder: ClassVar[Optional[KeyBuilder]] = None
|
_key_builder: ClassVar[Optional[KeyBuilder]] = None
|
||||||
|
_cache_status_header: ClassVar[Optional[str]] = None
|
||||||
_enable: ClassVar[bool] = True
|
_enable: ClassVar[bool] = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -32,6 +33,7 @@ class FastAPICache:
|
|||||||
expire: Optional[int] = None,
|
expire: Optional[int] = None,
|
||||||
coder: Type[Coder] = JsonCoder,
|
coder: Type[Coder] = JsonCoder,
|
||||||
key_builder: KeyBuilder = default_key_builder,
|
key_builder: KeyBuilder = default_key_builder,
|
||||||
|
cache_status_header: str = "X-FastAPI-Cache",
|
||||||
enable: bool = True,
|
enable: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
if cls._init:
|
if cls._init:
|
||||||
@@ -42,6 +44,7 @@ class FastAPICache:
|
|||||||
cls._expire = expire
|
cls._expire = expire
|
||||||
cls._coder = coder
|
cls._coder = coder
|
||||||
cls._key_builder = key_builder
|
cls._key_builder = key_builder
|
||||||
|
cls._cache_status_header = cache_status_header
|
||||||
cls._enable = enable
|
cls._enable = enable
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -52,6 +55,7 @@ class FastAPICache:
|
|||||||
cls._expire = None
|
cls._expire = None
|
||||||
cls._coder = None
|
cls._coder = None
|
||||||
cls._key_builder = None
|
cls._key_builder = None
|
||||||
|
cls._cache_status_header = None
|
||||||
cls._enable = True
|
cls._enable = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -78,6 +82,11 @@ class FastAPICache:
|
|||||||
assert cls._key_builder, "You must call init first!" # nosec: B101
|
assert cls._key_builder, "You must call init first!" # nosec: B101
|
||||||
return cls._key_builder
|
return cls._key_builder
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_cache_status_header(cls) -> str:
|
||||||
|
assert cls._cache_status_header, "You must call init first!" # nosec: B101
|
||||||
|
return cls._cache_status_header
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_enable(cls) -> bool:
|
def get_enable(cls) -> bool:
|
||||||
return cls._enable
|
return cls._enable
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ def cache(
|
|||||||
expire = expire or FastAPICache.get_expire()
|
expire = expire or FastAPICache.get_expire()
|
||||||
key_builder = key_builder or FastAPICache.get_key_builder()
|
key_builder = key_builder or FastAPICache.get_key_builder()
|
||||||
backend = FastAPICache.get_backend()
|
backend = FastAPICache.get_backend()
|
||||||
|
cache_status_header = FastAPICache.get_cache_status_header()
|
||||||
|
|
||||||
cache_key = key_builder(
|
cache_key = key_builder(
|
||||||
func,
|
func,
|
||||||
@@ -181,6 +182,7 @@ def cache(
|
|||||||
{
|
{
|
||||||
"Cache-Control": f"max-age={expire}",
|
"Cache-Control": f"max-age={expire}",
|
||||||
"ETag": f"W/{hash(to_cache)}",
|
"ETag": f"W/{hash(to_cache)}",
|
||||||
|
cache_status_header: "MISS",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -191,6 +193,7 @@ def cache(
|
|||||||
{
|
{
|
||||||
"Cache-Control": f"max-age={ttl}",
|
"Cache-Control": f"max-age={ttl}",
|
||||||
"ETag": etag,
|
"ETag": etag,
|
||||||
|
cache_status_header: "HIT",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,18 @@ def init_cache() -> Generator[Any, Any, None]:
|
|||||||
def test_datetime() -> None:
|
def test_datetime() -> None:
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/datetime")
|
response = client.get("/datetime")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "MISS"
|
||||||
now = response.json().get("now")
|
now = response.json().get("now")
|
||||||
now_ = pendulum.now().replace(microsecond=0) # type: ignore[no-untyped-call]
|
now_ = pendulum.now().replace(microsecond=0) # type: ignore[no-untyped-call]
|
||||||
assert pendulum.parse(now).replace(microsecond=0) == now_ # type: ignore[attr-defined]
|
assert pendulum.parse(now).replace(microsecond=0) == now_ # type: ignore[attr-defined]
|
||||||
response = client.get("/datetime")
|
response = client.get("/datetime")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "HIT"
|
||||||
now = response.json().get("now")
|
now = response.json().get("now")
|
||||||
assert pendulum.parse(now).replace(microsecond=0) == now_ # type: ignore[attr-defined]
|
assert pendulum.parse(now).replace(microsecond=0) == now_ # type: ignore[attr-defined]
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
response = client.get("/datetime")
|
response = client.get("/datetime")
|
||||||
now = response.json().get("now")
|
now = response.json().get("now")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "MISS"
|
||||||
now = pendulum.parse(now).replace(microsecond=0) # type: ignore[attr-defined]
|
now = pendulum.parse(now).replace(microsecond=0) # type: ignore[attr-defined]
|
||||||
assert now != now_
|
assert now != now_
|
||||||
assert now == pendulum.now().replace(microsecond=0) # type: ignore[no-untyped-call]
|
assert now == pendulum.now().replace(microsecond=0) # type: ignore[no-untyped-call]
|
||||||
@@ -38,15 +41,18 @@ def test_date() -> None:
|
|||||||
"""Test path function without request or response arguments."""
|
"""Test path function without request or response arguments."""
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/date")
|
response = client.get("/date")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "MISS"
|
||||||
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
||||||
|
|
||||||
# do it again to test cache
|
# do it again to test cache
|
||||||
response = client.get("/date")
|
response = client.get("/date")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "HIT"
|
||||||
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
||||||
|
|
||||||
# now test with cache disabled, as that's a separate code path
|
# now test with cache disabled, as that's a separate code path
|
||||||
FastAPICache._enable = False # pyright: ignore[reportPrivateUsage]
|
FastAPICache._enable = False # pyright: ignore[reportPrivateUsage]
|
||||||
response = client.get("/date")
|
response = client.get("/date")
|
||||||
|
assert "X-FastAPI-Cache" not in response.headers
|
||||||
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
assert pendulum.parse(response.json()) == pendulum.today() # type: ignore[attr-defined]
|
||||||
FastAPICache._enable = True # pyright: ignore[reportPrivateUsage]
|
FastAPICache._enable = True # pyright: ignore[reportPrivateUsage]
|
||||||
|
|
||||||
@@ -72,6 +78,7 @@ def test_kwargs() -> None:
|
|||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
name = "Jon"
|
name = "Jon"
|
||||||
response = client.get("/kwargs", params={"name": name})
|
response = client.get("/kwargs", params={"name": name})
|
||||||
|
assert "X-FastAPI-Cache" not in response.headers
|
||||||
assert response.json() == {"name": name}
|
assert response.json() == {"name": name}
|
||||||
|
|
||||||
|
|
||||||
@@ -83,20 +90,25 @@ def test_method() -> None:
|
|||||||
|
|
||||||
def test_pydantic_model() -> None:
|
def test_pydantic_model() -> None:
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
r1 = client.get("/pydantic_instance").json()
|
r1 = client.get("/pydantic_instance")
|
||||||
r2 = client.get("/pydantic_instance").json()
|
assert r1.headers.get("X-FastAPI-Cache") == "MISS"
|
||||||
assert r1 == r2
|
r2 = client.get("/pydantic_instance")
|
||||||
|
assert r2.headers.get("X-FastAPI-Cache") == "HIT"
|
||||||
|
assert r1.json() == r2.json()
|
||||||
|
|
||||||
|
|
||||||
def test_non_get() -> None:
|
def test_non_get() -> None:
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.put("/uncached_put")
|
response = client.put("/uncached_put")
|
||||||
|
assert "X-FastAPI-Cache" not in response.headers
|
||||||
assert response.json() == {"value": 1}
|
assert response.json() == {"value": 1}
|
||||||
response = client.put("/uncached_put")
|
response = client.put("/uncached_put")
|
||||||
|
assert "X-FastAPI-Cache" not in response.headers
|
||||||
assert response.json() == {"value": 2}
|
assert response.json() == {"value": 2}
|
||||||
|
|
||||||
|
|
||||||
def test_alternate_injected_namespace() -> None:
|
def test_alternate_injected_namespace() -> None:
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/namespaced_injection")
|
response = client.get("/namespaced_injection")
|
||||||
|
assert response.headers.get("X-FastAPI-Cache") == "MISS"
|
||||||
assert response.json() == {"__fastapi_cache_request": 42, "__fastapi_cache_response": 17}
|
assert response.json() == {"__fastapi_cache_request": 42, "__fastapi_cache_response": 17}
|
||||||
|
|||||||
Reference in New Issue
Block a user