From 6013ef7d9355aae88e3d966075823bb12c6e4671 Mon Sep 17 00:00:00 2001 From: Yi-Pin Chen Date: Sat, 29 Jun 2024 13:55:33 -0700 Subject: [PATCH] Python: add FUNCTION DELETE command (#1714) * Python: adds FUNCTION DELETE command Co-authored-by: Shoham Elias --- CHANGELOG.md | 1 + .../glide/async_commands/cluster_commands.py | 31 ++++++++++ .../async_commands/standalone_commands.py | 26 +++++++++ .../glide/async_commands/transaction.py | 19 ++++++ python/python/tests/test_async_client.py | 58 +++++++++++++++++++ python/python/tests/test_transaction.py | 2 + 6 files changed, 137 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 146267524d..dc7bc271ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ * Python: Added XPENDING command ([#1704](https://github.com/aws/glide-for-redis/pull/1704)) * Python: Added RANDOMKEY command ([#1701](https://github.com/aws/glide-for-redis/pull/1701)) * Python: Added FUNCTION FLUSH command ([#1700](https://github.com/aws/glide-for-redis/pull/1700)) +* Python: Added FUNCTION DELETE command ([#1714](https://github.com/aws/glide-for-redis/pull/1714)) ### Breaking Changes * Node: Update XREAD to return a Map of Map ([#1494](https://github.com/aws/glide-for-redis/pull/1494)) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 290478d3cc..38ea221af3 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -384,6 +384,37 @@ async def function_flush( ), ) + async def function_delete( + self, library_name: str, route: Optional[Route] = None + ) -> TOK: + """ + Deletes a library and all its functions. + + See https://valkey.io/docs/latest/commands/function-delete/ for more details. + + Args: + library_code (str): The libary name to delete + route (Optional[Route]): The command will be routed to all primaries, unless `route` is provided, + in which case the client will route the command to the nodes defined by `route`. + + Returns: + TOK: A simple `OK`. + + Examples: + >>> await client.function_delete("my_lib") + "OK" + + Since: Redis 7.0.0. + """ + return cast( + TOK, + await self._execute_command( + RequestType.FunctionDelete, + [library_name], + route, + ), + ) + async def time(self, route: Optional[Route] = None) -> TClusterResponse[List[str]]: """ Returns the server time. diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 2cb15a3621..57d5063735 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -288,6 +288,32 @@ async def function_flush(self, mode: Optional[FlushMode] = None) -> TOK: ), ) + async def function_delete(self, library_name: str) -> TOK: + """ + Deletes a library and all its functions. + + See https://valkey.io/docs/latest/commands/function-delete/ for more details. + + Args: + library_code (str): The libary name to delete + + Returns: + TOK: A simple `OK`. + + Examples: + >>> await client.function_delete("my_lib") + "OK" + + Since: Redis 7.0.0. + """ + return cast( + TOK, + await self._execute_command( + RequestType.FunctionDelete, + [library_name], + ), + ) + async def time(self) -> List[str]: """ Returns the server time. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 402232a55b..95a4732247 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -1822,6 +1822,25 @@ def function_flush( [mode.value] if mode else [], ) + def function_delete(self: TTransaction, library_name: str) -> TTransaction: + """ + Deletes a library and all its functions. + + See https://valkey.io/docs/latest/commands/function-delete/ for more details. + + Args: + library_code (str): The libary name to delete + + Commands response: + TOK: A simple `OK`. + + Since: Redis 7.0.0. + """ + return self.append_command( + RequestType.FunctionDelete, + [library_name], + ) + def xadd( self: TTransaction, key: str, diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 6b49e6e43b..16c282198c 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -6673,6 +6673,7 @@ async def test_function_load(self, redis_client: TGlideClient): assert await redis_client.function_load(code, True) == lib_name func2_name = f"myfunc2c{get_random_string(5)}" + new_code = f"""{code}\n redis.register_function({func2_name}, function(keys, args) return #args end)""" new_code = generate_lua_lib_code( lib_name, {func_name: "return args[1]", func2_name: "return #args"}, True ) @@ -6725,6 +6726,7 @@ async def test_function_load_cluster_with_route( assert await redis_client.function_load(code, True, route) == lib_name func2_name = f"myfunc2c{get_random_string(5)}" + new_code = f"""{code}\n redis.register_function({func2_name}, function(keys, args) return #args end)""" new_code = generate_lua_lib_code( lib_name, {func_name: "return args[1]", func2_name: "return #args"}, True ) @@ -6791,6 +6793,62 @@ async def test_function_flush_with_routing( # Clean up by flushing functions again assert await redis_client.function_flush(route=route) == OK + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_function_delete(self, redis_client: TGlideClient): + min_version = "7.0.0" + if await check_if_server_version_lt(redis_client, min_version): + pytest.skip(f"Redis version required >= {min_version}") + + lib_name = f"mylib1C{get_random_string(5)}" + func_name = f"myfunc1c{get_random_string(5)}" + code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, True) + + # Load the function + assert await redis_client.function_load(code) == lib_name + + # TODO: Ensure the library exists with FUNCTION LIST + + # Delete the function + assert await redis_client.function_delete(lib_name) == OK + + # TODO: Ensure the function is no longer present with FUNCTION LIST + + # deleting a non-existing library + with pytest.raises(RequestError) as e: + await redis_client.function_delete(lib_name) + assert "Library not found" in str(e) + + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + @pytest.mark.parametrize("single_route", [True, False]) + async def test_function_delete_with_routing( + self, redis_client: GlideClusterClient, single_route: bool + ): + min_version = "7.0.0" + if await check_if_server_version_lt(redis_client, min_version): + pytest.skip(f"Redis version required >= {min_version}") + + lib_name = f"mylib1C{get_random_string(5)}" + func_name = f"myfunc1c{get_random_string(5)}" + code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, True) + route = SlotKeyRoute(SlotType.PRIMARY, "1") if single_route else AllPrimaries() + + # Load the function + assert await redis_client.function_load(code, False, route) == lib_name + + # TODO: Ensure the library exists with FUNCTION LIST + + # Delete the function + assert await redis_client.function_delete(lib_name, route) == OK + + # TODO: Ensure the function is no longer present with FUNCTION LIST + + # deleting a non-existing library + with pytest.raises(RequestError) as e: + await redis_client.function_delete(lib_name) + assert "Library not found" in str(e) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_srandmember(self, redis_client: TGlideClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 18552c5735..a880f26b27 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -102,6 +102,8 @@ async def transaction_test( args.append(lib_name) transaction.function_load(code, True) args.append(lib_name) + transaction.function_delete(lib_name) + args.append(OK) transaction.function_flush() args.append(OK) transaction.function_flush(FlushMode.ASYNC)