changeset 8dd832481bb8 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=8dd832481bb8
description:
        Add duration on Cache

        issue8249
        review283181002
diffstat:

 CHANGELOG                   |   1 +
 doc/ref/cache.rst           |  19 ++++++++++---------
 trytond/cache.py            |  28 ++++++++++++++++++++++------
 trytond/tests/test_cache.py |   9 +++++++++
 4 files changed, 42 insertions(+), 15 deletions(-)

diffs (133 lines):

diff -r d85963a57040 -r 8dd832481bb8 CHANGELOG
--- a/CHANGELOG Thu Apr 11 15:17:30 2019 +0200
+++ b/CHANGELOG Thu Apr 11 18:35:25 2019 +0200
@@ -1,3 +1,4 @@
+* Add duration on Cache
 * Add strip wildcards helpers
 * Add list-form view
 * Do not set id for on_change calls if cached
diff -r d85963a57040 -r 8dd832481bb8 doc/ref/cache.rst
--- a/doc/ref/cache.rst Thu Apr 11 15:17:30 2019 +0200
+++ b/doc/ref/cache.rst Thu Apr 11 18:35:25 2019 +0200
@@ -5,16 +5,17 @@
 Cache
 =====
 
-.. class:: Cache(name[, size_limit[, context]])
+.. class:: Cache(name[, size_limit[, duration[, context]]])
 
-The class is used to cache values between server requests. The `name` should
-be unique and it's used to identify the cache. We usually use
-`<class_name>.<content_name>` to make it unique. The `size_limit` field can
-be used to limit the number of values cached and the `context` parameter
-is used to indicate if the cache depends on the user context and is true
-by default.
-The cache is cleaned on :class:`Transaction` starts and resets on
-:class:`Transaction` commit or rollback.
+The class is used to cache values between server requests. The `name` should be
+unique and it's used to identify the cache. We usually use
+`<class_name>.<content_name>` to make it unique. The `size_limit` parameter can
+be used to limit the number of values cached and it has 1024 as the default
+value.  The `duration` parameter defines how long a cached value stays valid
+but if it is not set the value remains valid until it is cleared.  And the
+`context` parameter is used to indicate if the cache depends on the user
+context and is true by default.  The cache is cleaned on :class:`Transaction`
+starts and resets on :class:`Transaction` commit or rollback.
 
 .. warning::
     As there is no deepcopy of the values cached, they must never be mutated
diff -r d85963a57040 -r 8dd832481bb8 trytond/cache.py
--- a/trytond/cache.py  Thu Apr 11 15:17:30 2019 +0200
+++ b/trytond/cache.py  Thu Apr 11 18:35:25 2019 +0200
@@ -1,5 +1,6 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+import datetime as dt
 import json
 import logging
 import select
@@ -44,10 +45,18 @@
 class BaseCache(object):
     _instances = {}
 
-    def __init__(self, name, size_limit=1024, context=True):
+    def __init__(self, name, size_limit=1024, duration=None, context=True):
         self._name = name
         self.size_limit = size_limit
         self.context = context
+        if isinstance(duration, dt.timedelta):
+            self.duration = duration
+        elif isinstance(duration, (int, float)):
+            self.duration = dt.timedelta(seconds=duration)
+        elif duration:
+            self.duration = dt.timedelta(**duration)
+        else:
+            self.duration = None
         assert self._name not in self._instances
         self._instances[self._name] = self
 
@@ -94,9 +103,9 @@
     _table = 'ir_cache'
     _channel = _table
 
-    def __init__(self, name, size_limit=1024, context=True):
-        super(MemoryCache, self).__init__(name, size_limit, context)
-        self._database_cache = defaultdict(lambda: LRUDict(size_limit))
+    def __init__(self, *args, **kwargs):
+        super(MemoryCache, self).__init__(*args, **kwargs)
+        self._database_cache = defaultdict(lambda: LRUDict(self.size_limit))
         self._transaction_cache = WeakKeyDictionary()
         self._transaction_lower = {}
         self._timestamp = {}
@@ -120,7 +129,10 @@
         key = self._key(key)
         cache = self._get_cache()
         try:
-            result = cache[key] = cache.pop(key)
+            (expire, result) = cache.pop(key)
+            if expire and expire < dt.datetime.now():
+                return default
+            cache[key] = (expire, result)
             return result
         except (KeyError, TypeError):
             return default
@@ -128,8 +140,12 @@
     def set(self, key, value):
         key = self._key(key)
         cache = self._get_cache()
+        if self.duration:
+            expire = dt.datetime.now() + self.duration
+        else:
+            expire = None
         try:
-            cache[key] = value
+            cache[key] = (expire, value)
         except TypeError:
             pass
         return value
diff -r d85963a57040 -r 8dd832481bb8 trytond/tests/test_cache.py
--- a/trytond/tests/test_cache.py       Thu Apr 11 15:17:30 2019 +0200
+++ b/trytond/tests/test_cache.py       Thu Apr 11 18:35:25 2019 +0200
@@ -12,6 +12,7 @@
 
 
 cache = MemoryCache('test.cache')
+cache_expire = MemoryCache('test.cache_expire', duration=1)
 
 
 class CacheTestCase(unittest.TestCase):
@@ -134,6 +135,14 @@
         self.addCleanup(transaction3.stop)
         self.assertEqual(cache.get('foo'), None)
 
+    @with_transaction()
+    def test_memory_cache_expire(self):
+        "Test expired cache"
+        cache_expire.set('foo', "bar")
+        time.sleep(cache_expire.duration.total_seconds())
+
+        self.assertEqual(cache_expire.get('foo'), None)
+
 
 @unittest.skipIf(backend.name() == 'sqlite', "SQLite has not channel")
 class MemoryCacheChannelTestCase(MemoryCacheTestCase):

Reply via email to