From 2822ab5d713c54c7b6bddd0560155252f9aafd84 Mon Sep 17 00:00:00 2001 From: "Charl P. Botha" Date: Fri, 14 Oct 2022 13:44:49 +0200 Subject: [PATCH 1/2] Factor out sync handling and use everywhere --- fastapi_cache/decorator.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fastapi_cache/decorator.py b/fastapi_cache/decorator.py index 150afcd..d24c565 100644 --- a/fastapi_cache/decorator.py +++ b/fastapi_cache/decorator.py @@ -62,13 +62,24 @@ def cache( nonlocal coder nonlocal expire nonlocal key_builder + + async def ensure_async_func(*args, **kwargs): + """Run cached sync functions in thread pool just like FastAPI.""" + if inspect.iscoroutinefunction(func): + # async, return as is + return func(*args, **kwargs) + else: + # sync, wrap in thread and return async + return run_in_threadpool(func, *args, **kwargs) + + copy_kwargs = kwargs.copy() request = copy_kwargs.pop("request", None) response = copy_kwargs.pop("response", None) if ( request and request.headers.get("Cache-Control") == "no-store" ) or not FastAPICache.get_enable(): - return await func(*args, **kwargs) + return await ensure_async_func(*args, **kwargs) coder = coder or FastAPICache.get_coder() expire = expire or FastAPICache.get_expire() @@ -82,12 +93,13 @@ def cache( if not request: if ret is not None: return coder.decode(ret) - ret = await func(*args, **kwargs) + ret = await ensure_async_func(*args, **kwargs) await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) return ret if request.method != "GET": - return await func(request, *args, **kwargs) + return await ensure_async_func(request, *args, **kwargs) + if_none_match = request.headers.get("if-none-match") if ret is not None: if response: @@ -102,10 +114,8 @@ def cache( kwargs.pop("request") if not response_param: kwargs.pop("response") - if inspect.iscoroutinefunction(func): - ret = await func(*args, **kwargs) - else: - ret = await run_in_threadpool(func, *args, **kwargs) + + ret = await ensure_async_func(*args, **kwargs) await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire()) return ret From af9c4d4c56c3654526d2d9cd0a45c62b0a3710e4 Mon Sep 17 00:00:00 2001 From: "Charl P. Botha" Date: Fri, 14 Oct 2022 13:44:59 +0200 Subject: [PATCH 2/2] Restore demo of sync handling --- examples/redis/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/redis/main.py b/examples/redis/main.py index 9c51d15..0a62277 100644 --- a/examples/redis/main.py +++ b/examples/redis/main.py @@ -41,11 +41,13 @@ async def get_data(request: Request, response: Response): return pendulum.today() +# Note: This function MUST be sync to demonstrate fastapi-cache's correct handling, +# i.e. running cached sync functions in threadpool just like FastAPI itself! @app.get("/blocking") @cache(namespace="test", expire=10) -async def blocking(): +def blocking(): time.sleep(5) - return dict(ret=await get_ret()) + return dict(ret=get_ret()) @app.get("/datetime")