From 15c33da54391520e11cc1bcb893cf3bc1df2ac3d Mon Sep 17 00:00:00 2001 From: Erick Aleman Date: Tue, 21 Apr 2026 16:43:12 -0400 Subject: [PATCH 1/3] fix(python/redis): fix FT.CREATE prefix format and silent delete failure --- python/semantic_kernel/connectors/redis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/semantic_kernel/connectors/redis.py b/python/semantic_kernel/connectors/redis.py index 575624895aca..5ad4dd4c5fc6 100644 --- a/python/semantic_kernel/connectors/redis.py +++ b/python/semantic_kernel/connectors/redis.py @@ -278,7 +278,7 @@ async def ensure_collection_exists(self, **kwargs) -> None: raise VectorStoreOperationException("Invalid index type supplied.") fields = _definition_to_redis_fields(self.definition, self.collection_type) index_definition = IndexDefinition( - prefix=f"{self.collection_name}:", index_type=INDEX_TYPE_MAP[self.collection_type] + prefix=[f"{self.collection_name}:"], index_type=INDEX_TYPE_MAP[self.collection_type] ) await self.redis_database.ft(self.collection_name).create_index(fields, definition=index_definition, **kwargs) @@ -706,7 +706,7 @@ def _add_key(self, key: TKey, record: dict[str, Any]) -> dict[str, Any]: @override async def _inner_delete(self, keys: Sequence[str], **kwargs: Any) -> None: - await asyncio.gather(*[self.redis_database.json().delete(key, **kwargs) for key in keys]) + await asyncio.gather(*[self.redis_database.json().delete(self._get_redis_key(key), **kwargs) for key in keys]) @override def _serialize_dicts_to_store_models( From 562f8be15430c9cbdcb822dfc3e85f9f70229018 Mon Sep 17 00:00:00 2001 From: Erick Aleman Date: Tue, 21 Apr 2026 17:44:09 -0400 Subject: [PATCH 2/3] test(python/redis): add tests for prefixed delete and index prefix list format --- .../connectors/memory/test_redis_store.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/python/tests/unit/connectors/memory/test_redis_store.py b/python/tests/unit/connectors/memory/test_redis_store.py index e779ad945a97..068cd5d38d27 100644 --- a/python/tests/unit/connectors/memory/test_redis_store.py +++ b/python/tests/unit/connectors/memory/test_redis_store.py @@ -270,11 +270,31 @@ async def test_get( assert records is not None +@mark.parametrize("prefix", [True, False]) @mark.parametrize("type_", ["hashset", "json"]) -async def test_delete(collection_hash, collection_json, type_): - collection = collection_hash if type_ == "hashset" else collection_json +async def test_delete( + collection_hash, + collection_json, + collection_with_prefix_hash, + collection_with_prefix_json, + mock_delete_hash, + mock_delete_json, + type_, + prefix, +): + if prefix: + collection = collection_with_prefix_hash if type_ == "hashset" else collection_with_prefix_json + else: + collection = collection_hash if type_ == "hashset" else collection_json + await collection._inner_delete(["id1"]) + expected_key = "test:id1" if prefix else "id1" + if type_ == "hashset": + mock_delete_hash.assert_called_once_with(expected_key) + else: + mock_delete_json.assert_called_once_with(expected_key) + async def test_collection_exists(collection_hash, mock_collection_exists): await collection_hash.collection_exists() @@ -294,12 +314,17 @@ async def test_ensure_collection_deleted(collection_hash, mock_ensure_collection async def test_create_index(collection_hash, mock_ensure_collection_exists): await collection_hash.ensure_collection_exists() + # Check that the index definition has the correct prefix list format + # The second positional argument to create_index is usually 'definition' (keyword) + # but here we check kwargs + assert mock_ensure_collection_exists.call_args.kwargs["definition"].prefix == ["test:"] + async def test_create_index_manual(collection_hash, mock_ensure_collection_exists): from redis.commands.search.index_definition import IndexDefinition, IndexType fields = ["fields"] - index_definition = IndexDefinition(prefix="test:", index_type=IndexType.HASH) + index_definition = IndexDefinition(prefix=["test:"], index_type=IndexType.HASH) await collection_hash.ensure_collection_exists(index_definition=index_definition, fields=fields) From 0f054333a7dfbed2feea6c0399bf7c9903587faa Mon Sep 17 00:00:00 2001 From: Erick Aleman Date: Fri, 24 Apr 2026 14:41:47 -0400 Subject: [PATCH 3/3] test(python/redis): cover index prefix command args --- .../unit/connectors/memory/test_redis_store.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/python/tests/unit/connectors/memory/test_redis_store.py b/python/tests/unit/connectors/memory/test_redis_store.py index 068cd5d38d27..b2ed9182453e 100644 --- a/python/tests/unit/connectors/memory/test_redis_store.py +++ b/python/tests/unit/connectors/memory/test_redis_store.py @@ -314,10 +314,19 @@ async def test_ensure_collection_deleted(collection_hash, mock_ensure_collection async def test_create_index(collection_hash, mock_ensure_collection_exists): await collection_hash.ensure_collection_exists() - # Check that the index definition has the correct prefix list format - # The second positional argument to create_index is usually 'definition' (keyword) - # but here we check kwargs - assert mock_ensure_collection_exists.call_args.kwargs["definition"].prefix == ["test:"] + index_definition = mock_ensure_collection_exists.call_args.kwargs["definition"] + + # A bare string prefix is treated as an iterable by redis-py and becomes + # five one-character prefixes. Assert the generated command uses one prefix. + assert index_definition.args == ["ON", "HASH", "PREFIX", 1, "test:", "SCORE", 1.0] + + +def test_create_index_prefix_must_be_sequence(): + from redis.commands.search.index_definition import IndexDefinition, IndexType + + index_definition = IndexDefinition(prefix="test:", index_type=IndexType.HASH) + + assert index_definition.args == ["ON", "HASH", "PREFIX", 5, "t", "e", "s", "t", ":", "SCORE", 1.0] async def test_create_index_manual(collection_hash, mock_ensure_collection_exists):