[docs]classConfiguration(BaseSettings):"""Defines environment variables used to configure the local file cache handler. This should also include any appropriate default values for fields which are not required. """path:str=Field(description="The path to the directory to write cache data to.",)
[docs]classConfig:"""Allow environment variable override of configuration fields. This also enforce a prefix for all environment variables for this handler. As an example the field `path` would be set using the environment variable `GROVE_CACHE_LOCAL_FILE_PATH`. """env_prefix="GROVE_CACHE_LOCAL_FILE_"case_insensitive=True
[docs]classHandler(BaseCache):"""A local file backed cache for pointers and other Grove data."""def__init__(self):self.logger=logging.getLogger(__name__)# Wrap validation errors to keep them in the Grove exception hierarchy.try:self.config=Configuration()# type: ignoreexceptValidationErroraserr:raiseConfigurationException(parsing.validation_error(err))
[docs]defget(self,pk:str,sk:str)->str:"""Retrieve a value from a local file backed cache with the given key. :param pk: Partition key of the value to retrieve. :param sk: Sort key of the value to retrieve. :raises NotFoundException: No value was found. :return: Value from the cache. """path=os.path.join(self.config.path,CACHE_PATH.format(pk=pk,sk=sk))value=Nonetry:os.makedirs(os.path.dirname(path),exist_ok=True)withopen(path,"r")ashndl:value=hndl.read()exceptFileNotFoundError:# If the file isn't found, we treat this as the cache is empty for this# PK / SK, so we can just drop through and let the is None handler take# care of this for us.passexceptOSErroraserr:raiseAccessException(f"Unable to read cache entry from {path}. {err}")ifvalueisNone:self.logger.info("No value found in cache",extra={"pk":pk,"sk":sk})raiseNotFoundException("No value found in cache")returnvalue
[docs]defset(self,pk:str,sk:str,value:str,not_set:bool=False,constraint:Optional[str]=None,):"""Stores the value for the given key in a local file backed cache. :param pk: Partition key of the value to save. :param sk: Sort key of the value to save. :param value: Value to save. :param not_set: Specifies whether the value must not already be set in the cache for the set to be successful. :param constraint: An optional condition to use set operation. If provided, the currently cached value must match for the delete to be successful. :raises ValueError: An incompatible set of parameters were provided. :raises DataFormatException: The provided constraint was not satisfied. """current=Nonepath=os.path.join(self.config.path,CACHE_PATH.format(pk=pk,sk=sk))ifconstraintisnotNoneandnot_set:raiseValueError("A value cannot both have a constraint AND not be set.")# First check if the value is set, and if so whether the caller requires that it# NOT already be set.try:current=self.get(pk,sk)exceptNotFoundException:passifcurrentandnot_set:raiseDataFormatException("Value is already set in cache")# Next check if the constraint is met.ifconstraintisnotNoneandcurrent!=constraint:raiseDataFormatException("Current value in cache did not match constraint")# Finally, set the value.try:os.makedirs(os.path.dirname(path),exist_ok=True)withopen(path,"w")ashndl:hndl.truncate()hndl.write(value)exceptOSErroraserr:raiseAccessException(f"Unable to write cache entry to {path}. {err}")
[docs]defdelete(self,pk:str,sk:str,constraint:Optional[str]=None):"""Deletes an entry from local file backed cache that has the given key. :param pk: Partition key of the value to delete. :param sk: Sort key of the value to delete. :param constraint: An optional condition to use during the delete. The value provided as the condition must match for the delete to be successful. :raises DataFormatException: The provided constraint was not satisfied. """# To enforce constraints, we first need the current value - if any.current=Nonepath=os.path.join(self.config.path,CACHE_PATH.format(pk=pk,sk=sk))try:current=self.get(pk,sk)exceptNotFoundException:pass# Next check if the constraint is met.ifconstraintisnotNoneandcurrent!=constraint:raiseDataFormatException("Current value in cache did not match constraint")try:os.unlink(path)exceptFileNotFoundError:passexceptOSErroraserr:raiseAccessException(f"Unable to delete cache entry from {path}. {err}")