From 45c5e9fd77e6c5758752a72b03b4f32a1e41a2fc Mon Sep 17 00:00:00 2001 From: Justin Cechmanek Date: Wed, 10 Jul 2024 16:35:53 -0700 Subject: [PATCH 1/3] adds drop_keys and async drop_keys methods to index --- redisvl/index/index.py | 23 +++++++++++++++ tests/integration/test_async_search_index.py | 31 ++++++++++++++++++++ tests/integration/test_search_index.py | 31 ++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 25b907f8..9a70d755 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -497,6 +497,19 @@ def clear(self) -> int: return total_records_deleted + def drop_keys(self, keys: Union[str, List[str]]) -> None: + """Remove a specific entry or entries from the index by it's key ID. + Args: + keys (Union[str, List[str]]): The document ID or IDs to remove from the index. + """ + if isinstance(keys, List): + with self._redis_client.pipeline(transaction=False) as pipe: # type: ignore + for key in keys: # type: ignore + pipe.delete(key) + pipe.execute() + else: + self._redis_client.delete(keys) # type: ignore + def load( self, data: Iterable[Any], @@ -935,6 +948,16 @@ async def clear(self) -> int: return total_records_deleted + async def drop_keys(self, keys: Union[str, List[str]]) -> None: + """Remove a specific entry or entries from the index by it's key ID. + Args: + keys (Union[str, List[str]]): The document ID or IDs to remove from the index. + """ + if isinstance(keys, List): + await self._redis_client.delete(*keys) # type: ignore + else: + await self._redis_client.delete(keys) # type: ignore + async def load( self, data: Iterable[Any], diff --git a/tests/integration/test_async_search_index.py b/tests/integration/test_async_search_index.py index 41e5a03a..e32ede56 100644 --- a/tests/integration/test_async_search_index.py +++ b/tests/integration/test_async_search_index.py @@ -184,6 +184,37 @@ async def test_search_index_clear(async_client, async_index): assert await async_index.exists() +@pytest.mark.asyncio +async def test_search_index_drop_key(async_client, async_index): + async_index.set_client(async_client) + await async_index.create(overwrite=True, drop=True) + data = [{"id": "1", "test": "foo"}, {"id": "2", "test": "bar"}] + keys = await async_index.load(data, id_field="id") + + await async_index.drop_keys(keys[0]) + assert not await async_index.fetch(keys[0]) + assert await async_index.fetch(keys[1]) is not None + + +@pytest.mark.asyncio +async def test_search_index_drop_keys(async_client, async_index): + async_index.set_client(async_client) + await async_index.create(overwrite=True, drop=True) + data = [ + {"id": "1", "test": "foo"}, + {"id": "2", "test": "bar"}, + {"id": "3", "test": "baz"}, + ] + keys = await async_index.load(data, id_field="id") + + await async_index.drop_keys(keys[0:2]) + assert not await async_index.fetch(keys[0]) + assert not await async_index.fetch(keys[1]) + assert await async_index.fetch(keys[2]) is not None + + assert await async_index.exists() + + @pytest.mark.asyncio async def test_search_index_load_and_fetch(async_client, async_index): async_index.set_client(async_client) diff --git a/tests/integration/test_search_index.py b/tests/integration/test_search_index.py index 1c215dc9..eebcbad7 100644 --- a/tests/integration/test_search_index.py +++ b/tests/integration/test_search_index.py @@ -170,6 +170,37 @@ def test_search_index_clear(client, index): assert index.exists() +def test_search_index_drop_key(client, index): + index.set_client(client) + index.create(overwrite=True, drop=True) + data = [{"id": "1", "test": "foo"}, {"id": "2", "test": "bar"}] + keys = index.load(data, id_field="id") + + # test passing a single string key removes only that key + index.drop_keys(keys[0]) + assert not index.fetch(keys[0]) + assert index.fetch(keys[1]) is not None # still have all other entries + + +def test_search_index_drop_keys(client, index): + index.set_client(client) + index.create(overwrite=True, drop=True) + data = [ + {"id": "1", "test": "foo"}, + {"id": "2", "test": "bar"}, + {"id": "3", "test": "baz"}, + ] + keys = index.load(data, id_field="id") + + # test passing a list of keys selectively removes only those keys + index.drop_keys(keys[0:2]) + assert not index.fetch(keys[0]) + assert not index.fetch(keys[1]) + assert index.fetch(keys[2]) is not None + + assert index.exists() + + def test_search_index_load_and_fetch(client, index): index.set_client(client) index.create(overwrite=True, drop=True) From 1499bfb285e87e271ac3f1a411cd2a73b491ee9d Mon Sep 17 00:00:00 2001 From: Justin Cechmanek Date: Thu, 11 Jul 2024 09:51:01 -0700 Subject: [PATCH 2/3] simplifies drop_keys method --- redisvl/index/index.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 9a70d755..8b799af3 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -503,10 +503,7 @@ def drop_keys(self, keys: Union[str, List[str]]) -> None: keys (Union[str, List[str]]): The document ID or IDs to remove from the index. """ if isinstance(keys, List): - with self._redis_client.pipeline(transaction=False) as pipe: # type: ignore - for key in keys: # type: ignore - pipe.delete(key) - pipe.execute() + self._redis_client.delete(*keys) # type: ignore else: self._redis_client.delete(keys) # type: ignore From fb87d93b581dcaf3aac0e6c79830c7ae9a0ec6a9 Mon Sep 17 00:00:00 2001 From: Justin Cechmanek Date: Thu, 11 Jul 2024 10:06:49 -0700 Subject: [PATCH 3/3] drop_keys returns number of records removed from Redis --- redisvl/index/index.py | 20 ++++++++++++++------ tests/integration/test_async_search_index.py | 6 ++++-- tests/integration/test_search_index.py | 6 ++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 8b799af3..2187c759 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -497,15 +497,19 @@ def clear(self) -> int: return total_records_deleted - def drop_keys(self, keys: Union[str, List[str]]) -> None: + def drop_keys(self, keys: Union[str, List[str]]) -> int: """Remove a specific entry or entries from the index by it's key ID. + Args: keys (Union[str, List[str]]): The document ID or IDs to remove from the index. + + Returns: + int: Count of records deleted from Redis. """ if isinstance(keys, List): - self._redis_client.delete(*keys) # type: ignore + return self._redis_client.delete(*keys) # type: ignore else: - self._redis_client.delete(keys) # type: ignore + return self._redis_client.delete(keys) # type: ignore def load( self, @@ -945,15 +949,19 @@ async def clear(self) -> int: return total_records_deleted - async def drop_keys(self, keys: Union[str, List[str]]) -> None: + async def drop_keys(self, keys: Union[str, List[str]]) -> int: """Remove a specific entry or entries from the index by it's key ID. + Args: keys (Union[str, List[str]]): The document ID or IDs to remove from the index. + + Returns: + int: Count of records deleted from Redis. """ if isinstance(keys, List): - await self._redis_client.delete(*keys) # type: ignore + return await self._redis_client.delete(*keys) # type: ignore else: - await self._redis_client.delete(keys) # type: ignore + return await self._redis_client.delete(keys) # type: ignore async def load( self, diff --git a/tests/integration/test_async_search_index.py b/tests/integration/test_async_search_index.py index e32ede56..5e795391 100644 --- a/tests/integration/test_async_search_index.py +++ b/tests/integration/test_async_search_index.py @@ -191,7 +191,8 @@ async def test_search_index_drop_key(async_client, async_index): data = [{"id": "1", "test": "foo"}, {"id": "2", "test": "bar"}] keys = await async_index.load(data, id_field="id") - await async_index.drop_keys(keys[0]) + dropped = await async_index.drop_keys(keys[0]) + assert dropped == 1 assert not await async_index.fetch(keys[0]) assert await async_index.fetch(keys[1]) is not None @@ -207,7 +208,8 @@ async def test_search_index_drop_keys(async_client, async_index): ] keys = await async_index.load(data, id_field="id") - await async_index.drop_keys(keys[0:2]) + dropped = await async_index.drop_keys(keys[0:2]) + assert dropped == 2 assert not await async_index.fetch(keys[0]) assert not await async_index.fetch(keys[1]) assert await async_index.fetch(keys[2]) is not None diff --git a/tests/integration/test_search_index.py b/tests/integration/test_search_index.py index eebcbad7..574cec91 100644 --- a/tests/integration/test_search_index.py +++ b/tests/integration/test_search_index.py @@ -177,7 +177,8 @@ def test_search_index_drop_key(client, index): keys = index.load(data, id_field="id") # test passing a single string key removes only that key - index.drop_keys(keys[0]) + dropped = index.drop_keys(keys[0]) + assert dropped == 1 assert not index.fetch(keys[0]) assert index.fetch(keys[1]) is not None # still have all other entries @@ -193,7 +194,8 @@ def test_search_index_drop_keys(client, index): keys = index.load(data, id_field="id") # test passing a list of keys selectively removes only those keys - index.drop_keys(keys[0:2]) + dropped = index.drop_keys(keys[0:2]) + assert dropped == 2 assert not index.fetch(keys[0]) assert not index.fetch(keys[1]) assert index.fetch(keys[2]) is not None