from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Tuple from server.core.cache.keys import make_custom_key from server.core.utils import get_id class BaseResult(ABC): """Base class for cache result handlers.""" def __init__( self, collection_name: str = None, key_getter: Callable[[dict], str or int] = get_id, key_options: Tuple = None, result_items_key: str = None, ): """Init instance. Args: collection_name: Collection name. key_getter: Callable which returns string key (from data dictionary returned by wrapped function). key_options: Set of collection names and key getters, to put under several keys in cache. result_items_key: if result has items packed in dict this is items key, return dict of items even for one. """ self.key_options = key_options if key_options else ((collection_name, key_getter),) self.collection_name, self.key_getter = self.key_options[0] self.result_items_key = result_items_key @abstractmethod def map_data(self, data: Any, key_getter: Callable) -> Dict: """Process function result to create key to data mapping. :param data: Function result. :param key_getter: Callable which returns string key (from data dictionary returned by wrapped function). :return Key to data mapping. """ @abstractmethod def process_result(self, cached_data: Dict, new_data: Dict = None) -> List or Dict: """Process cached and new data to form final resultset. :param cached_data: Id to data mapping from cache. :param new_data: Id to data mapping from function call and map_data. :return Final function call resultset. """ def get_results( self, call_result: Dict or List, cached_data: Dict, key_parts: List ) -> Tuple[Dict[str, Dict[str, Tuple[str, dict] or Tuple[int, dict]]], List or Dict]: """Get mapping for caching and combined result (cache + func call for missing). :param call_result: Function call result. :param cached_data: Data from cache. :param key_parts: Call args key parts. """ caching_map = {} new_data_main = None for collection_name, key_getter in self.key_options: new_data = self.map_data(call_result, key_getter) if new_data_main is None: new_data_main = new_data if new_data: caching_map[collection_name] = { make_custom_key(key_value=key, key_args_params=key_parts): (key, value) for key, value in new_data.items() } return caching_map, self.process_result(cached_data, new_data_main) def get_keys(self, id_list: List[str] or List[int], key_parts: List) -> List[str]: """Get list of cache keys. :param id_list: List of object IDs. :param key_parts: List of function args key parts. :return List of keys. """ return [make_custom_key(key_value=key_value, key_args_params=key_parts) for key_value in id_list] @staticmethod def replace_args(args, arg_index, keys): """Set ID chunk to args. :param args: Function args to replace in. :param arg_index: Position in args. :param keys: Value to set in args. :return Changed args. """ args_list = list(args) args_list[arg_index] = keys return tuple(args_list) class SingleResult(BaseResult): def map_data(self, data: Any, key_getter: Callable) -> Dict: if self.result_items_key: data = data[self.result_items_key] if isinstance(data, list): data = data[0] key = key_getter(data) if not key: return {} return {key: data} def process_result(self, cached_data: Dict, new_data: Dict = None) -> List or Dict: result = next(iter(new_data.values() if new_data else cached_data.values())) if self.result_items_key: return {self.result_items_key: [result]} return result @staticmethod def replace_args(args, arg_index, keys): args_list = list(args) args_list[arg_index] = keys[0] return tuple(args_list) class ListResult(BaseResult): def map_data(self, data: List or Dict, key_getter: Callable) -> Dict: if self.result_items_key and self.result_items_key in data: data = data[self.result_items_key] result = {} # could be some records with the same key for item in data: if item is not None: key = key_getter(item) if key: result[key] = item return result def process_result(self, cached_data: Dict, new_data: Dict = None) -> List or Dict: result = list(cached_data.values()) if new_data: result = result + list(new_data.values()) if self.result_items_key: return {self.result_items_key: result} return result