Skip to content

Commit

Permalink
[#23] chained exceptions (and improved coverage)
Browse files Browse the repository at this point in the history
  • Loading branch information
zmumi committed May 4, 2024
1 parent 5c3ddde commit 6315b64
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 20 deletions.
6 changes: 3 additions & 3 deletions memoize/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def refresh(actual_entry: Optional[CacheEntry], key: CacheKey,
logger.debug('As entry expired, waiting for results of concurrent refresh %s', key)
entry = await update_statuses.await_updated(key)
if isinstance(entry, Exception):
raise CachedMethodFailedException('Concurrent refresh failed to complete', entry)
raise CachedMethodFailedException('Concurrent refresh failed to complete') from entry
return entry
elif actual_entry is not None and update_statuses.is_being_updated(key):
logger.debug('As update point reached but concurrent update already in progress, '
Expand All @@ -104,11 +104,11 @@ async def refresh(actual_entry: Optional[CacheEntry], key: CacheKey,
except (asyncio.TimeoutError, _timeout_error_type()) as e:
logger.debug('Timeout for %s: %s', key, e)
update_statuses.mark_update_aborted(key, e)
raise CachedMethodFailedException('Refresh timed out', e)
raise CachedMethodFailedException('Refresh timed out') from e
except Exception as e:
logger.debug('Error while refreshing cache for %s: %s', key, e)
update_statuses.mark_update_aborted(key, e)
raise CachedMethodFailedException('Refresh failed to complete', e)
raise CachedMethodFailedException('Refresh failed to complete') from e

@functools.wraps(method)
async def wrapper(*args, **kwargs):
Expand Down
4 changes: 2 additions & 2 deletions tests/asynciotests/test_showcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@ async def get_value_or_throw(arg, kwarg=None):
self.assertEqual('ok #2', res4) # value from cache - still relevant
self.assertEqual('ok #2', res5) # stale from cache - refresh in background
self.assertEqual('ok #2', res6) # stale from cache - should be updated but method throws
expected = CachedMethodFailedException('Refresh failed to complete', ValueError('throws #4', ))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(str(context.exception), str(CachedMethodFailedException('Refresh failed to complete')))
self.assertEqual(str(context.exception.__cause__), str(ValueError("throws #4")))
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from memoize.coerced import _timeout_error_type
from tests.py310workaround import fix_python_3_10_compatibility

fix_python_3_10_compatibility()
Expand Down Expand Up @@ -274,8 +275,8 @@ async def get_value(arg, kwarg=None):
await get_value_cached('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh failed to complete', ValueError('Get lost', ))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(str(context.exception), str(CachedMethodFailedException('Refresh failed to complete')))
self.assertEqual(str(context.exception.__cause__), str(ValueError("Get lost")))

@gen_test
async def test_should_throw_exception_on_refresh_timeout(self):
Expand All @@ -295,8 +296,8 @@ async def get_value(arg, kwarg=None):
await get_value_cached('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh timed out', TimeoutError('Timeout'))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())

@staticmethod
async def _call_thrice(call):
Expand Down
54 changes: 47 additions & 7 deletions tests/asynciotests/test_wrapper_on_asyncio.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from memoize.coerced import _timeout_error_type
from tests.py310workaround import fix_python_3_10_compatibility

fix_python_3_10_compatibility()
Expand Down Expand Up @@ -177,13 +178,51 @@ async def get_value(arg, kwarg=None):
# then
with self.assertRaises(Exception) as context:
await res1
self.assertEqual(context.exception, ValueError('stub0'))
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(str(context.exception.__cause__), str(ValueError('stub0')))

with self.assertRaises(Exception) as context:
await res2
self.assertEqual(context.exception, ValueError('stub0'))
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(str(context.exception.__cause__), str(ValueError('stub0')))

with self.assertRaises(Exception) as context:
await res3
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(str(context.exception.__cause__), str(ValueError('stub0')))

@gen_test
async def test_should_return_timeout_for_all_concurrent_callers(self):
# given
value = 0

@memoize(configuration=DefaultInMemoryCacheConfiguration(method_timeout=timedelta(milliseconds=1)))
async def get_value(arg, kwarg=None):
await _ensure_asyncio_background_tasks_finished()
time.sleep(.200)
await _ensure_asyncio_background_tasks_finished()
return value

# when
res1 = get_value('test', kwarg='args1')
res2 = get_value('test', kwarg='args1')
res3 = get_value('test', kwarg='args1')

# then
with self.assertRaises(Exception) as context:
await res1
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())

with self.assertRaises(Exception) as context:
await res2
self.assertEqual(context.exception, ValueError('stub0'))
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())

with self.assertRaises(Exception) as context:
await res3
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())

@gen_test
async def test_should_return_same_value_on_constant_key_function(self):
Expand Down Expand Up @@ -278,8 +317,8 @@ async def get_value(arg, kwarg=None):
await get_value('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh failed to complete', ValueError('Get lost', ))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(str(context.exception), str(CachedMethodFailedException('Refresh failed to complete')))
self.assertEqual(str(context.exception.__cause__), str(ValueError("Get lost")))

@gen_test
async def test_should_throw_exception_on_refresh_timeout(self):
Expand All @@ -297,8 +336,9 @@ async def get_value(arg, kwarg=None):
await get_value('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh timed out', TimeoutError('Timeout'))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())


@staticmethod
async def _call_thrice(call):
Expand Down
10 changes: 6 additions & 4 deletions tests/tornadotests/test_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from memoize.coerced import _timeout_error_type
from tests.py310workaround import fix_python_3_10_compatibility

fix_python_3_10_compatibility()
Expand Down Expand Up @@ -265,8 +266,8 @@ def get_value(arg, kwarg=None):
yield get_value('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh failed to complete', ValueError('Get lost', ))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(str(context.exception), str(CachedMethodFailedException('Refresh failed to complete')))
self.assertEqual(str(context.exception.__cause__), str(ValueError("Get lost")))

@gen_test
def test_should_throw_exception_on_refresh_timeout(self):
Expand All @@ -285,5 +286,6 @@ def get_value(arg, kwarg=None):
yield get_value('test1', kwarg='args1')

# then
expected = CachedMethodFailedException('Refresh timed out', TimeoutError('Timeout'))
self.assertEqual(str(expected), str(context.exception)) # ToDo: consider better comparision
self.assertEqual(context.exception.__class__, CachedMethodFailedException)
self.assertEqual(context.exception.__cause__.__class__, _timeout_error_type())

0 comments on commit 6315b64

Please sign in to comment.