Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ Create a dictionary with capacity for 100 elements and elements expiring in 10 s
from expiringdict import ExpiringDict
cache = ExpiringDict(max_len=100, max_age_seconds=10)

Create a dictionary with elements expiring in 10 seconds and anto refresh expired time when getitem:

.. code-block:: py

from expiringdict import ExpiringDict
cache = ExpiringDict(max_age_seconds=10, auto_refresh=True)

Create a dictionary with auto expired:

.. code-block:: py

from expiringdict import ExpiringDict
cache = ExpiringDict(max_age_seconds=10, auto_expired=True)

put and get a value there:

.. code-block:: py
Expand Down
26 changes: 23 additions & 3 deletions expiringdict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

import time
from threading import RLock
from threading import RLock,Thread
import sys
from typing import Any, Union

Expand All @@ -28,7 +28,7 @@


class ExpiringDict(OrderedDict):
def __init__(self, max_len, max_age_seconds, items=None):
def __init__(self, max_len, max_age_seconds, items=None, auto_refresh=False, auto_expired=False):
# type: (Union[int, None], Union[float, None], Union[None,dict,OrderedDict,ExpiringDict]) -> None

if not self.__is_instance_of_expiring_dict(items):
Expand All @@ -38,6 +38,7 @@ def __init__(self, max_len, max_age_seconds, items=None):
self.max_len = max_len
self.max_age = max_age_seconds
self.lock = RLock()
self.auto_refresh = auto_refresh

if sys.version_info >= (3, 5):
self._safe_keys = lambda: list(self.keys())
Expand All @@ -55,6 +56,21 @@ def __init__(self, max_len, max_age_seconds, items=None):
else:
raise ValueError('can not unpack items')

if auto_expired:
self.thread = Thread(target=self._expiring_key, args=())
self.thread.setDaemon(True)
self.thread.start()

def _expiring_key(self):
while True:
for key in self._safe_keys():
try:
self.__getitem__(key, expired_check=True)
except KeyError:
pass

time.sleep(0.1)

def __contains__(self, key):
""" Return True if the dict has a key, else return False. """
try:
Expand All @@ -68,7 +84,7 @@ def __contains__(self, key):
pass
return False

def __getitem__(self, key, with_age=False):
def __getitem__(self, key, with_age=False, expired_check=False):
""" Return the item of the dict.

Raises a KeyError if key is not in the map.
Expand All @@ -77,6 +93,10 @@ def __getitem__(self, key, with_age=False):
item = OrderedDict.__getitem__(self, key)
item_age = time.time() - item[1]
if item_age < self.max_age:
if self.auto_refresh and not expired_check:
set_time = time.time()
OrderedDict.__setitem__(self, key, (item[0], set_time))

if with_age:
return item[0], item_age
else:
Expand Down
37 changes: 36 additions & 1 deletion tests/expiringdict_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
from nose.tools import assert_raises, eq_, ok_

from expiringdict import ExpiringDict

try:
from collections import OrderedDict
except ImportError:
# Python < 2.7
from ordereddict import OrderedDict

def test_create():
assert_raises(AssertionError, ExpiringDict, max_len=1, max_age_seconds=-1)
Expand Down Expand Up @@ -51,6 +55,37 @@ def test_basics():
del d['e']
ok_('e' not in d)

def test_auto_refresh():
d = ExpiringDict(max_len=3, max_age_seconds=0.01, auto_refresh=True)
d['a'] = 'x'
eq_(d.get('a'), 'x')
sleep(0.005)
d['a']
sleep(0.005)
eq_(d.get('a'), 'x')
sleep(0.01)
eq_(d.get('a'), None)

d = ExpiringDict(max_len=3, max_age_seconds=0.01, auto_refresh=False)
d['a'] = 'x'
eq_(d.get('a'), 'x')
sleep(0.005)
d['a']
sleep(0.005)
eq_(d.get('a'), None)
sleep(0.01)
eq_(d.get('a'), None)

def test_auto_expired():
d = ExpiringDict(max_len=3, max_age_seconds=0.1, auto_expired=True)
d['a'] = 'x'
d['b'] = 'y'
sleep(1)
ok_('a' not in d._safe_keys())
ok_('b' not in d._safe_keys())
eq_(d.get('a'), None)
eq_(d.get('b'), None)


def test_pop():
d = ExpiringDict(max_len=3, max_age_seconds=0.01)
Expand Down