Created
July 21, 2021 07:16
-
-
Save costastf/d83cf78ababd499f9a4e811660d92fc2 to your computer and use it in GitHub Desktop.
expiring dictionary
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2.7 | |
# -*- coding: UTF-8 -*- | |
# File: ExpiringDictionary.py | |
""" | |
Main module file | |
Put your classes here | |
""" | |
import collections | |
import logging | |
import logging.config | |
import logging.handlers | |
from datetime import datetime | |
__author__ = 'Costas Tyfoxylos <costas.tyf@gmail.com>' | |
__docformat__ = 'plaintext' | |
__date__ = '2016-01-29' | |
# This is the main prefix used for logging | |
LOGGER_BASENAME = 'ExpiringDictionary' | |
LOGGER = logging.getLogger(LOGGER_BASENAME) | |
LOGGER.setLevel(logging.DEBUG) | |
LOGGER.addHandler(logging.NullHandler()) | |
class ExpiringDictionary(collections.MutableMapping): | |
"""A dictionary that upon access can retire it's keys based on their time to live.""" | |
def __init__(self, retention=60, recursive_clean=False, aggressive=False): | |
""" | |
Initilizes the dictionary object. | |
:param retention: The time in seconds to keep the values | |
:param recursive_clean: A flag of whether the cleanup should be done recursively on getting of any keys | |
:param aggressive: A flag of whether the cleanup should be done aggressively on any access of the keys | |
""" | |
self.logger = logging.getLogger('{base}.{suffix}'.format(base=LOGGER_BASENAME, suffix=self.__class__.__name__)) | |
self._store = dict() | |
try: | |
self.__retention = int(retention) | |
except ValueError: | |
self.logger.error('Retention should be a number in seconds.') | |
raise | |
self.__recurse = recursive_clean | |
self.__aggressive = aggressive | |
def __getitem__(self, key): | |
""" | |
Implements the accessing of keys | |
:param key: The key to get from the dictionary | |
:return: The value of the key if the key exists and has not expired, None otherwise. | |
""" | |
self.logger.debug('Retrieving key :{key}'.format(key=key)) | |
self.__clean() | |
try: | |
value = self._store[key] | |
self.logger.debug('Got value :{value} checking for timestamp'.format(value=value)) | |
value = self.__check_timestamp(value) | |
if not value: | |
self.logger.debug('Timestamp expired. Removing key :{key}'.format(key=key)) | |
del self._store[key] | |
self.logger.debug('Retrieved value :{value}'.format(value=value)) | |
except KeyError: | |
self.logger.debug('Key not found :{key}'.format(key=key)) | |
value = None | |
return value | |
def __setitem__(self, key, value): | |
""" | |
Implements the setting of keys and values | |
:param key: The key to set on the dictionary | |
:param value: The value to set that key to | |
""" | |
if self.__aggressive: | |
self.logger.debug('Detected aggressive cleaning. Doing recursive cleaning.') | |
self.__clean() | |
self.logger.debug('Adding key :{key} with value :{value}'.format(key=key, value=value)) | |
key, value = self.__set_timestamp(key, value) | |
self._store[key] = value | |
def __delitem__(self, key): | |
""" | |
Implements the deletion of a key | |
:param key:The key to delete | |
""" | |
self.logger.debug('Removing key :{key}'.format(key=key)) | |
try: | |
del self._store[key] | |
except KeyError: | |
self.logger.error('Key :{key} does not exist. Cannot remove'.format(key=key)) | |
def __iter__(self): | |
""" | |
Implements the iteration of the items | |
:return: An iterator of the internal items of the dictionary | |
""" | |
return iter([value for value in iter(self._store) if value]) | |
def __len__(self): | |
""" | |
Implements the len method on the object | |
:return: The lenght of the dictionary | |
""" | |
return len(self._store) | |
def __check_timestamp(self, value): | |
""" | |
Checks for the validity of the value based on it's timestamp | |
:param value: The value to check | |
:return: The value if not expired, None otherwise | |
""" | |
output = None | |
now = datetime.now() | |
timestamp = value['timestamp'] | |
if (now - timestamp).total_seconds() <= self.__retention: | |
output = value['value'] | |
return output | |
@staticmethod | |
def __set_timestamp(key, value): | |
""" | |
Injects the timestamp in the value | |
:param key: The key of the dictionary | |
:param value: The value | |
:return: A key, value pair with the injected timestamp in the value | |
""" | |
return key, {'timestamp': datetime.now(), 'value': value} | |
def __clean(self): | |
""" | |
Recursively cleans all expired keys if the recurse flag is set | |
:return: True if recurse is set and everything is cleared, False otherwise | |
""" | |
outcome = False | |
if self.__recurse: | |
self.logger.debug('Cleaning recursively.') | |
for key, value in self._store.items(): | |
if not self.__check_timestamp(value): | |
self.logger.debug('Removing key :{key} with expired timestamp.'.format(key=key)) | |
del self._store[key] | |
outcome = True | |
return outcome |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment