Skip to content

Commit

Permalink
Merge pull request #425 from PaleNeutron/fix-no-cache
Browse files Browse the repository at this point in the history
fix #424, no-cache should store the result to cache
  • Loading branch information
long2ice authored Jul 24, 2024
2 parents 00d34e4 + 8b5601c commit 701d83f
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 19 deletions.
9 changes: 9 additions & 0 deletions examples/in_memory/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ async def uncached_put():
put_ret = put_ret + 1
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")
@cache(namespace="test", expire=5, injected_dependency_namespace="monty_python")
Expand Down
19 changes: 5 additions & 14 deletions fastapi_cache/coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@

import pendulum
from fastapi.encoders import jsonable_encoder
from pydantic import BaseConfig, ValidationError, fields
from starlette.responses import JSONResponse
from starlette.templating import (
_TemplateResponse as TemplateResponse, # pyright: ignore[reportPrivateUsage]
)


class ModelField:
pass

_T = TypeVar("_T", bound=type)


Expand Down Expand Up @@ -69,7 +72,7 @@ def decode(cls, value: bytes) -> Any:
# 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
# attribute for this cache.
_type_field_cache: ClassVar[Dict[Any, fields.ModelField]] = {}
_type_field_cache: ClassVar[Dict[Any, ModelField]] = {}

@overload
@classmethod
Expand All @@ -89,18 +92,6 @@ def decode_as_type(cls, value: bytes, *, type_: Optional[_T]) -> Union[_T, Any]:
"""
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


Expand Down
6 changes: 3 additions & 3 deletions fastapi_cache/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def _uncacheable(request: Optional[Request]) -> bool:
Returns true if:
- Caching has been disabled globally
- 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():
Expand All @@ -81,7 +81,7 @@ def _uncacheable(request: Optional[Request]) -> bool:
return False
if request.method != "GET":
return True
return request.headers.get("Cache-Control") in ("no-store", "no-cache")
return request.headers.get("Cache-Control") == "no-store"


def cache(
Expand Down Expand Up @@ -182,7 +182,7 @@ async def ensure_async_func(*args: P.args, **kwargs: P.kwargs) -> R:
)
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)
to_cache = coder.encode(result)

Expand Down
27 changes: 25 additions & 2 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ def test_pydantic_model() -> None:

def test_non_get() -> None:
with TestClient(app) as client:
response = client.put("/uncached_put")
response = client.put("/cached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 1}
response = client.put("/uncached_put")
response = client.put("/cached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 2}

Expand All @@ -112,3 +112,26 @@ def test_alternate_injected_namespace() -> None:
response = client.get("/namespaced_injection")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
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}

0 comments on commit 701d83f

Please sign in to comment.