初めに
Djangoには元々キャッシュ機能があり
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request): ...
こんな感じでデコレータを使うことでビューの結果をキャッシュできます
Django Ninjaと同じRESTフレームワークであるDjango REST Frameworkではこのcache_page
のデコレータが使えたのですがDjango Ninjaでは使えなかったので使えるデコレータを実装しました
コード
cache.py
from django.core.cache import cache
from typing import Any
from django.http import HttpRequest
from functools import wraps
class Cache:
@classmethod
def get(cls, key):
return cache.get(key)
@classmethod
def set(cls, key, value, time_out = 60 * 60):
cache.set(key, value, time_out)
def cache_request(timeout, *, cache_key = None):
def wrapper(func):
def inject(func):
@wraps(func)
def request(request: HttpRequest, **kwargs: Any) -> Any:
key = cache_key
if not key:
# パスとクエリをキャッシュキーにする
# クエリの並び順が変わっても同じキャッシュを使うためにソートしている
path_key = request.path
query_string_key = ""
if request.META.get("QUERY_STRING", ""):
query = {}
for query_string in request.META.get("QUERY_STRING", "").split('&'):
query_data = query_string.split('=')
query[query_data[0]] = query_data[1]
query = sorted(query.items())
query = dict((x, y) for x, y in query)
for k, v in query.items():
if query_string_key:
query_string_key += "&"
query_string_key += f"{k}={v}"
key = f"{path_key}?{query_string_key}"
if key:
cache = Cache.get(key)
if cache:
return cache
res = func(request, **kwargs)
if key:
Cache.set(key, res, timeout)
return res
return request
return inject(func)
return wrapper
使い方
前提としてDjangoのキャッシュが利用できる状態にしてください
その後こんな感じで利用できます
from ninja.pagination import paginate
from xxxx.cache import cache_request
router = Router()
@router.get("/")
@cache_request(60*60)
@paginate
def list(request): ...
一つ注意点としては@paginate
がある場合@paginate
よりも上に設定してください
下だとクエリにOFFSET、LIMITがつかない結果がキャッシュされます
キャッシュのキーはパスになっているので、何か他のものにしたい場合は、以下のように指定することもできます
@cache_request(60*60, cache_key='items')