Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
18 changes: 12 additions & 6 deletions redisvl/extensions/cache/llm/langcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,18 +536,16 @@ async def aupdate(self, key: str, **kwargs) -> None:
def delete(self) -> None:
"""Delete the entire cache.

This deletes all entries in the cache by calling delete_query
with no attributes.
This deletes all entries in the cache by calling the flush API.
"""
self._client.delete_query(attributes={})
self._client.flush()

async def adelete(self) -> None:
"""Async delete the entire cache.

This deletes all entries in the cache by calling delete_query
with no attributes.
This deletes all entries in the cache by calling the flush API.
"""
await self._client.delete_query_async(attributes={})
await self._client.flush_async()

def clear(self) -> None:
"""Clear the cache of all entries.
Expand Down Expand Up @@ -584,10 +582,14 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:

Args:
attributes (Dict[str, Any]): Attributes to match for deletion.
If empty, no deletion is performed.

Returns:
Dict[str, Any]: Result of the deletion operation.
"""
if not attributes:
# No attributes provided, return result with zero deletions
return {"deleted_entries_count": 0}
result = self._client.delete_query(attributes=attributes)
# Convert DeleteQueryResponse to dict
return result.model_dump() if hasattr(result, "model_dump") else {}
Expand All @@ -597,10 +599,14 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A

Args:
attributes (Dict[str, Any]): Attributes to match for deletion.
If empty, no deletion is performed.

Returns:
Dict[str, Any]: Result of the deletion operation.
"""
if not attributes:
# No attributes provided, return result with zero deletions
return {"deleted_entries_count": 0}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a warning here? Would it be helpful to the end user?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, thanks for asking about this. I've reconsidered and think raising an exception is best instead!

result = await self._client.delete_query_async(attributes=attributes)
# Convert DeleteQueryResponse to dict
return result.model_dump() if hasattr(result, "model_dump") else {}
127 changes: 122 additions & 5 deletions tests/unit/test_langcache_semantic_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def test_check_with_empty_attributes_does_not_send_attributes(
assert "attributes" not in call_kwargs

def test_delete(self, mock_langcache_client):
"""Test deleting the entire cache."""
"""Test deleting the entire cache using flush()."""
_, mock_client = mock_langcache_client

cache = LangCacheSemanticCache(
Expand All @@ -367,14 +367,14 @@ def test_delete(self, mock_langcache_client):

cache.delete()

mock_client.delete_query.assert_called_once_with(attributes={})
mock_client.flush.assert_called_once()

@pytest.mark.asyncio
async def test_adelete(self, mock_langcache_client):
"""Test async deleting the entire cache."""
"""Test async deleting the entire cache using flush()."""
_, mock_client = mock_langcache_client

mock_client.delete_query_async = AsyncMock()
mock_client.flush_async = AsyncMock()

cache = LangCacheSemanticCache(
name="test",
Expand All @@ -385,7 +385,40 @@ async def test_adelete(self, mock_langcache_client):

await cache.adelete()

mock_client.delete_query_async.assert_called_once_with(attributes={})
mock_client.flush_async.assert_called_once()

def test_clear(self, mock_langcache_client):
"""Test that clear() calls delete() which uses flush()."""
_, mock_client = mock_langcache_client

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

cache.clear()

mock_client.flush.assert_called_once()

@pytest.mark.asyncio
async def test_aclear(self, mock_langcache_client):
"""Test that async clear() calls adelete() which uses flush()."""
_, mock_client = mock_langcache_client

mock_client.flush_async = AsyncMock()

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

await cache.aclear()

mock_client.flush_async.assert_called_once()

def test_delete_by_id(self, mock_langcache_client):
"""Test deleting a single entry by ID."""
Expand All @@ -402,6 +435,90 @@ def test_delete_by_id(self, mock_langcache_client):

mock_client.delete_by_id.assert_called_once_with(entry_id="entry-123")

def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client):
"""Test deleting entries by attributes with valid attributes."""
_, mock_client = mock_langcache_client

mock_response = MagicMock()
mock_response.model_dump.return_value = {"deleted_count": 5}
mock_client.delete_query.return_value = mock_response

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

result = cache.delete_by_attributes({"topic": "python"})

assert result == {"deleted_count": 5}
mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"})

def test_delete_by_attributes_with_empty_attributes_returns_empty(
self, mock_langcache_client
):
"""Test that delete_by_attributes returns correct shape with empty attributes."""
_, mock_client = mock_langcache_client

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

result = cache.delete_by_attributes({})

# Should return dict with correct shape without calling delete_query
assert result == {"deleted_entries_count": 0}
mock_client.delete_query.assert_not_called()

@pytest.mark.asyncio
async def test_adelete_by_attributes_with_valid_attributes(
self, mock_langcache_client
):
"""Test async deleting entries by attributes with valid attributes."""
_, mock_client = mock_langcache_client

mock_response = MagicMock()
mock_response.model_dump.return_value = {"deleted_count": 3}
mock_client.delete_query_async = AsyncMock(return_value=mock_response)

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

result = await cache.adelete_by_attributes({"language": "python"})

assert result == {"deleted_count": 3}
mock_client.delete_query_async.assert_called_once_with(
attributes={"language": "python"}
)

@pytest.mark.asyncio
async def test_adelete_by_attributes_with_empty_attributes_returns_empty(
self, mock_langcache_client
):
"""Test that async delete_by_attributes returns correct shape with empty attributes."""
_, mock_client = mock_langcache_client

cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

result = await cache.adelete_by_attributes({})

# Should return dict with correct shape without calling delete_query_async
assert result == {"deleted_entries_count": 0}
mock_client.delete_query_async.assert_not_called()

def test_update_not_supported(self, mock_langcache_client):
"""Test that update raises NotImplementedError."""
cache = LangCacheSemanticCache(
Expand Down
Loading