seitime-frappe/frappe/utils/caching.py
Ankush Menat 81b37cb7d2
refactor: clean up code to py310 supported features (#17367)
refactor: clean up code to py39+ supported syntax

- f-strings instead of format
- latest typing support instead of pre 3.9 TitleCase
- remove UTF-8 declarations.
- many more changes

Powered by https://github.com/asottile/pyupgrade/ + manual cleanups
2022-07-01 11:51:05 +05:30

130 lines
4.1 KiB
Python

# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. Check LICENSE
import json
from collections import defaultdict
from datetime import datetime, timedelta
from functools import wraps
from typing import Callable
import frappe
_SITE_CACHE = defaultdict(lambda: defaultdict(dict))
def __generate_request_cache_key(args: tuple, kwargs: dict):
"""Generate a key for the cache."""
if not kwargs:
return hash(args)
return hash((args, frozenset(kwargs.items())))
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
per request with the same set of (kw)arguments.
Usage:
from frappe.utils.caching import request_cache
@request_cache
def calculate_pi(num_terms=0):
import math, time
print(f"{num_terms = }")
time.sleep(10)
return math.pi
calculate_pi(10) # will calculate value
calculate_pi(10) # will return value from cache
"""
@wraps(func)
def wrapper(*args, **kwargs):
if not getattr(frappe.local, "initialised", None):
return func(*args, **kwargs)
if not hasattr(frappe.local, "request_cache"):
frappe.local.request_cache = defaultdict(dict)
try:
args_key = __generate_request_cache_key(args, kwargs)
except Exception:
return func(*args, **kwargs)
try:
return frappe.local.request_cache[func][args_key]
except KeyError:
return_val = func(*args, **kwargs)
frappe.local.request_cache[func][args_key] = return_val
return return_val
return wrapper
def site_cache(ttl: int | None = None, maxsize: int | None = None) -> 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
"""
def time_cache_wrapper(func: Callable = None) -> Callable:
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
if ttl is not None and not callable(ttl):
func.ttl = ttl
func.expiration = datetime.utcnow() + timedelta(seconds=func.ttl)
if maxsize is not None and not callable(maxsize):
func.maxsize = maxsize
@wraps(func)
def site_cache_wrapper(*args, **kwargs):
if getattr(frappe.local, "initialised", None):
func_call_key = json.dumps((args, kwargs))
if hasattr(func, "ttl") and datetime.utcnow() >= func.expiration:
func.clear_cache()
func.expiration = datetime.utcnow() + timedelta(seconds=func.ttl)
if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][frappe.local.site]) >= func.maxsize:
_SITE_CACHE[func_key][frappe.local.site].pop(
next(iter(_SITE_CACHE[func_key][frappe.local.site])), None
)
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 site_cache_wrapper
if callable(ttl):
return time_cache_wrapper(ttl)
return time_cache_wrapper