From 43792428091d1313ea95d9acaed6b72a3d105a48 Mon Sep 17 00:00:00 2001 From: Nishanth Date: Tue, 29 Jul 2025 22:23:39 -0700 Subject: [PATCH 1/4] Add lrem functionality to list.py --- src/tools/list.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/tools/list.py b/src/tools/list.py index 929f24e..aca9d46 100644 --- a/src/tools/list.py +++ b/src/tools/list.py @@ -82,3 +82,27 @@ async def llen(name: str) -> int: return r.llen(name) except RedisError as e: return f"Error retrieving length of list '{name}': {str(e)}" + +@mcp.tool() +async def lrem(name: str, count: int, element: FieldT) -> str: +    """Remove elements from a Redis list. +    +    Args: +        name: The name of the list +        count: Number of elements to remove (0 = all, positive = from head, negative = from tail) +        element: The element value to remove +        +    Returns: +        A string indicating the number of elements removed. +    """ +    try: +        r = RedisConnectionManager.get_connection() +        removed_count = r.lrem(name, count, element) +        +        if removed_count == 0: +            return f"Element '{element}' not found in list '{name}' or list does not exist." +        else: +            return f"Removed {removed_count} occurrence(s) of '{element}' from list '{name}'." +            +    except RedisError as e: +        return f"Error removing element from list '{name}': {str(e)}" From 612541702aa959e1a28f2d576705afb36bb38c0f Mon Sep 17 00:00:00 2001 From: Nishanth Date: Sat, 8 Nov 2025 14:53:56 -0800 Subject: [PATCH 2/4] Add test cases for lrem --- src/tools/list.py | 42 +++++++++++++-------------- tests/tools/test_list.py | 61 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/tools/list.py b/src/tools/list.py index aca9d46..43066aa 100644 --- a/src/tools/list.py +++ b/src/tools/list.py @@ -85,24 +85,24 @@ async def llen(name: str) -> int: @mcp.tool() async def lrem(name: str, count: int, element: FieldT) -> str: -    """Remove elements from a Redis list. -    -    Args: -        name: The name of the list -        count: Number of elements to remove (0 = all, positive = from head, negative = from tail) -        element: The element value to remove -        -    Returns: -        A string indicating the number of elements removed. -    """ -    try: -        r = RedisConnectionManager.get_connection() -        removed_count = r.lrem(name, count, element) -        -        if removed_count == 0: -            return f"Element '{element}' not found in list '{name}' or list does not exist." -        else: -            return f"Removed {removed_count} occurrence(s) of '{element}' from list '{name}'." -            -    except RedisError as e: -        return f"Error removing element from list '{name}': {str(e)}" + """Remove elements from a Redis list. + + Args: + name: The name of the list + count: Number of elements to remove (0 = all, positive = from head, negative = from tail) + element: The element value to remove + + Returns: + A string indicating the number of elements removed. + """ + try: + r = RedisConnectionManager.get_connection() + removed_count = r.lrem(name, count, element) + + if removed_count == 0: + return f"Element '{element}' not found in list '{name}' or list does not exist." + else: + return f"Removed {removed_count} occurrence(s) of '{element}' from list '{name}'." + + except RedisError as e: + return f"Error removing element from list '{name}': {str(e)}" diff --git a/tests/tools/test_list.py b/tests/tools/test_list.py index 8ec9c78..a5e2bcf 100644 --- a/tests/tools/test_list.py +++ b/tests/tools/test_list.py @@ -5,7 +5,7 @@ import pytest from redis.exceptions import RedisError -from src.tools.list import llen, lpop, lpush, lrange, rpop, rpush +from src.tools.list import llen, lpop, lpush, lrange, rpop, rpush, lrem class TestListOperations: @@ -278,3 +278,62 @@ async def test_push_operations_return_new_length( # Results should indicate successful push regardless of return value assert "pushed to the left of list" in result1 assert "pushed to the right of list" in result2 + + @pytest.mark.asyncio + async def test_lrem_success_single_removal(self, mock_redis_connection_manager): + """Test successful removal of a single matching element.""" + mock_redis = mock_redis_connection_manager + mock_redis.lrem.return_value = 1 + + result = await lrem("test_list", 1, "value1") + + mock_redis.lrem.assert_called_once_with("test_list", 1, "value1") + assert "Removed 1 occurrence(s) of 'value1' from list 'test_list'" in result + + @pytest.mark.asyncio + async def test_lrem_success_multiple_removal(self, mock_redis_connection_manager): + """Test successful removal of multiple matching elements.""" + mock_redis = mock_redis_connection_manager + mock_redis.lrem.return_value = 3 + + result = await lrem("test_list", 0, "value1") + + mock_redis.lrem.assert_called_once_with("test_list", 0, "value1") + assert "Removed 3 occurrence(s) of 'value1' from list 'test_list'" in result + + @pytest.mark.asyncio + async def test_lrem_no_elements_removed(self, mock_redis_connection_manager): + """Test when no elements are removed because the element is not found.""" + mock_redis = mock_redis_connection_manager + mock_redis.lrem.return_value = 0 + + result = await lrem("test_list", 2, "missing_value") + + mock_redis.lrem.assert_called_once_with("test_list", 2, "missing_value") + assert ( + "Element 'missing_value' not found in list 'test_list' or list does not exist." + in result + ) + + @pytest.mark.asyncio + async def test_lrem_negative_count(self, mock_redis_connection_manager): + """Test removal with a negative count (from tail).""" + mock_redis = mock_redis_connection_manager + mock_redis.lrem.return_value = 2 + + result = await lrem("test_list", -2, "value_tail") + + mock_redis.lrem.assert_called_once_with("test_list", -2, "value_tail") + assert "Removed 2 occurrence(s) of 'value_tail' from list 'test_list'" in result + + @pytest.mark.asyncio + async def test_lrem_redis_error(self, mock_redis_connection_manager): + """Test error handling during lrem operation.""" + mock_redis = mock_redis_connection_manager + mock_redis.lrem.side_effect = RedisError("Connection failed") + + result = await lrem("test_list", 1, "value1") + + assert ( + "Error removing element from list 'test_list': Connection failed" in result + ) From e326f045bc83aabc42d2465894c0d3fe5f780280 Mon Sep 17 00:00:00 2001 From: Nishanth Date: Mon, 10 Nov 2025 10:04:42 -0800 Subject: [PATCH 3/4] running ruff format command --- src/tools/list.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tools/list.py b/src/tools/list.py index 43066aa..f9d389f 100644 --- a/src/tools/list.py +++ b/src/tools/list.py @@ -83,26 +83,27 @@ async def llen(name: str) -> int: except RedisError as e: return f"Error retrieving length of list '{name}': {str(e)}" + @mcp.tool() async def lrem(name: str, count: int, element: FieldT) -> str: """Remove elements from a Redis list. - + Args: name: The name of the list count: Number of elements to remove (0 = all, positive = from head, negative = from tail) element: The element value to remove - + Returns: A string indicating the number of elements removed. """ try: r = RedisConnectionManager.get_connection() removed_count = r.lrem(name, count, element) - + if removed_count == 0: return f"Element '{element}' not found in list '{name}' or list does not exist." else: return f"Removed {removed_count} occurrence(s) of '{element}' from list '{name}'." - + except RedisError as e: return f"Error removing element from list '{name}': {str(e)}" From 2636929ecd75adf061f3d6fc1beee3157cd72c9d Mon Sep 17 00:00:00 2001 From: Nishanth Date: Tue, 11 Nov 2025 11:43:20 -0800 Subject: [PATCH 4/4] fix the failed tests --- tests/test_integration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 2d5a1f7..95705ca 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -238,6 +238,7 @@ def test_server_lists_tools(self, server_process): "xdel", "set", "get", + "lrem", ] for tool in tool_names: assert tool in expected_tools, ( @@ -268,7 +269,7 @@ def test_server_tool_count_and_names(self, server_process): tool_names = [tool["name"] for tool in tools] # Expected tool count (based on @mcp.tool() decorators in codebase) - expected_tool_count = 44 + expected_tool_count = 45 assert len(tools) == expected_tool_count, ( f"Expected {expected_tool_count} tools, but got {len(tools)}" ) @@ -295,6 +296,7 @@ def test_server_tool_count_and_names(self, server_process): "json_get", "json_set", "llen", + "lrem", "lpop", "lpush", "lrange",