Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added expire command #97

Merged
merged 1 commit into from
Dec 29, 2016
Merged
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
10 changes: 10 additions & 0 deletions aiocache/backends/memcached.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ async def _exists(self, key):
"""
return await self.client.append(key, b'')

async def _expire(self, key, ttl):
"""
Expire the given key in ttl seconds. If ttl is 0, remove the expiration

:param key: str key to expire
:param ttl: int number of seconds for expiration. If 0, ttl is disabled
:returns: True if set, False if key is not found
"""
return await self.client.touch(key, ttl)

async def _delete(self, key):
"""
Deletes the given key.
Expand Down
18 changes: 18 additions & 0 deletions aiocache/backends/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ async def _exists(self, key):
"""
return key in SimpleMemoryBackend._cache

async def _expire(self, key, ttl):
"""
Expire the given key in ttl seconds. If ttl is 0, remove the expiration

:param key: str key to expire
:param ttl: int number of seconds for expiration. If 0, ttl is disabled
:returns: True if set, False if key is not found
"""
if key in SimpleMemoryBackend._cache:
handle = SimpleMemoryBackend._handlers.pop(key, None)
if handle:
handle.cancel()
loop = asyncio.get_event_loop()
SimpleMemoryBackend._handlers[key] = loop.call_later(ttl, self.__delete, key)
return True

return False

async def _delete(self, key):
"""
Deletes the given key.
Expand Down
11 changes: 11 additions & 0 deletions aiocache/backends/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ async def _exists(self, key):
exists = await redis.exists(key)
return True if exists > 0 else False

async def _expire(self, key, ttl):
"""
Expire the given key in ttl seconds. If ttl is 0, remove the expiration

:param key: str key to expire
:param ttl: int number of seconds for expiration. If 0, ttl is disabled
:returns: True if set, False if key is not found
"""
with await self._connect() as redis:
return await redis.expire(key, ttl)

async def _delete(self, key):
"""
Deletes the given key.
Expand Down
20 changes: 20 additions & 0 deletions aiocache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,26 @@ async def exists(self, key, namespace=None):
async def _exists(self, key):
raise NotImplementedError()

@plugin_pipeline
async def expire(self, key, ttl, namespace=None):
"""
Set the ttl to the given key. By setting it to 0, it will disable it

:param key: str key to expire
:param ttl: int number of seconds for expiration. If 0, ttl is disabled
:param namespace: str alternative namespace to use
:returns: True if set, False if key is not found
"""
with Timeout(self._timeout):
start = time.time()
ns_key = self._build_key(key, namespace=namespace)
ret = await self._expire(ns_key, ttl)
logger.debug("EXPIRE %s %d (%.4f)s", ns_key, ret, time.time() - start)
return ret

async def _expire(self, key, ttl):
raise NotImplementedError()

@plugin_pipeline
async def clear(self, namespace=None):
"""
Expand Down
2 changes: 2 additions & 0 deletions aiocache/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class BasePlugin:
'multi_set',
'delete',
'exists',
'expire',
'clear',
'raw',
]
Expand All @@ -46,6 +47,7 @@ class BasePlugin:
'add': True,
'delete': 0,
'exists': False,
'expire': True,
'clear': True,
'raw': None,
}
Expand Down
15 changes: 13 additions & 2 deletions tests/integration/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ async def test_multi_set(self, cache):
async def test_multi_set_with_ttl(self, cache):
pairs = [(pytest.KEY, "value"), [pytest.KEY_1, "random_value"]]
assert await cache.multi_set(pairs, ttl=1) is True
await asyncio.sleep(2)
await asyncio.sleep(1.1)

assert await cache.multi_get([pytest.KEY, pytest.KEY_1]) == [None, None]

@pytest.mark.asyncio
async def test_set_with_ttl(self, cache):
await cache.set(pytest.KEY, "value", ttl=1)
await asyncio.sleep(2)
await asyncio.sleep(1.1)

assert await cache.get(pytest.KEY) is None

Expand Down Expand Up @@ -178,6 +178,17 @@ async def test_exists_existing(self, cache):
await cache.set(pytest.KEY, "value")
assert await cache.exists(pytest.KEY) is True

@pytest.mark.asyncio
async def test_expire_existing(self, cache):
await cache.set(pytest.KEY, "value")
assert await cache.expire(pytest.KEY, 1) is True
await asyncio.sleep(1.1)
assert await cache.exists(pytest.KEY) is False

@pytest.mark.asyncio
async def test_expire_missing(self, cache):
assert await cache.expire(pytest.KEY, 1) is False

@pytest.mark.asyncio
async def test_clear(self, cache):
await cache.set(pytest.KEY, "value")
Expand Down
1 change: 1 addition & 0 deletions tests/ut/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class MockCache(BaseCache):
_multi_set = asynctest.CoroutineMock()
_delete = asynctest.CoroutineMock()
_exists = asynctest.CoroutineMock()
_expire = asynctest.CoroutineMock()
_clear = asynctest.CoroutineMock()
_raw = asynctest.CoroutineMock()

Expand Down
22 changes: 22 additions & 0 deletions tests/ut/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ async def test_exists(self, base_cache):
with pytest.raises(NotImplementedError):
await base_cache.exists(pytest.KEY)

@pytest.mark.asyncio
async def test_expire(self, base_cache):
with pytest.raises(NotImplementedError):
await base_cache.expire(pytest.KEY, 0)

@pytest.mark.asyncio
async def test_clear(self, base_cache):
with pytest.raises(NotImplementedError):
Expand Down Expand Up @@ -175,6 +180,23 @@ async def test_delete_timeouts(self, mock_cache):
with pytest.raises(asyncio.TimeoutError):
await mock_cache.delete(pytest.KEY)

@pytest.mark.asyncio
async def test_expire(self, mock_cache):
await mock_cache.expire(pytest.KEY, 1)
mock_cache._expire.assert_called_with(mock_cache._build_key(pytest.KEY), 1)

@pytest.mark.asyncio
async def test_expire_timeouts(self, mock_cache):
mock_cache._expire = asynctest.CoroutineMock(side_effect=asyncio.sleep(0.005))

with pytest.raises(asyncio.TimeoutError):
await mock_cache.expire(pytest.KEY, 0)

@pytest.mark.asyncio
async def test_clear(self, mock_cache):
await mock_cache.clear(pytest.KEY)
mock_cache._clear.assert_called_with(mock_cache._build_key(pytest.KEY))

@pytest.mark.asyncio
async def test_clear_timeouts(self, mock_cache):
mock_cache._clear = asynctest.CoroutineMock(side_effect=asyncio.sleep(0.005))
Expand Down