Skip to content

Instantly share code, notes, and snippets.

@costastf
Created July 21, 2021 07:16
Show Gist options
  • Save costastf/d83cf78ababd499f9a4e811660d92fc2 to your computer and use it in GitHub Desktop.
Save costastf/d83cf78ababd499f9a4e811660d92fc2 to your computer and use it in GitHub Desktop.
expiring dictionary
#!/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