feat: frappe.utils.caching.site_cache

Decorator to cache method calls across requests. The cache is stored in
frappe.utils.caching._SITE_CACHE. The cache persists on the parent process.
It offers a light-weight cache for the current process without the additional
overhead of serializing / deserializing Python objects.

Note: This cache isn't shared among workers. If you need to share data across
workers, use redis (frappe.cache API) instead.

Usage:
    from frappe.utils.caching import site_cache

    @site_cache
    def calculate_pi():
        import math, time
        precision = get_precision("Math Constant", "Pi") # depends on
        site data
	return round(math.pi, precision)

    calculate_pi(10) # will calculate value
    calculate_pi(10) # will return value from cache
    calculate_pi.clear_cache() # clear this function's cache for all sites
    calculate_pi(10) # will calculate value
This commit is contained in:
gavin 2022-05-27 23:38:52 +05:30 committed by Gavin D'souza
parent db9b0d3d4f
commit 3be3b83c4f

View file

@ -2,17 +2,21 @@
# License: MIT. Check LICENSE
import json
from collections import defaultdict
from functools import wraps
from typing import Callable, Dict, Tuple
import frappe
_SITE_CACHE = defaultdict(lambda: defaultdict(dict))
def __generate_key(func, args, kwargs):
def __generate_request_cache_key(func: Callable, args: Tuple, kwargs: Dict):
"""Generate a key for the cache."""
return f"{func.__module__}.{func.__name__}{json.dumps((args, kwargs))}"
def request_cache(func):
def request_cache(func: Callable) -> Callable:
"""Decorator to cache function calls mid-request. Cache is stored in
frappe.local.request_cache. The cache only persists for the current request
and is cleared when the request is over. The function is called just once
@ -42,7 +46,7 @@ def request_cache(func):
logger = frappe.logger(module=__name__)
try:
key = __generate_key(func, args, kwargs)
key = __generate_request_cache_key(func, args, kwargs)
except Exception:
logger.warning(f"request_cache: Couldn't generate key for args: {args}, kwargs: {kwargs}")
return func(*args, **kwargs)
@ -54,3 +58,49 @@ def request_cache(func):
return frappe.local.request_cache[key]
return wrapper
def site_cache(func: Callable) -> Callable:
"""Decorator to cache method calls across requests. The cache is stored in
frappe.utils.caching._SITE_CACHE. The cache persists on the parent process.
It offers a light-weight cache for the current process without the additional
overhead of serializing / deserializing Python objects.
Note: This cache isn't shared among workers. If you need to share data across
workers, use redis (frappe.cache API) instead.
Usage:
from frappe.utils.caching import site_cache
@site_cache
def calculate_pi():
import math, time
precision = get_precision("Math Constant", "Pi") # depends on site data
return round(math.pi, precision)
calculate_pi(10) # will calculate value
calculate_pi(10) # will return value from cache
calculate_pi.clear_cache() # clear this function's cache for all sites
calculate_pi(10) # will calculate value
"""
func_key = f"{func.__module__}.{func.__name__}"
def clear_cache():
"""Clear cache for this function for all sites if not specified."""
_SITE_CACHE[func_key].clear()
func.clear_cache = clear_cache
@wraps(func)
def wrapper(*args, **kwargs):
if getattr(frappe.local, "initialised", None):
func_call_key = json.dumps((args, kwargs))
if func_call_key not in _SITE_CACHE[func_key][frappe.local.site]:
_SITE_CACHE[func_key][frappe.local.site][func_call_key] = func(*args, **kwargs)
return _SITE_CACHE[func_key][frappe.local.site][func_call_key]
return func(*args, **kwargs)
return wrapper