Package: c27cache
C27CacheC27Cache is a simple HTTP caching library designed to work withFastAPIInstallationWhile C27Cache is still early in it's development, it's been used in production on a couple of services.With pippipinstallc27cacheWith Poetrypoetryaddc27cacheUsage and ExamplesBasic UsageInitialize C27Cachefromc27cache.configimportC27Cachefrompytzimporttimezoneasia_kolkata=timezone('Asia/Kolkata')C27Cache.init(redis_url=REDIS_URL,namespace='test_namespace',tz=asia_kolkata)Thetzattribute becomes import when thecachedecorator relies on theexpire_end_of_dayandexpire_end_of_weekattributes to expire the cache key.Define your controllersThettl_in_secondsexpires the cache in 180 seconds. There are other approaches to take with helpers likeexpire_end_of_dayandexpires_end_of_weekimportredisfromdatetimeimportdatetimefromfastapiimportFastAPI,Request,Responsefromfastapi.responsesimportJSONResponsefromc27cacheimportC27Cache,cache,invalidate_cache@app.get("/b/home")@cache(key="b.home",ttl_in_seconds=180)asyncdefhome(request:Request,response:Response):returnJSONResponse({"page":"home","datetime":str(datetime.utcnow())})@app.get("/b/welcome")@cache(key="b.home",end_of_week=True)asyncdefhome(request:Request,response:Response):returnJSONResponse({"page":"welcome","datetime":str(datetime.utcnow())})Building keys from parameter objects.While it's always possible to explicitly pass keys onto thekeyattribute, there are scenarios where the keys need to be built based on the parameters received by the controller method. For instance, in an authenticated API where theuser_idis fetched as a controllerDependsargument.@app.get("/b/logged-in")@cache(key="b.logged_in.{}",obj="user",obj_attr="id")asyncdeflogged_in(request:Request,response:Response,user=user):returnJSONResponse({"page":"home","user":user.id,"datetime":str(datetime.utcnow())})In the example above, the key allows room for a dynamic attribute fetched from the objectuser. The key eventually becomesb.logged_in.112358if theuser.idreturns112358Explicitly invalidating the cacheThe cache invalidation can be managed using the@invalidate_cachedecorator.@app.post("/b/logged-in")@invalidate_cache(key="b.logged_in.{}",obj="user",obj_attr="id",namespace="test_namespace")asyncdefpost_logged_in(request:Request,response:Response,user=user):returnJSONResponse({"page":"home","user":user.id,"datetime":str(datetime.utcnow())})Invalidating more than one key at a timeThe cache invalidation decorator allows for multiple keys to be invalidated in the same call. However, the it assumes that the object attributes generated apply all keys.@app.post("/b/logged-in")@invalidate_cache(keys=["b.logged_in.{}","b.profile.{}"],obj="user",obj_attr="id",namespace="test_namespace")asyncdefpost_logged_in(request:Request,response:Response,user=user):returnJSONResponse({"page":"home","user":user.id,"datetime":str(datetime.utcnow())})Full ExampleimportosimportredisfromdatetimeimportdatetimefromfastapiimportFastAPI,Request,Responsefromfastapi.responsesimportJSONResponsefromc27cacheimportC27Cache,cache,invalidate_cacheREDIS_URL=os.environ.get("REDIS_URL","redis://localhost:6379/3")redis_client=redis.Redis.from_url(REDIS_URL)classUser:id:str="112358"user=User()app=FastAPI()C27Cache.init(redis_url=REDIS_URL,namespace='test_namespace')@app.get("/b/home")@cache(key="b.home",ttl_in_seconds=180)asyncdefhome(request:Request,response:Response):returnJSONResponse({"page":"home","datetime":str(datetime.utcnow())})@app.get("/b/logged-in")@cache(key="b.logged_in.{}",obj="user",obj_attr="id")asyncdeflogged_in(request:Request,response:Response,user=user):returnJSONResponse({"page":"home","user":user.id,"datetime":str(datetime.utcnow())})@app.post("/b/logged-in")@invalidate_cache(key="b.logged_in.{}",obj="user",obj_attr="id",namespace="test_namespace")asyncdefpost_logged_in(request:Request,response:Response,user=user):returnJSONResponse({"page":"home","user":user.id,"datetime":str(datetime.utcnow())})Computingttldynamically for cache keys using aCallableA callable can be passed as part of the decorator to dynamically compute what the ttl for a cache key should be. For exampleasyncdefmy_ttl_callable()->int:return3600@app.get('/b/ttl_callable')@cache(key='b.ttl_callable_expiry',ttl_func=my_ttl_callable)asyncdefpath_with_ttl_callable(request:Request,response:Response):returnJSONResponse({"page":"path_with_ttl_callable","datetime":str(datetime.utcnow())})Thettl_funcis always assumed to be anasyncmethodCaching methods that aren't controllersC27Cache works exactly the same way with regular methods. The example below explains usage of the cache in service objects and application services.importosimportredisfromc27cacheimportC27Cache,cache,invalidate_cacheREDIS_URL=os.environ.get("REDIS_URL","redis://localhost:6379/3")redis_client=redis.Redis.from_url(REDIS_URL)classUser:id:str="112358"user=User()C27Cache.init(redis_url=REDIS_URL,namespace='test_namespace')@cache(key='cache.me',ttl_in_seconds=360)asyncdefcache_me(x:int,invoke_count:int):invoke_count=invoke_count+1result=x*2return[result,invoke_count]