Skip to content

Commit c038ad4

Browse files
authored
Merge pull request #72 from redis/fix/fix-client-error-handling
Fix client error handling
2 parents ceba13f + 4cd4cc0 commit c038ad4

File tree

3 files changed

+65
-15
lines changed

3 files changed

+65
-15
lines changed

agent-memory-client/agent_memory_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
memory management capabilities for AI agents and applications.
66
"""
77

8-
__version__ = "0.12.2"
8+
__version__ = "0.12.3"
99

1010
from .client import MemoryAPIClient, MemoryClientConfig, create_memory_client
1111
from .exceptions import (

agent-memory-client/agent_memory_client/client.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import logging # noqa: F401
99
import re
1010
from collections.abc import AsyncIterator, Sequence
11-
from typing import TYPE_CHECKING, Any, Literal, TypedDict
11+
from typing import TYPE_CHECKING, Any, Literal, NoReturn, TypedDict
1212

1313
if TYPE_CHECKING:
1414
from typing_extensions import Self
@@ -149,8 +149,11 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
149149
"""Close the client when exiting the context manager."""
150150
await self.close()
151151

152-
def _handle_http_error(self, response: httpx.Response) -> None:
153-
"""Handle HTTP errors and convert to appropriate exceptions."""
152+
def _handle_http_error(self, response: httpx.Response) -> NoReturn:
153+
"""Handle HTTP errors and convert to appropriate exceptions.
154+
155+
This method always raises an exception and never returns normally.
156+
"""
154157
if response.status_code == 404:
155158
from .exceptions import MemoryNotFoundError
156159

@@ -162,6 +165,10 @@ def _handle_http_error(self, response: httpx.Response) -> None:
162165
except Exception:
163166
message = f"HTTP {response.status_code}: {response.text}"
164167
raise MemoryServerError(message, response.status_code)
168+
# This should never be reached, but mypy needs to know this never returns
169+
raise MemoryServerError(
170+
f"Unexpected status code: {response.status_code}", response.status_code
171+
)
165172

166173
async def health_check(self) -> HealthCheckResponse:
167174
"""
@@ -176,7 +183,6 @@ async def health_check(self) -> HealthCheckResponse:
176183
return HealthCheckResponse(**response.json())
177184
except httpx.HTTPStatusError as e:
178185
self._handle_http_error(e.response)
179-
raise
180186

181187
async def list_sessions(
182188
self,
@@ -215,7 +221,6 @@ async def list_sessions(
215221
return SessionListResponse(**response.json())
216222
except httpx.HTTPStatusError as e:
217223
self._handle_http_error(e.response)
218-
raise
219224

220225
async def get_working_memory(
221226
self,
@@ -291,7 +296,6 @@ async def get_working_memory(
291296
return WorkingMemoryResponse(**response_data)
292297
except httpx.HTTPStatusError as e:
293298
self._handle_http_error(e.response)
294-
raise
295299

296300
async def get_or_create_working_memory(
297301
self,
@@ -454,7 +458,6 @@ async def put_working_memory(
454458
return WorkingMemoryResponse(**response.json())
455459
except httpx.HTTPStatusError as e:
456460
self._handle_http_error(e.response)
457-
raise
458461

459462
async def delete_working_memory(
460463
self, session_id: str, namespace: str | None = None, user_id: str | None = None
@@ -487,7 +490,6 @@ async def delete_working_memory(
487490
return AckResponse(**response.json())
488491
except httpx.HTTPStatusError as e:
489492
self._handle_http_error(e.response)
490-
raise
491493

492494
async def set_working_memory_data(
493495
self,
@@ -678,7 +680,6 @@ async def create_long_term_memory(
678680
return AckResponse(**response.json())
679681
except httpx.HTTPStatusError as e:
680682
self._handle_http_error(e.response)
681-
raise
682683

683684
async def delete_long_term_memories(self, memory_ids: Sequence[str]) -> AckResponse:
684685
"""
@@ -701,7 +702,6 @@ async def delete_long_term_memories(self, memory_ids: Sequence[str]) -> AckRespo
701702
return AckResponse(**response.json())
702703
except httpx.HTTPStatusError as e:
703704
self._handle_http_error(e.response)
704-
raise
705705

706706
async def get_long_term_memory(self, memory_id: str) -> MemoryRecord:
707707
"""
@@ -722,7 +722,6 @@ async def get_long_term_memory(self, memory_id: str) -> MemoryRecord:
722722
return MemoryRecord(**response.json())
723723
except httpx.HTTPStatusError as e:
724724
self._handle_http_error(e.response)
725-
raise
726725

727726
async def edit_long_term_memory(
728727
self, memory_id: str, updates: dict[str, Any]
@@ -749,7 +748,6 @@ async def edit_long_term_memory(
749748
return MemoryRecord(**response.json())
750749
except httpx.HTTPStatusError as e:
751750
self._handle_http_error(e.response)
752-
raise
753751

754752
async def search_long_term_memory(
755753
self,
@@ -900,7 +898,6 @@ async def search_long_term_memory(
900898
return MemoryRecordResults(**data)
901899
except httpx.HTTPStatusError as e:
902900
self._handle_http_error(e.response)
903-
raise
904901

905902
# === LLM Tool Integration ===
906903

@@ -2957,7 +2954,6 @@ async def memory_prompt(
29572954
return {"response": result}
29582955
except httpx.HTTPStatusError as e:
29592956
self._handle_http_error(e.response)
2960-
raise
29612957

29622958
async def hydrate_memory_prompt(
29632959
self,

agent-memory-client/tests/test_client.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,60 @@ def test_validation_with_none_values(self, enhanced_test_client):
702702
# Should not raise
703703
enhanced_test_client.validate_memory_record(memory)
704704

705+
@pytest.mark.asyncio
706+
async def test_get_or_create_handles_404_correctly(self, enhanced_test_client):
707+
"""Test that get_or_create_working_memory properly handles 404 errors.
708+
709+
This test verifies the fix for a bug where _handle_http_error would raise
710+
MemoryNotFoundError, but then the code would re-raise the original
711+
HTTPStatusError, preventing get_or_create_working_memory from catching
712+
the MemoryNotFoundError and creating a new session.
713+
"""
714+
715+
session_id = "nonexistent-session"
716+
717+
# Mock get_working_memory to raise MemoryNotFoundError (simulating 404)
718+
async def mock_get_working_memory(*args, **kwargs):
719+
# Simulate what happens when the server returns 404
720+
response = Mock()
721+
response.status_code = 404
722+
response.url = f"http://test/v1/working-memory/{session_id}"
723+
raise httpx.HTTPStatusError(
724+
"404 Not Found", request=Mock(), response=response
725+
)
726+
727+
# Mock put_working_memory to return a created session
728+
async def mock_put_working_memory(*args, **kwargs):
729+
return WorkingMemoryResponse(
730+
session_id=session_id,
731+
messages=[],
732+
memories=[],
733+
data={},
734+
context=None,
735+
user_id=None,
736+
)
737+
738+
with (
739+
patch.object(
740+
enhanced_test_client,
741+
"get_working_memory",
742+
side_effect=mock_get_working_memory,
743+
),
744+
patch.object(
745+
enhanced_test_client,
746+
"put_working_memory",
747+
side_effect=mock_put_working_memory,
748+
),
749+
):
750+
# This should NOT raise an exception - it should create a new session
751+
created, memory = await enhanced_test_client.get_or_create_working_memory(
752+
session_id=session_id
753+
)
754+
755+
# Verify that a new session was created
756+
assert created is True
757+
assert memory.session_id == session_id
758+
705759

706760
class TestContextUsagePercentage:
707761
"""Tests for context usage percentage functionality."""

0 commit comments

Comments
 (0)