Merge pull request #425 from PaleNeutron/fix-no-cache

fix #424, no-cache should store the result to cache
This commit is contained in:
long2ice
2024-07-24 21:46:19 +08:00
committed by GitHub
4 changed files with 42 additions and 19 deletions

View File

@@ -114,6 +114,15 @@ async def uncached_put():
put_ret = put_ret + 1 put_ret = put_ret + 1
return {"value": put_ret} return {"value": put_ret}
put_ret2 = 0
@app.get("/cached_put")
@cache(namespace="test", expire=5)
async def cached_put():
global put_ret2
put_ret2 = put_ret2 + 1
return {"value": put_ret2}
@app.get("/namespaced_injection") @app.get("/namespaced_injection")
@cache(namespace="test", expire=5, injected_dependency_namespace="monty_python") @cache(namespace="test", expire=5, injected_dependency_namespace="monty_python")

View File

@@ -15,12 +15,15 @@ from typing import (
import pendulum import pendulum
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from pydantic import BaseConfig, ValidationError, fields
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from starlette.templating import ( from starlette.templating import (
_TemplateResponse as TemplateResponse, # pyright: ignore[reportPrivateUsage] _TemplateResponse as TemplateResponse, # pyright: ignore[reportPrivateUsage]
) )
class ModelField:
pass
_T = TypeVar("_T", bound=type) _T = TypeVar("_T", bound=type)
@@ -69,7 +72,7 @@ class Coder:
# decode_as_type method and then stores a different kind of field for a # decode_as_type method and then stores a different kind of field for a
# given type, do make sure that the subclass provides its own class # given type, do make sure that the subclass provides its own class
# attribute for this cache. # attribute for this cache.
_type_field_cache: ClassVar[Dict[Any, fields.ModelField]] = {} _type_field_cache: ClassVar[Dict[Any, ModelField]] = {}
@overload @overload
@classmethod @classmethod
@@ -89,18 +92,6 @@ class Coder:
""" """
result = cls.decode(value) result = cls.decode(value)
if type_ is not None:
try:
field = cls._type_field_cache[type_]
except KeyError:
field = cls._type_field_cache[type_] = fields.ModelField(
name="body", type_=type_, class_validators=None, model_config=BaseConfig
)
result, errors = field.validate(result, {}, loc=())
if errors is not None:
if not isinstance(errors, list):
errors = [errors]
raise ValidationError(errors, type_)
return result return result

View File

@@ -72,7 +72,7 @@ def _uncacheable(request: Optional[Request]) -> bool:
Returns true if: Returns true if:
- Caching has been disabled globally - Caching has been disabled globally
- This is not a GET request - This is not a GET request
- The request has a Cache-Control header with a value of "no-store" or "no-cache" - The request has a Cache-Control header with a value of "no-store"
""" """
if not FastAPICache.get_enable(): if not FastAPICache.get_enable():
@@ -81,7 +81,7 @@ def _uncacheable(request: Optional[Request]) -> bool:
return False return False
if request.method != "GET": if request.method != "GET":
return True return True
return request.headers.get("Cache-Control") in ("no-store", "no-cache") return request.headers.get("Cache-Control") == "no-store"
def cache( def cache(
@@ -182,7 +182,7 @@ def cache(
) )
ttl, cached = 0, None ttl, cached = 0, None
if cached is None: # cache miss if cached is None or (request is not None and request.headers.get("Cache-Control") == "no-cache") : # cache miss
result = await ensure_async_func(*args, **kwargs) result = await ensure_async_func(*args, **kwargs)
to_cache = coder.encode(result) to_cache = coder.encode(result)

View File

@@ -99,10 +99,10 @@ def test_pydantic_model() -> None:
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("/cached_put")
assert "X-FastAPI-Cache" not in response.headers 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("/cached_put")
assert "X-FastAPI-Cache" not in response.headers assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 2} assert response.json() == {"value": 2}
@@ -112,3 +112,26 @@ def test_alternate_injected_namespace() -> None:
response = client.get("/namespaced_injection") response = client.get("/namespaced_injection")
assert response.headers.get("X-FastAPI-Cache") == "MISS" 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}
def test_cache_control() -> None:
with TestClient(app) as client:
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# HIT
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# no-cache
response = client.get("/cached_put", headers={"Cache-Control": "no-cache"})
assert response.json() == {"value": 2}
response = client.get("/cached_put")
assert response.json() == {"value": 2}
# no-store
response = client.get("/cached_put", headers={"Cache-Control": "no-store"})
assert response.json() == {"value": 3}
response = client.get("/cached_put")
assert response.json() == {"value": 2}