fromcollections.abcimportCallablefromfunctoolsimportpartialfromtypingimportAnyfrommastermind.storage.pickle_ioimport(delete_pickled_data,read_pickled_data,write_pickled_data,)_user_data=None# Variable to hold user data dictionary
[docs]classUserDataManager:""" Manages user data with a customizable data storage interface. You should not use this class directly. Use get_user_data_manager() instead. This class wraps a data dictionary and provides methods to modify, retrieve, and clear user data, while ensuring that changes are saved through a provided save function. """def__init__(self,data:dict,save_fn:Callable[[dict],None])->None:""" Decorate the given data dictionary with the UserDataManager interface. Args: data (dict): The data dictionary to be decorated. save_fn (Callable[dict, None]): A function that saves the data dictionary. """super().__setattr__("_data",data)super().__setattr__("save_data",lambda:save_fn(data=self._data))
[docs]defclear_all(self)->None:"""Clears all the user data and saves the changes."""self._data.clear()# clear method ensure _data instance is the sameself.save_data()
def_retrieve_item(self,key:str)->Any:""" Retrieves the value associated with the given key, or None if the key does not exist. Args: key (str): The key to retrieve the value for. Returns: Any: The value associated with the key, or None if the key does not exist. """returnself._data.get(key,None)def_modify_item(self,key:str,value:Any)->None:""" Modify the value associated with the given key in the internal dictionary. If the key is one of the instance attribute, it modify that instead. After modifying the internal dictionary, it saves the changes to the file. Args: key (str): The key to modify the value for. value (Any): The new value to associate with the key. """ifkeyin{"_data","save_data"}andhasattr(self,key):raiseNotImplementedError(f"Modification of {key} attribute is forbidden.")self._data[key]=valueself.save_data()def__contains__(self,key:str)->bool:returnkeyinself._data# Allow dot and bracket notation for accessing and modifying datadef__getattr__(self,key:str)->Any:returnself._retrieve_item(key)def__getitem__(self,key:str)->Any:returnself._retrieve_item(key)def__setattr__(self,key:str,value:Any)->None:self._modify_item(key,value)def__setitem__(self,key:str,value:Any)->None:self._modify_item(key,value)
def_load_data_safely(filepath:str)->dict:# sourcery skip: extract-duplicate-method"""Loads the pickled data from the specified file path. If the file doesn't exist, it return an empty dictionary. This method is 'safe' because it handles exceptions and provides a user-friendly error message. Args: filepath (str): The path to the file to load data from. Raises: RuntimeError: When the data to be loaded is corrupted. """try:data=read_pickled_data(filepath)returndataifdataisnotNoneelse{}exceptExceptionase:print("An unexpected error occurred while loading the data.")print(e)print("\nIf this issue persists, consider deleting the stored data.")ifnot_prompt_delete_data(filepath):raiseRuntimeError("Data could not be loaded.")fromereturn{}def_prompt_delete_data(filepath:str)->bool:"""Prompts the user to delete the stored data. Args: filepath (str): The path to the file to delete. Returns: bool: Whether the user wants to delete the stored data. """decision=input("Do you want to delete the stored data? (y/n): ")ifdecision.lower()!="y":returnFalsedelete_pickled_data(filepath)print("Data deleted successfully.")returnTruedef_update_user_data(filepath:str):"""Load user data and update user_data variable if not initialized."""global_user_data# global ensures only 1 user_data exists at any timeif"_user_data"notinglobals()or_user_dataisNone:_user_data=_load_data_safely(filepath)
[docs]defget_user_data_manager()->UserDataManager:"""Returns a new UserDataManager instance."""filepath="data/user_data.pkl"_update_user_data(filepath)# ensure UserDataManager always use the same user_datareturnUserDataManager(_user_data,partial(write_pickled_data,filepath))