11# pyright: reportGeneralTypeIssues=false
2+ from collections import defaultdict
23from contextlib import asynccontextmanager
3- from typing import AsyncIterator , Dict , Optional
4+ from typing import Annotated , AsyncIterator , Dict , List , Optional
45
56import pendulum
67import uvicorn
7- from fastapi import FastAPI
8+ from fastapi import Body , FastAPI , Query
89from fastapi_cache import FastAPICache
910from fastapi_cache .backends .inmemory import InMemoryBackend
10- from fastapi_cache .decorator import cache
11+ from fastapi_cache .decorator import cache , cache_invalidator
12+ from fastapi_cache .tag_provider import TagProvider
1113from pydantic import BaseModel
1214from starlette .requests import Request
1315from starlette .responses import JSONResponse , Response
@@ -65,7 +67,7 @@ async def get_kwargs(name: str):
6567
6668
6769@app .get ("/sync-me" )
68- @cache (namespace = "test" ) # pyright: ignore[reportArgumentType]
70+ @cache (namespace = "test" ) # pyright: ignore[reportArgumentType]
6971def sync_me ():
7072 # as per the fastapi docs, this sync function is wrapped in a thread,
7173 # thereby converted to async. fastapi-cache does the same.
@@ -115,8 +117,10 @@ async def uncached_put():
115117 put_ret = put_ret + 1
116118 return {"value" : put_ret }
117119
120+
118121put_ret2 = 0
119122
123+
120124@app .get ("/cached_put" )
121125@cache (namespace = "test" , expire = 5 )
122126async def cached_put ():
@@ -126,7 +130,7 @@ async def cached_put():
126130
127131
128132@app .get ("/namespaced_injection" )
129- @cache (namespace = "test" , expire = 5 , injected_dependency_namespace = "monty_python" ) # pyright: ignore[reportArgumentType]
133+ @cache (namespace = "test" , expire = 5 , injected_dependency_namespace = "monty_python" ) # pyright: ignore[reportArgumentType]
130134def namespaced_injection (
131135 __fastapi_cache_request : int = 42 , __fastapi_cache_response : int = 17
132136) -> Dict [str , int ]:
@@ -136,5 +140,65 @@ def namespaced_injection(
136140 }
137141
138142
143+ # Note: examples with cache invalidation
144+ files = defaultdict (
145+ list ,
146+ {
147+ 1 : [1 , 2 , 3 ],
148+ 2 : [4 , 5 , 6 ],
149+ 3 : [100 ],
150+ },
151+ )
152+
153+ FileTagProvider = TagProvider ("file" )
154+
155+
156+ # Note: providing tags for future granular cache invalidation
157+ @app .get ("/files" )
158+ @cache (expire = 10 , tag_provider = FileTagProvider )
159+ async def get_files (file_id_in : Annotated [Optional [List [int ]], Query ()] = None ):
160+ return [
161+ {"id" : k , "value" : v }
162+ for k , v in files .items ()
163+ if (True if not file_id_in else k in file_id_in )
164+ ]
165+
166+
167+ # Note: here we're retrieving keys by file_id, so we also need to invalidate this, when file changes
168+ @app .get ("/files/{file_id:int}" )
169+ @cache (
170+ expire = 10 ,
171+ tag_provider = FileTagProvider ,
172+ items_provider = lambda data , method_args , method_kwargs : [
173+ {"id" : method_kwargs ["file_id" ]}
174+ ],
175+ )
176+ async def get_file_keys (file_id : int ):
177+ if file_id in files :
178+ return files [file_id ]
179+ return Response ("file id not found" )
180+
181+
182+ # Note: here we can use default invalidator, because in response we have :id:
183+ @app .patch ("/files/{file_id:int}" )
184+ @cache_invalidator (tag_provider = FileTagProvider )
185+ async def edit_file (file_id : int , items : Annotated [List [int ], Body (embed = True )]):
186+ files [file_id ] = items
187+ return {
188+ "id" : file_id ,
189+ "value" : files [file_id ]
190+ }
191+
192+
193+ # Note: here we need to use custom :invalidator: because we don't have access to identifier in response
194+ @app .delete ("/files/{file_id:int}" )
195+ @cache_invalidator (
196+ tag_provider = FileTagProvider , invalidator = lambda resp , kwargs : kwargs ["file_id" ]
197+ )
198+ async def delete_file (file_id : int ):
199+ if file_id in files :
200+ del files [file_id ]
201+
202+
139203if __name__ == "__main__" :
140204 uvicorn .run ("main:app" , reload = True )
0 commit comments