Add duration on Cache

issue8249
review283181002
This commit is contained in:
Cédric Krier
2019-04-11 18:35:25 +02:00
parent 49b7238d3f
commit 4b4c57a362
4 changed files with 42 additions and 15 deletions
+1
View File
@@ -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
+10 -9
View File
@@ -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
+22 -6
View File
@@ -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 @@ def freeze(o):
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 @@ class MemoryCache(BaseCache):
_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 @@ class MemoryCache(BaseCache):
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 @@ class MemoryCache(BaseCache):
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
+9
View File
@@ -12,6 +12,7 @@ from trytond.transaction import Transaction
cache = MemoryCache('test.cache')
cache_expire = MemoryCache('test.cache_expire', duration=1)
class CacheTestCase(unittest.TestCase):
@@ -134,6 +135,14 @@ class MemoryCacheTestCase(unittest.TestCase):
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):