Skip to content
Open
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
1 change: 1 addition & 0 deletions src/memos/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def wrapper(*args, **kwargs):
if fallback is not None and callable(fallback):
result = fallback(e, *args, **kwargs)
return result
raise
finally:
elapsed_ms = (time.perf_counter() - start) * 1000.0

Expand Down
59 changes: 59 additions & 0 deletions tests/test_utils_timing_exception_reraise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Test that timed_with_status re-raises exceptions when no fallback is configured.

Regression test for issue #1523: timed_with_status decorator silently swallowed
exceptions and returned None when no fallback was provided, masking real errors.
"""

import pytest

from memos.utils import timed_with_status


class TestTimedWithStatusExceptionReraise:
"""Verify that exceptions are re-raised when no fallback is configured."""

def test_exception_reraised_when_no_fallback(self):
"""When no fallback is configured, exceptions should propagate to caller."""

@timed_with_status(log_prefix="test_func")
def failing_func():
raise ValueError("upstream error")

with pytest.raises(ValueError, match="upstream error"):
failing_func()

def test_exception_reraised_preserves_type(self):
"""The re-raised exception should preserve its original type."""

class CustomError(Exception):
pass

@timed_with_status()
def custom_error_func():
raise CustomError("specific error")

with pytest.raises(CustomError, match="specific error"):
custom_error_func()

def test_fallback_still_works(self):
"""When fallback is provided, it should still be called instead of re-raising."""

@timed_with_status(fallback=lambda exc, *args, **kwargs: "fallback_result")
def failing_with_fallback():
raise RuntimeError("error")

result = failing_with_fallback()
assert result == "fallback_result"

def test_no_implicit_none_return(self):
"""Decorated function should never return None on exception without fallback."""

@timed_with_status()
def fail_and_return():
raise KeyError("missing key")

# Should raise, not return None
with pytest.raises(KeyError):
result = fail_and_return()
# This line should never execute
assert result is not None, "Function returned None instead of raising"
Loading