Merge branch 'long2ice:main' into fix/piclke-coder

This commit is contained in:
Ivan Moiseev
2023-01-11 16:26:05 +03:00
committed by GitHub
7 changed files with 51 additions and 14 deletions

View File

@@ -2,14 +2,16 @@
## 0.2 ## 0.2
### 0.2.1
- Support cache jinja2 template response.
### 0.2.0 ### 0.2.0
- Make `request` and `response` optional. - Make `request` and `response` optional.
- Add typing info to the `cache` decorator. - Add typing info to the `cache` decorator.
- 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.1 ## 0.1

View File

@@ -2,7 +2,7 @@ import pendulum
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import JSONResponse, Response
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend from fastapi_cache.backends.inmemory import InMemoryBackend
@@ -51,6 +51,12 @@ def sync_me():
return 42 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") @app.on_event("startup")
async def startup(): async def startup():
FastAPICache.init(InMemoryBackend()) FastAPICache.init(InMemoryBackend())

View File

@@ -9,7 +9,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from redis.asyncio.connection import ConnectionPool from redis.asyncio.connection import ConnectionPool
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import JSONResponse, Response
from fastapi_cache import FastAPICache from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend from fastapi_cache.backends.redis import RedisBackend
@@ -73,6 +73,12 @@ async def cache_html(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "ret": await get_ret()}) 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") @app.on_event("startup")
async def startup(): async def startup():
pool = ConnectionPool.from_url(url="redis://redis") pool = ConnectionPool.from_url(url="redis://redis")

View File

@@ -7,6 +7,7 @@ from typing import Any
import pendulum import pendulum
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from starlette.responses import JSONResponse
from starlette.templating import _TemplateResponse as TemplateResponse from starlette.templating import _TemplateResponse as TemplateResponse
CONVERTERS = { CONVERTERS = {
@@ -52,6 +53,8 @@ class Coder:
class JsonCoder(Coder): class JsonCoder(Coder):
@classmethod @classmethod
def encode(cls, value: Any) -> str: def encode(cls, value: Any) -> str:
if isinstance(value, JSONResponse):
return value.body
return json.dumps(value, cls=JsonEncoder) return json.dumps(value, cls=JsonEncoder)
@classmethod @classmethod

View File

@@ -77,9 +77,9 @@ def cache(
# if the wrapped function does NOT have request or response in its function signature, # 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 # make sure we don't pass them in as keyword arguments
if not request_param: if not request_param:
kwargs.pop("request") kwargs.pop("request", None)
if not response_param: if not response_param:
kwargs.pop("response") kwargs.pop("response", None)
if inspect.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
# async, return as is. # async, return as is.
@@ -123,13 +123,20 @@ def cache(
args=args, args=args,
kwargs=copy_kwargs, kwargs=copy_kwargs,
) )
try:
ttl, ret = await backend.get_with_ttl(cache_key) ttl, ret = await backend.get_with_ttl(cache_key)
except ConnectionError:
ttl, ret = 0, None
if not request: if not request:
if ret is not None: if ret is not None:
return coder.decode(ret) return coder.decode(ret)
ret = await ensure_async_func(*args, **kwargs) ret = await ensure_async_func(*args, **kwargs)
await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) try:
await backend.set(
cache_key, coder.encode(ret), expire or FastAPICache.get_expire()
)
except ConnectionError:
pass
return ret return ret
if request.method != "GET": if request.method != "GET":
@@ -148,7 +155,10 @@ def cache(
ret = await ensure_async_func(*args, **kwargs) ret = await ensure_async_func(*args, **kwargs)
try:
await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire())
except ConnectionError:
pass
return ret return ret
return inner return inner

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "fastapi-cache2" name = "fastapi-cache2"
version = "0.2.1" version = "0.2.0"
description = "Cache for FastAPI" description = "Cache for FastAPI"
authors = ["long2ice <long2ice@gmail.com>"] authors = ["long2ice <long2ice@gmail.com>"]
license = "Apache-2.0" license = "Apache-2.0"

View File

@@ -57,3 +57,13 @@ def test_sync() -> None:
with TestClient(app) as client: with TestClient(app) as client:
response = client.get("/sync-me") response = client.get("/sync-me")
assert response.json() == 42 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")