diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c7187..49a047f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### 0.2.1 - Support cache jinja2 template response. +- Support cache `JSONResponse` +- Add `py.typed` file and type hints +- Add TestCase +- Fix cache decorate sync function - Transparently handle backend connection failures. ### 0.2.0 diff --git a/examples/in_memory/main.py b/examples/in_memory/main.py index e7164ba..471899e 100644 --- a/examples/in_memory/main.py +++ b/examples/in_memory/main.py @@ -2,7 +2,7 @@ import pendulum import uvicorn from fastapi import FastAPI from starlette.requests import Request -from starlette.responses import Response +from starlette.responses import Response, JSONResponse from fastapi_cache import FastAPICache from fastapi_cache.backends.inmemory import InMemoryBackend @@ -51,6 +51,12 @@ def sync_me(): return 42 +@app.get("/cache_response_obj") +@cache(namespace="test", expire=5) +async def cache_response_obj(): + return JSONResponse({"a": 1}) + + @app.on_event("startup") async def startup(): FastAPICache.init(InMemoryBackend()) diff --git a/examples/redis/main.py b/examples/redis/main.py index 434acd5..19a272a 100644 --- a/examples/redis/main.py +++ b/examples/redis/main.py @@ -4,6 +4,7 @@ import pendulum import redis.asyncio as redis import uvicorn from fastapi import FastAPI +from starlette.responses import JSONResponse from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates @@ -73,6 +74,12 @@ async def cache_html(request: Request): return templates.TemplateResponse("index.html", {"request": request, "ret": await get_ret()}) +@app.get("/cache_response_obj") +@cache(namespace="test", expire=5) +async def cache_response_obj(): + return JSONResponse({"a": 1}) + + @app.on_event("startup") async def startup(): pool = ConnectionPool.from_url(url="redis://redis") diff --git a/fastapi_cache/coder.py b/fastapi_cache/coder.py index e8f55a1..66ef94f 100644 --- a/fastapi_cache/coder.py +++ b/fastapi_cache/coder.py @@ -6,6 +6,7 @@ from typing import Any import pendulum from fastapi.encoders import jsonable_encoder +from starlette.responses import JSONResponse from starlette.templating import _TemplateResponse as TemplateResponse CONVERTERS = { @@ -51,6 +52,8 @@ class Coder: class JsonCoder(Coder): @classmethod def encode(cls, value: Any) -> str: + if isinstance(value, JSONResponse): + return value.body return json.dumps(value, cls=JsonEncoder) @classmethod diff --git a/fastapi_cache/decorator.py b/fastapi_cache/decorator.py index bf1ccff..bcfe9b5 100644 --- a/fastapi_cache/decorator.py +++ b/fastapi_cache/decorator.py @@ -77,9 +77,9 @@ def cache( # if the wrapped function does NOT have request or response in its function signature, # make sure we don't pass them in as keyword arguments if not request_param: - kwargs.pop("request") + kwargs.pop("request", None) if not response_param: - kwargs.pop("response") + kwargs.pop("response", None) if inspect.iscoroutinefunction(func): # async, return as is. diff --git a/tests/test_decorator.py b/tests/test_decorator.py index b118cde..0eb96c1 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -57,3 +57,13 @@ def test_sync() -> None: with TestClient(app) as client: response = client.get("/sync-me") assert response.json() == 42 + + +def test_cache_response_obj() -> None: + with TestClient(app) as client: + cache_response = client.get("cache_response_obj") + assert cache_response.json() == {"a": 1} + get_cache_response = client.get("cache_response_obj") + assert get_cache_response.json() == {"a": 1} + assert get_cache_response.headers.get("cache-control") + assert get_cache_response.headers.get("etag")