Repository: Kinto/kinto

Issue:
Should we allow cache entries without ttl?
Or define a max_ttl setting that is always applied ?
Should we allow cache entries without ttl?
Or define a max_ttl setting that is always applied ?


Discussion:

Resolving patch:
diff --git a/kinto/core/cache/__init__.py b/kinto/core/cache/__init__.py
--- a/kinto/core/cache/__init__.py
+++ b/kinto/core/cache/__init__.py
@@ -47,9 +47,8 @@ def expire(self, key, ttl):
         """
         raise NotImplementedError
 
-    def set(self, key, value, ttl=None):
-        """Store a value with the specified `key`. If `ttl` is provided,
-        set an expiration value.
+    def set(self, key, value, ttl):
+        """Store a value with the specified `key`.
 
         :param str key: key
         :param str value: value to store
diff --git a/kinto/core/cache/memory.py b/kinto/core/cache/memory.py
--- a/kinto/core/cache/memory.py
+++ b/kinto/core/cache/memory.py
@@ -59,15 +59,12 @@ def expire(self, key, ttl):
         self._ttl[self.prefix + key] = msec_time() + int(ttl * 1000.0)
 
     @synchronized
-    def set(self, key, value, ttl=None):
+    def set(self, key, value, ttl):
         if isinstance(value, bytes):
             raise TypeError("a string-like object is required, not 'bytes'")
         self._clean_expired()
         self._clean_oversized()
-        if ttl is not None:
-            self.expire(key, ttl)
-        else:
-            logger.warning("No TTL for cache key '{}'".format(key))
+        self.expire(key, ttl)
         item_key = self.prefix + key
         self._store[item_key] = value
         self._created_at[item_key] = msec_time()
diff --git a/kinto/core/cache/postgresql/__init__.py b/kinto/core/cache/postgresql/__init__.py
--- a/kinto/core/cache/postgresql/__init__.py
+++ b/kinto/core/cache/postgresql/__init__.py
@@ -123,12 +123,10 @@ def expire(self, key, ttl):
         with self.client.connect() as conn:
             conn.execute(query, dict(ttl=ttl, key=self.prefix + key))
 
-    def set(self, key, value, ttl=None):
+    def set(self, key, value, ttl):
         if isinstance(value, bytes):
             raise TypeError("a string-like object is required, not 'bytes'")
 
-        if ttl is None:
-            logger.warning("No TTL for cache key '{}'".format(key))
         query = """
         INSERT INTO cache (key, value, ttl)
         VALUES (:key, :value, sec2ttl(:ttl))


Test patch:
diff --git a/kinto/core/cache/testing.py b/kinto/core/cache/testing.py
--- a/kinto/core/cache/testing.py
+++ b/kinto/core/cache/testing.py
@@ -50,7 +50,7 @@ def test_backend_error_is_raised_anywhere(self):
             (self.cache.ttl, ''),
             (self.cache.expire, '', 0),
             (self.cache.get, ''),
-            (self.cache.set, '', ''),
+            (self.cache.set, '', '', 42),
             (self.cache.delete, ''),
         ]
         for call in calls:
@@ -87,13 +87,13 @@ def test_ping_logs_error_if_unavailable(self):
 
     def test_set_adds_the_record(self):
         stored = 'toto'
-        self.cache.set('foobar', stored)
+        self.cache.set('foobar', stored, 42)
         retrieved = self.cache.get('foobar')
         self.assertEquals(retrieved, stored)
 
     def test_values_remains_python_dict(self):
         def setget(k, v):
-            self.cache.set(k, v)
+            self.cache.set(k, v, 42)
             return (self.cache.get(k), v)
 
         self.assertEqual(*setget('foobar', 3))
@@ -103,10 +103,10 @@ def setget(k, v):
 
     def test_bytes_cannot_be_stored_in_the_cache(self):
         with pytest.raises(TypeError):
-            self.cache.set('test', b'foo')
+            self.cache.set('test', b'foo', 42)
 
     def test_delete_removes_the_record(self):
-        self.cache.set('foobar', 'toto')
+        self.cache.set('foobar', 'toto', 42)
         self.cache.delete('foobar')
         retrieved = self.cache.get('foobar')
         self.assertIsNone(retrieved)
@@ -115,7 +115,7 @@ def test_delete_does_not_fail_if_record_is_unknown(self):
         self.cache.delete('foobar')
 
     def test_expire_expires_the_value(self):
-        self.cache.set('foobar', 'toto')
+        self.cache.set('foobar', 'toto', 42)
         self.cache.expire('foobar', 0.01)
         time.sleep(0.02)
         retrieved = self.cache.get('foobar')
@@ -128,7 +128,7 @@ def test_set_with_ttl_expires_the_value(self):
         self.assertIsNone(retrieved)
 
     def test_ttl_return_the_time_to_live(self):
-        self.cache.set('foobar', 'toto')
+        self.cache.set('foobar', 'toto', 42)
         self.cache.expire('foobar', 10)
         ttl = self.cache.ttl('foobar')
         self.assertGreater(ttl, 0)
@@ -142,7 +142,7 @@ def test_cache_prefix_is_set(self):
         backend_prefix = self.get_backend_prefix(prefix='prefix_')
 
         # Set the value
-        backend_prefix.set('key', 'foo')
+        backend_prefix.set('key', 'foo', 42)
 
         # Validate that it was set with the prefix.
         obtained = self.cache.get('prefix_key')
@@ -152,7 +152,7 @@ def test_cache_when_prefix_is_not_set(self):
         backend_prefix = self.get_backend_prefix(prefix='')
 
         # Set a value
-        backend_prefix.set('key', 'foo')
+        backend_prefix.set('key', 'foo', 42)
 
         # Validate that it was set with no prefix
         obtained = self.cache.get('key')
@@ -162,7 +162,7 @@ def test_prefix_value_use_to_get_data(self):
         backend_prefix = self.get_backend_prefix(prefix='prefix_')
 
         # Set the value with the prefix
-        self.cache.set('prefix_key', 'foo')
+        self.cache.set('prefix_key', 'foo', 42)
 
         # Validate that the prefix was added
         obtained = backend_prefix.get('key')
@@ -171,7 +171,7 @@ def test_prefix_value_use_to_get_data(self):
     def test_prefix_value_use_to_delete_data(self):
         backend_prefix = self.get_backend_prefix(prefix='prefix_')
         # Set the value
-        self.cache.set('prefix_key', 'foo')
+        self.cache.set('prefix_key', 'foo', 42)
 
         # Delete the value
         backend_prefix.delete('key')
diff --git a/tests/core/test_cache.py b/tests/core/test_cache.py
--- a/tests/core/test_cache.py
+++ b/tests/core/test_cache.py
@@ -20,7 +20,7 @@ def test_mandatory_overrides(self):
             (self.cache.ttl, ''),
             (self.cache.expire, '', ''),
             (self.cache.get, ''),
-            (self.cache.set, '', ''),
+            (self.cache.set, '', '', 42),
             (self.cache.delete, ''),
         ]
         for call in calls:
@@ -67,11 +67,11 @@ def test_clean_expired_expires_items(self):
     def test_add_over_quota_clean_oversized_items(self):
         for x in range(100):
             # Each entry is 70 bytes
-            self.cache.set('foo{0:03d}'.format(x), 'toto')
+            self.cache.set('foo{0:03d}'.format(x), 'toto', 42)
             time.sleep(0.001)
         assert self.cache.get('foo000') == 'toto'
         # This should delete the 2 first entries
-        self.cache.set('foobar', 'tata')
+        self.cache.set('foobar', 'tata', 42)
         assert self.cache._quota == 7000 - 70 * 20
         assert self.cache.get('foo000') is None
         assert self.cache.get('foobar') == 'tata'
@@ -79,7 +79,7 @@ def test_add_over_quota_clean_oversized_items(self):
     def test_size_quota_can_be_set_to_zero(self):
         before = self.cache.max_size_bytes
         self.cache.max_size_bytes = 0
-        self.cache.set('foobar', 'tata')
+        self.cache.set('foobar', 'tata', 42)
         self.cache.max_size_bytes = before
         assert self.cache.get('foobar') == 'tata'