From 29da9b9860d6d3a73e983d15252b414fa35a7f6a Mon Sep 17 00:00:00 2001 From: David Froger Date: Tue, 4 Nov 2025 17:16:33 +0100 Subject: [PATCH 1/2] add test for setting ttl --- tests/integration/test_async_search_index.py | 23 ++++++++++++++++++++ tests/integration/test_search_index.py | 21 ++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/integration/test_async_search_index.py b/tests/integration/test_async_search_index.py index 35d9da22..2ce139e7 100644 --- a/tests/integration/test_async_search_index.py +++ b/tests/integration/test_async_search_index.py @@ -707,3 +707,26 @@ async def test_async_search_index_connect(index_schema, redis_url): await async_index.connect(redis_url=redis_url) assert async_index.client is not None await async_index.disconnect() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("ttl", [None, 30]) +async def test_search_index_load_with_ttl(async_index, ttl): + """Test that TTL is correctly set on keys when using load() with ttl parameter.""" + await async_index.create(overwrite=True, drop=True) + + # Load test data with TTL parameter + data = [{"id": "1", "test": "foo"}] + keys = await async_index.load(data, id_field="id", ttl=ttl) + + # Check TTL on the loaded key + client = await async_index._get_client() + key_ttl = await client.ttl(keys[0]) + + if ttl is None: + # No TTL set, should return -1 + assert key_ttl == -1 + else: + # TTL should be set and close to the expected value + assert key_ttl > 0 + assert abs(key_ttl - ttl) <= 5 diff --git a/tests/integration/test_search_index.py b/tests/integration/test_search_index.py index 2cef1040..0cbdf8ab 100644 --- a/tests/integration/test_search_index.py +++ b/tests/integration/test_search_index.py @@ -697,3 +697,24 @@ def test_search_index_validates_query_with_hnsw_algorithm(hnsw_index, sample_dat ) # Should not raise hnsw_index.query(query) + + +@pytest.mark.parametrize("ttl", [None, 30]) +def test_search_index_load_with_ttl(index, ttl): + """Test that TTL is correctly set on keys when using load() with ttl parameter.""" + index.create(overwrite=True, drop=True) + + # Load test data with TTL parameter + data = [{"id": "1", "test": "foo"}] + keys = index.load(data, id_field="id", ttl=ttl) + + # Check TTL on the loaded key + key_ttl = index.client.ttl(keys[0]) + + if ttl is None: + # No TTL set, should return -1 + assert key_ttl == -1 + else: + # TTL should be set and close to the expected value + assert key_ttl > 0 + assert abs(key_ttl - ttl) <= 5 From afbed2b970de2f43249bb20ae92a26622b42f709 Mon Sep 17 00:00:00 2001 From: David Froger Date: Tue, 4 Nov 2025 14:18:26 +0100 Subject: [PATCH 2/2] fix #417: fix setting ttl on async pipeline BEFORE: - await pipe.expire() triggers ClusterPipeline.__await__ - this triggers .initialize() which delete the pipeline commands AFTER: - pipe.expire() in case of pipeline - await pipe.expire() in case of classic client --- redisvl/index/storage.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/redisvl/index/storage.py b/redisvl/index/storage.py index 6143d525..74b9e775 100644 --- a/redisvl/index/storage.py +++ b/redisvl/index/storage.py @@ -175,6 +175,20 @@ async def _aget( """Asynchronously get data from Redis using the provided client or pipeline.""" raise NotImplementedError + @staticmethod + async def _aexpire(client: AsyncRedisClientOrPipeline, key: str, ttl: int): + """Asynchronously set TTL on a key using the provided client or pipeline + + Args: + client (AsyncRedisClientOrPipeline): The async Redis client or pipeline instance. + key (str): The key for which to set the TTL. + ttl (int): Time-to-live in seconds for each key. + """ + if isinstance(client, (AsyncPipeline, AsyncClusterPipeline)): + client.expire(key, ttl) + else: + await client.expire(key, ttl) + def _validate(self, obj: Dict[str, Any]) -> Dict[str, Any]: """ Validate an object against the schema using Pydantic-based validation. @@ -490,7 +504,7 @@ async def awrite( # Set TTL if provided if ttl: - await pipe.expire(key, ttl) + await self._aexpire(pipe, key, ttl) added_keys.append(key) @@ -615,7 +629,7 @@ async def _aset(client: AsyncRedisClientOrPipeline, key: str, obj: Dict[str, Any """Asynchronously set a hash value in Redis for the given key. Args: - client (AsyncClientOrPipeline): The async Redis client or pipeline instance. + client (AsyncRedisClientOrPipeline): The async Redis client or pipeline instance. key (str): The key under which to store the hash. obj (Dict[str, Any]): The hash to store in Redis. """ @@ -644,7 +658,7 @@ async def _aget( """Asynchronously retrieve a hash value from Redis for the given key. Args: - client (AsyncRedisClient): The async Redis client or pipeline instance. + client (AsyncRedisClientOrPipeline): The async Redis client or pipeline instance. key (str): The key for which to retrieve the hash. Returns: