Skip to content
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
26 changes: 20 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,18 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:

Args:
attributes (Dict[str, Any]): Attributes to match for deletion.
Cannot be empty.

Returns:
Dict[str, Any]: Result of the deletion operation.

Raises:
ValueError: If attributes is an empty dictionary.
"""
if not attributes:
raise ValueError(
"Cannot delete by attributes with an empty attributes dictionary."
)
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 +603,18 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A

Args:
attributes (Dict[str, Any]): Attributes to match for deletion.
Cannot be empty.

Returns:
Dict[str, Any]: Result of the deletion operation.

Raises:
ValueError: If attributes is an empty dictionary.
"""
if not attributes:
raise ValueError(
"Cannot delete by attributes with an empty attributes dictionary."
)
result = await self._client.delete_query_async(attributes=attributes)
# Convert DeleteQueryResponse to dict
return result.model_dump() if hasattr(result, "model_dump") else {}
123 changes: 118 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,86 @@ 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_entries_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_entries_count": 5}
mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"})

def test_delete_by_attributes_with_empty_attributes_raises_error(
self, mock_langcache_client
):
"""Test that delete_by_attributes raises ValueError with empty attributes."""
cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

with pytest.raises(
ValueError,
match="Cannot delete by attributes with an empty attributes dictionary",
):
cache.delete_by_attributes({})

@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_entries_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_entries_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_raises_error(
self, mock_langcache_client
):
"""Test that async delete_by_attributes raises ValueError with empty attributes."""
cache = LangCacheSemanticCache(
name="test",
server_url="https://api.example.com",
cache_id="test-cache",
api_key="test-key",
)

with pytest.raises(
ValueError,
match="Cannot delete by attributes with an empty attributes dictionary",
):
await cache.adelete_by_attributes({})

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