Skip to content

Commit 9d33cd0

Browse files
Copilotabrookins
andcommitted
Fix LangCache clear() to raise NotImplementedError instead of sending empty attributes
- Changed delete() and adelete() to raise NotImplementedError - Added validation to delete_by_attributes() and adelete_by_attributes() to reject empty attributes - Updated all related tests to verify new behavior - Added new tests for delete_by_attributes with both valid and empty attributes Co-authored-by: abrookins <[email protected]>
1 parent 19b96e2 commit 9d33cd0

File tree

2 files changed

+152
-19
lines changed

2 files changed

+152
-19
lines changed

redisvl/extensions/cache/llm/langcache.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -536,18 +536,34 @@ async def aupdate(self, key: str, **kwargs) -> None:
536536
def delete(self) -> None:
537537
"""Delete the entire cache.
538538
539-
This deletes all entries in the cache by calling delete_query
540-
with no attributes.
539+
Note: LangCache API does not support deleting all entries without
540+
specific attributes. Use delete_by_attributes() to delete entries
541+
matching specific criteria, or delete_by_id() to delete individual entries.
542+
543+
Raises:
544+
NotImplementedError: LangCache does not support clearing all entries.
541545
"""
542-
self._client.delete_query(attributes={})
546+
raise NotImplementedError(
547+
"LangCache API does not support deleting all entries without attributes. "
548+
"Use delete_by_attributes(attributes) to delete entries matching specific "
549+
"criteria, or delete_by_id(entry_id) to delete individual entries."
550+
)
543551

544552
async def adelete(self) -> None:
545553
"""Async delete the entire cache.
546554
547-
This deletes all entries in the cache by calling delete_query
548-
with no attributes.
555+
Note: LangCache API does not support deleting all entries without
556+
specific attributes. Use adelete_by_attributes() to delete entries
557+
matching specific criteria, or adelete_by_id() to delete individual entries.
558+
559+
Raises:
560+
NotImplementedError: LangCache does not support clearing all entries.
549561
"""
550-
await self._client.delete_query_async(attributes={})
562+
raise NotImplementedError(
563+
"LangCache API does not support deleting all entries without attributes. "
564+
"Use adelete_by_attributes(attributes) to delete entries matching specific "
565+
"criteria, or adelete_by_id(entry_id) to delete individual entries."
566+
)
551567

552568
def clear(self) -> None:
553569
"""Clear the cache of all entries.
@@ -584,10 +600,19 @@ def delete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
584600
585601
Args:
586602
attributes (Dict[str, Any]): Attributes to match for deletion.
603+
Cannot be empty.
587604
588605
Returns:
589606
Dict[str, Any]: Result of the deletion operation.
607+
608+
Raises:
609+
ValueError: If attributes is empty.
590610
"""
611+
if not attributes:
612+
raise ValueError(
613+
"attributes cannot be empty. Provide specific attributes to match, "
614+
"or use delete_by_id(entry_id) to delete individual entries."
615+
)
591616
result = self._client.delete_query(attributes=attributes)
592617
# Convert DeleteQueryResponse to dict
593618
return result.model_dump() if hasattr(result, "model_dump") else {}
@@ -597,10 +622,19 @@ async def adelete_by_attributes(self, attributes: Dict[str, Any]) -> Dict[str, A
597622
598623
Args:
599624
attributes (Dict[str, Any]): Attributes to match for deletion.
625+
Cannot be empty.
600626
601627
Returns:
602628
Dict[str, Any]: Result of the deletion operation.
629+
630+
Raises:
631+
ValueError: If attributes is empty.
603632
"""
633+
if not attributes:
634+
raise ValueError(
635+
"attributes cannot be empty. Provide specific attributes to match, "
636+
"or use adelete_by_id(entry_id) to delete individual entries."
637+
)
604638
result = await self._client.delete_query_async(attributes=attributes)
605639
# Convert DeleteQueryResponse to dict
606640
return result.model_dump() if hasattr(result, "model_dump") else {}

tests/unit/test_langcache_semantic_cache.py

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -354,38 +354,63 @@ def test_check_with_empty_attributes_does_not_send_attributes(
354354
_, call_kwargs = mock_client.search.call_args
355355
assert "attributes" not in call_kwargs
356356

357-
def test_delete(self, mock_langcache_client):
358-
"""Test deleting the entire cache."""
359-
_, mock_client = mock_langcache_client
360-
357+
def test_delete_not_supported(self, mock_langcache_client):
358+
"""Test that delete() raises NotImplementedError."""
361359
cache = LangCacheSemanticCache(
362360
name="test",
363361
server_url="https://api.example.com",
364362
cache_id="test-cache",
365363
api_key="test-key",
366364
)
367365

368-
cache.delete()
369-
370-
mock_client.delete_query.assert_called_once_with(attributes={})
366+
with pytest.raises(
367+
NotImplementedError, match="does not support deleting all entries"
368+
):
369+
cache.delete()
371370

372371
@pytest.mark.asyncio
373-
async def test_adelete(self, mock_langcache_client):
374-
"""Test async deleting the entire cache."""
375-
_, mock_client = mock_langcache_client
372+
async def test_adelete_not_supported(self, mock_langcache_client):
373+
"""Test that async delete() raises NotImplementedError."""
374+
cache = LangCacheSemanticCache(
375+
name="test",
376+
server_url="https://api.example.com",
377+
cache_id="test-cache",
378+
api_key="test-key",
379+
)
376380

377-
mock_client.delete_query_async = AsyncMock()
381+
with pytest.raises(
382+
NotImplementedError, match="does not support deleting all entries"
383+
):
384+
await cache.adelete()
378385

386+
def test_clear_not_supported(self, mock_langcache_client):
387+
"""Test that clear() raises NotImplementedError."""
379388
cache = LangCacheSemanticCache(
380389
name="test",
381390
server_url="https://api.example.com",
382391
cache_id="test-cache",
383392
api_key="test-key",
384393
)
385394

386-
await cache.adelete()
395+
with pytest.raises(
396+
NotImplementedError, match="does not support deleting all entries"
397+
):
398+
cache.clear()
399+
400+
@pytest.mark.asyncio
401+
async def test_aclear_not_supported(self, mock_langcache_client):
402+
"""Test that async clear() raises NotImplementedError."""
403+
cache = LangCacheSemanticCache(
404+
name="test",
405+
server_url="https://api.example.com",
406+
cache_id="test-cache",
407+
api_key="test-key",
408+
)
387409

388-
mock_client.delete_query_async.assert_called_once_with(attributes={})
410+
with pytest.raises(
411+
NotImplementedError, match="does not support deleting all entries"
412+
):
413+
await cache.aclear()
389414

390415
def test_delete_by_id(self, mock_langcache_client):
391416
"""Test deleting a single entry by ID."""
@@ -402,6 +427,80 @@ def test_delete_by_id(self, mock_langcache_client):
402427

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

430+
def test_delete_by_attributes_with_valid_attributes(self, mock_langcache_client):
431+
"""Test deleting entries by attributes with valid attributes."""
432+
_, mock_client = mock_langcache_client
433+
434+
mock_response = MagicMock()
435+
mock_response.model_dump.return_value = {"deleted_count": 5}
436+
mock_client.delete_query.return_value = mock_response
437+
438+
cache = LangCacheSemanticCache(
439+
name="test",
440+
server_url="https://api.example.com",
441+
cache_id="test-cache",
442+
api_key="test-key",
443+
)
444+
445+
result = cache.delete_by_attributes({"topic": "python"})
446+
447+
assert result == {"deleted_count": 5}
448+
mock_client.delete_query.assert_called_once_with(attributes={"topic": "python"})
449+
450+
def test_delete_by_attributes_with_empty_attributes_raises_error(
451+
self, mock_langcache_client
452+
):
453+
"""Test that delete_by_attributes raises ValueError with empty attributes."""
454+
cache = LangCacheSemanticCache(
455+
name="test",
456+
server_url="https://api.example.com",
457+
cache_id="test-cache",
458+
api_key="test-key",
459+
)
460+
461+
with pytest.raises(ValueError, match="attributes cannot be empty"):
462+
cache.delete_by_attributes({})
463+
464+
@pytest.mark.asyncio
465+
async def test_adelete_by_attributes_with_valid_attributes(
466+
self, mock_langcache_client
467+
):
468+
"""Test async deleting entries by attributes with valid attributes."""
469+
_, mock_client = mock_langcache_client
470+
471+
mock_response = MagicMock()
472+
mock_response.model_dump.return_value = {"deleted_count": 3}
473+
mock_client.delete_query_async = AsyncMock(return_value=mock_response)
474+
475+
cache = LangCacheSemanticCache(
476+
name="test",
477+
server_url="https://api.example.com",
478+
cache_id="test-cache",
479+
api_key="test-key",
480+
)
481+
482+
result = await cache.adelete_by_attributes({"language": "python"})
483+
484+
assert result == {"deleted_count": 3}
485+
mock_client.delete_query_async.assert_called_once_with(
486+
attributes={"language": "python"}
487+
)
488+
489+
@pytest.mark.asyncio
490+
async def test_adelete_by_attributes_with_empty_attributes_raises_error(
491+
self, mock_langcache_client
492+
):
493+
"""Test that async delete_by_attributes raises ValueError with empty attributes."""
494+
cache = LangCacheSemanticCache(
495+
name="test",
496+
server_url="https://api.example.com",
497+
cache_id="test-cache",
498+
api_key="test-key",
499+
)
500+
501+
with pytest.raises(ValueError, match="attributes cannot be empty"):
502+
await cache.adelete_by_attributes({})
503+
405504
def test_update_not_supported(self, mock_langcache_client):
406505
"""Test that update raises NotImplementedError."""
407506
cache = LangCacheSemanticCache(

0 commit comments

Comments
 (0)