Skip to content

Commit ab3e711

Browse files
Add a drop_documents() method to search index classes (#316)
Wrap key creation so users don't have to call private methods. Co-authored-by: Tyler Hutcherson <[email protected]>
1 parent f0a4f89 commit ab3e711

File tree

8 files changed

+452
-241
lines changed

8 files changed

+452
-241
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
env:
6363
HF_TOKEN: ${{ secrets.HF_TOKEN }}
6464
HF_HOME: ${{ github.workspace }}/hf_cache
65-
OPENAI_API_KEY: ${{ secrets.OPENAI_KEY }}
65+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
6666
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
6767
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
6868
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
@@ -152,7 +152,7 @@ jobs:
152152
if: matrix.connection == 'plain' && matrix.redis-version == 'latest'
153153
env:
154154
HF_HOME: ${{ github.workspace }}/hf_cache
155-
OPENAI_API_KEY: ${{ secrets.OPENAI_KEY }}
155+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
156156
GCP_LOCATION: ${{ secrets.GCP_LOCATION }}
157157
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
158158
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}

docs/overview/cli.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"name": "stdout",
2525
"output_type": "stream",
2626
"text": [
27-
"\u001b[32m16:19:10\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m RedisVL version 0.4.0\n"
27+
"\u001b[32m09:58:03\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m RedisVL version 0.5.2\n"
2828
]
2929
}
3030
],

poetry.lock

Lines changed: 341 additions & 234 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "redisvl"
3-
version = "0.5.1"
3+
version = "0.5.2"
44
description = "Python client library and CLI for using Redis as a vector database"
55
authors = ["Redis Inc. <[email protected]>"]
66
license = "MIT"
@@ -31,7 +31,7 @@ redis = "^5.0"
3131
pydantic = "^2"
3232
tenacity = ">=8.2.2"
3333
tabulate = "^0.9.0"
34-
ml-dtypes = "^0.4.0"
34+
ml-dtypes = ">=0.4.0,<1.0.0"
3535
python-ulid = "^3.0.0"
3636
nltk = { version = "^3.8.1", optional = true }
3737
jsonpath-ng = "^1.5.0"

redisvl/index/index.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,27 @@ def drop_keys(self, keys: Union[str, List[str]]) -> int:
592592
else:
593593
return self._redis_client.delete(keys) # type: ignore
594594

595+
def drop_documents(self, ids: Union[str, List[str]]) -> int:
596+
"""Remove documents from the index by their document IDs.
597+
598+
This method converts document IDs to Redis keys automatically by applying
599+
the index's key prefix and separator configuration.
600+
601+
Args:
602+
ids (Union[str, List[str]]): The document ID or IDs to remove from the index.
603+
604+
Returns:
605+
int: Count of documents deleted from Redis.
606+
"""
607+
if isinstance(ids, list):
608+
if not ids:
609+
return 0
610+
keys = [self.key(id) for id in ids]
611+
return self._redis_client.delete(*keys) # type: ignore
612+
else:
613+
key = self.key(ids)
614+
return self._redis_client.delete(key) # type: ignore
615+
595616
def expire_keys(
596617
self, keys: Union[str, List[str]], ttl: int
597618
) -> Union[int, List[int]]:
@@ -1236,6 +1257,28 @@ async def drop_keys(self, keys: Union[str, List[str]]) -> int:
12361257
else:
12371258
return await client.delete(keys)
12381259

1260+
async def drop_documents(self, ids: Union[str, List[str]]) -> int:
1261+
"""Remove documents from the index by their document IDs.
1262+
1263+
This method converts document IDs to Redis keys automatically by applying
1264+
the index's key prefix and separator configuration.
1265+
1266+
Args:
1267+
ids (Union[str, List[str]]): The document ID or IDs to remove from the index.
1268+
1269+
Returns:
1270+
int: Count of documents deleted from Redis.
1271+
"""
1272+
client = await self._get_client()
1273+
if isinstance(ids, list):
1274+
if not ids:
1275+
return 0
1276+
keys = [self.key(id) for id in ids]
1277+
return await client.delete(*keys)
1278+
else:
1279+
key = self.key(ids)
1280+
return await client.delete(key)
1281+
12391282
async def expire_keys(
12401283
self, keys: Union[str, List[str]], ttl: int
12411284
) -> Union[int, List[int]]:
@@ -1356,7 +1399,7 @@ async def fetch(self, id: str) -> Optional[Dict[str, Any]]:
13561399
async def _aggregate(
13571400
self, aggregation_query: AggregationQuery
13581401
) -> List[Dict[str, Any]]:
1359-
"""Execute an aggretation query and processes the results."""
1402+
"""Execute an aggregation query and processes the results."""
13601403
results = await self.aggregate(
13611404
aggregation_query, query_params=aggregation_query.params # type: ignore[attr-defined]
13621405
)

redisvl/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.5.1"
1+
__version__ = "0.5.2"

tests/integration/test_async_search_index.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,37 @@ async def test_search_index_drop_keys(async_index):
246246
assert await async_index.exists()
247247

248248

249+
@pytest.mark.asyncio
250+
async def test_search_index_drop_documents(async_index):
251+
await async_index.create(overwrite=True, drop=True)
252+
data = [
253+
{"id": "1", "test": "foo"},
254+
{"id": "2", "test": "bar"},
255+
{"id": "3", "test": "baz"},
256+
]
257+
await async_index.load(data, id_field="id")
258+
259+
# Test dropping a single document by ID
260+
dropped = await async_index.drop_documents("1")
261+
assert dropped == 1
262+
assert not await async_index.fetch("1")
263+
assert await async_index.fetch("2") is not None
264+
assert await async_index.fetch("3") is not None
265+
266+
# Test dropping multiple documents by ID
267+
dropped = await async_index.drop_documents(["2", "3"])
268+
assert dropped == 2
269+
assert not await async_index.fetch("2")
270+
assert not await async_index.fetch("3")
271+
272+
# Test dropping with an empty list
273+
dropped = await async_index.drop_documents([])
274+
assert dropped == 0
275+
276+
# Ensure the index still exists
277+
assert await async_index.exists()
278+
279+
249280
@pytest.mark.asyncio
250281
async def test_search_index_load_and_fetch(async_index):
251282
await async_index.create(overwrite=True, drop=True)

tests/integration/test_search_index.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,36 @@ def test_search_index_drop_keys(index):
240240
assert index.exists()
241241

242242

243+
def test_search_index_drop_documents(index):
244+
index.create(overwrite=True, drop=True)
245+
data = [
246+
{"id": "1", "test": "foo"},
247+
{"id": "2", "test": "bar"},
248+
{"id": "3", "test": "baz"},
249+
]
250+
index.load(data, id_field="id")
251+
252+
# Test dropping a single document by ID
253+
dropped = index.drop_documents("1")
254+
assert dropped == 1
255+
assert not index.fetch("1")
256+
assert index.fetch("2") is not None
257+
assert index.fetch("3") is not None
258+
259+
# Test dropping multiple documents by ID
260+
dropped = index.drop_documents(["2", "3"])
261+
assert dropped == 2
262+
assert not index.fetch("2")
263+
assert not index.fetch("3")
264+
265+
# Test dropping with an empty list
266+
dropped = index.drop_documents([])
267+
assert dropped == 0
268+
269+
# Ensure the index still exists
270+
assert index.exists()
271+
272+
243273
def test_search_index_load_and_fetch(index):
244274
index.create(overwrite=True, drop=True)
245275
data = [{"id": "1", "test": "foo"}]

0 commit comments

Comments
 (0)