From c36cddacc1c3ec35b13341a7d7ecdfbba1eabff7 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Tue, 2 Jul 2024 11:13:17 -0700 Subject: [PATCH 01/15] Add cluster and standalone versions of FUNCTION STATS commands --- .../glide/async_commands/cluster_commands.py | 40 +++++++++++++++++++ .../async_commands/standalone_commands.py | 39 ++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 5892066452..19b03e0416 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -450,6 +450,46 @@ async def fcall_ro_route( await self._execute_command(RequestType.FCallReadOnly, args, route), ) + async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[Mapping[str, Mapping[str, TResult]]]: + """ + Returns information about the function that's currently running and information about the + available execution engines. + + See https://redis.io/commands/function-stats/ for more details + + Args: + route (Optional[Route]): Specifies the routing configuration for the command. The client + will route the command to the nodes defined by `route`. + + Returns: + TClusterResponse[Mapping[str, Mapping[str, TResult]]]: A `Mapping` with two keys: + - `running_script` with information about the running script. + - `engines` with information about available engines and their stats. + See example for more details. + + Examples: + >>> await client.function_stats(RandomNode()) + { + 'running_script': { + 'name': 'foo', + 'command': ['FCALL', 'foo', '0', 'hello'], + 'duration_ms': 7758 + }, + 'engines': { + 'LUA': { + 'libraries_count': 1, + 'functions_count': 1, + } + } + } + + Since: Redis version 7.0.0. + """ + return cast( + TClusterResponse[Mapping[str, Mapping[str, TResult]]], + await self._execute_command(RequestType.FunctionStats, [], 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 4c0ee234aa..a4cd1a41e0 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -314,6 +314,45 @@ async def function_delete(self, library_name: str) -> TOK: ), ) + async def function_stats(self) -> Mapping[str, Mapping[str, TResult]]: + """ + Returns information about the function that's currently running and information about the + available execution engines. + + See https://redis.io/commands/function-stats/ for more details + + Returns: + Mapping[str, Mapping[str, TResult]]: A `Mapping` with two keys: + `running_script` with information about the running script, and + `engines` with information about available engines and their stats. + See example for more details. + + Examples: + >>> await client.function_stats() + { + 'running_script': { + 'name': 'foo', + 'command': ['FCALL', 'foo', '0', 'hello'], + 'duration_ms': 7758 + }, + 'engines': { + 'LUA': { + 'libraries_count': 1, + 'functions_count': 1, + } + } + } + + Since: Redis version 7.0.0. + """ + return cast( + Mapping[str, Mapping[str, TResult]], + await self._execute_command( + RequestType.FunctionStats, + [] + ) + ) + async def time(self) -> List[str]: """ Returns the server time. From 8a3f898eb6c85c5b1e4cbc2e4a4ade764345026e Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Tue, 2 Jul 2024 14:48:36 -0700 Subject: [PATCH 02/15] Add transaction implementation for FUNCTION STATS --- .../python/glide/async_commands/transaction.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 88daf11945..a91712bc69 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2018,6 +2018,23 @@ def fcall_ro( args.extend(arguments) return self.append_command(RequestType.FCallReadOnly, args) + def function_stats(self: TTransaction) -> TTransaction: + """ + Returns information about the function that's currently running and information about the + available execution engines. + + See https://redis.io/commands/function-stats/ for more details + + Command Response: + Mapping[TEncodable, Mapping[TEncodable, TResult]]: A `Mapping` with two keys: + - `running_script` with information about the running script. + - `engines` with information about available engines and their stats. + See example for more details. + + Since: Redis version 7.0.0. + """ + return self.append_command(RequestType.FunctionStats, []) + def xadd( self: TTransaction, key: TEncodable, From e44836ad0e153b73aafadba15c3caed0550f8879 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Tue, 2 Jul 2024 17:47:34 -0700 Subject: [PATCH 03/15] Start adding tests for FUNCTION STATS --- python/python/tests/test_async_client.py | 21 +++++++++++++++++++++ python/python/tests/test_transaction.py | 13 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 7bde22635f..d7be840540 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7631,6 +7631,27 @@ async def test_function_delete_with_routing( 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_function_stats(self, redis_client: TGlideClient): + min_version = "7.0.0" + if await check_if_server_version_lt(redis_client, min_version): + return pytest.mark.skip(reason=f"Redis version required >= {min_version}") + + + + @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_stats_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): + return pytest.mark.skip(reason=f"Redis version required >= {min_version}") + + + @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_fcall_with_key(self, redis_client: GlideClusterClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 221d7e29c0..1a982b1499 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -158,6 +158,19 @@ async def transaction_test( args.append(OK) transaction.function_flush(FlushMode.SYNC) args.append(OK) + transaction.function_stats() + args.append([ + { + b"running_script": { + }, + b"engines": { + b"LUA": { + b"libraries_count": 1, + b"functions_count": 1, + } + } + } + ]) transaction.dbsize() args.append(0) From 70d8f9d527835a76b7078d74c95f508cd508e3b5 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 11:48:14 -0700 Subject: [PATCH 04/15] Try finish implementing tests --- .../glide/async_commands/cluster_commands.py | 6 +- .../async_commands/standalone_commands.py | 7 +- .../glide/async_commands/transaction.py | 2 +- python/python/tests/test_async_client.py | 102 +++++++++++++++++- python/python/tests/test_transaction.py | 3 +- 5 files changed, 107 insertions(+), 13 deletions(-) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 97d4898bec..df520eb67e 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -554,7 +554,7 @@ async def fcall_ro_route( await self._execute_command(RequestType.FCallReadOnly, args, route), ) - async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[Mapping[TEncodable, Mapping[TEncodable, TResult]]]: + async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]]: """ Returns information about the function that's currently running and information about the available execution engines. @@ -566,7 +566,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons will route the command to the nodes defined by `route`. Returns: - TClusterResponse[Mapping[TEncodable, Mapping[TEncodable, TResult]]]: A `Mapping` with two keys: + TClusterResponse[Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]]: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. @@ -590,7 +590,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons Since: Redis version 7.0.0. """ return cast( - TClusterResponse[Mapping[TEncodable, Mapping[TEncodable, TResult]]], + Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]], await self._execute_command(RequestType.FunctionStats, [], route) ) diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 70c01df31b..2bb0a8f9bb 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -361,8 +361,7 @@ async def function_delete(self, library_name: TEncodable) -> TOK: ), ) -<<<<<<< HEAD - async def function_stats(self) -> Mapping[TEncodable, Mapping[TEncodable, TResult]]: + async def function_stats(self) -> Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: """ Returns information about the function that's currently running and information about the available execution engines. @@ -370,7 +369,7 @@ async def function_stats(self) -> Mapping[TEncodable, Mapping[TEncodable, TResul See https://redis.io/commands/function-stats/ for more details Returns: - Mapping[TEncodable, Mapping[TEncodable, TResult]]: A `Mapping` with two keys: + Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. @@ -394,7 +393,7 @@ async def function_stats(self) -> Mapping[TEncodable, Mapping[TEncodable, TResul Since: Redis version 7.0.0. """ return cast( - Mapping[TEncodable, Mapping[TEncodable, TResult]], + Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]], await self._execute_command( RequestType.FunctionStats, [] diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index a91712bc69..b02bcb1eb6 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2026,7 +2026,7 @@ def function_stats(self: TTransaction) -> TTransaction: See https://redis.io/commands/function-stats/ for more details Command Response: - Mapping[TEncodable, Mapping[TEncodable, TResult]]: A `Mapping` with two keys: + Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index d7be840540..3975e57c38 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7631,14 +7631,78 @@ async def test_function_delete_with_routing( await redis_client.function_delete(lib_name) assert "Library not found" in str(e) - @pytest.mark.parametrize("cluster_mode", [True, False]) + def check_function_stats_response(response, running_function, lib_count, function_count): + running_script_info = response["running_script"] + if running_script_info == None and len(running_function) != 0: + pytest.fail("No running function info") + + if running_script_info != None and len(running_function) == 0: + command = running_script_info["command"] + pytest.fail("Unexpected running function info: " + " ".join(command)) + + if running_script_info != None: + command = running_script_info["command"] + assert running_function == command + # command line format is: + # fcall|fcall_ro * * + assert running_function[1] == running_script_info["name"] + + expected = {"LUA": {"libraries_count": lib_count, "functions_count": function_count}} + assert expected == response["engines"] + + @pytest.mark.parametrize("cluster_mode", [False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_function_stats(self, redis_client: TGlideClient): + async def test_function_stats(self, redis_client: GlideClient): min_version = "7.0.0" if await check_if_server_version_lt(redis_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") + lib_name = "functionStats" + func_name = lib_name + assert await redis_client.function_flush(FlushMode.SYNC) == OK + code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) + assert await redis_client.function_load(code, True) == lib_name.encode() + + response = await redis_client.function_stats() + check_function_stats_response(response, [], 1, 1) + + code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) + + assert await redis_client.function_stats() + check_function_stats_response(response, [], 2, 3) + + assert await redis_client.function_flush(FlushMode.SYNC) == OK + + assert await redis_client.function_stats() + check_function_stats_response(response, [], 0, 0) + + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_function_stats_cluster(self, redis_client: GlideClusterClient): + min_version = "7.0.0" + if await check_if_server_version_lt(redis_client, min_version): + return pytest.mark.skip(reason=f"Redis version required >= {min_version}") + + lib_name = "functionStats_without_route" + func_name = lib_name + assert await redis_client.function_flush(FlushMode.SYNC) == OK + + code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) + assert await redis_client.function_load(code, True) == lib_name.encode() + + response = await redis_client.function_stats() + check_function_stats_response(response, [], 1, 1) + + code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) + + assert await redis_client.function_stats() + check_function_stats_response(response, [], 2, 3) + + assert await redis_client.function_flush(FlushMode.SYNC) == OK + + assert await redis_client.function_stats() + check_function_stats_response(response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -7650,7 +7714,39 @@ async def test_function_stats_with_routing( if await check_if_server_version_lt(redis_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") - + route = SlotKeyRoute(get_random_string(10), SlotType.PRIMARY) if single_route else AllPrimaries() + lib_name = "functionStats_with_route_" + str(single_route) + func_name = lib_name + assert await redis_client.function_flush(FlushMode.SYNC, route) == OK + + code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) + assert await redis_client.function_load(code, True, route) == lib_name.encode() + + response = await redis_client.function_stats(route) + if single_route: + check_function_stats_response(response, [], 1, 1) + else: + for node_response in response: + check_function_stats_response(node_response, [], 1, 1) + + code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) + assert await redis_client.function_load(code, True, route) == (lib_name + "_2").encode() + + response = await redis_client.function_stats(route) + if single_route: + check_function_stats_response(response, [], 2, 3) + else: + for node_response in response: + check_function_stats_response(node_response, [], 2, 3) + + assert await redis_client.function_flush(FlushMode.SYNC, route) == OK + + response = await redis_client.function_stats(route) + if single_route: + check_function_stats_response(response, [], 0, 0) + else: + for node_response in response: + check_function_stats_response(node_response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 1a982b1499..348b1b25c6 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -161,8 +161,7 @@ async def transaction_test( transaction.function_stats() args.append([ { - b"running_script": { - }, + b"running_script": None, b"engines": { b"LUA": { b"libraries_count": 1, From 6a66acdba807c97c457adc6071a9fedfc377cb57 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 12:10:35 -0700 Subject: [PATCH 05/15] Fix some issues in the tests --- python/python/tests/test_async_client.py | 35 +++++++----------------- python/python/tests/utils/utils.py | 20 ++++++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 3975e57c38..f36219a418 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -90,6 +90,7 @@ from tests.conftest import create_client from tests.utils.utils import ( check_function_list_response, + check_function_stats_response, check_if_server_version_lt, compare_maps, convert_bytes_to_string_object, @@ -7631,25 +7632,6 @@ async def test_function_delete_with_routing( await redis_client.function_delete(lib_name) assert "Library not found" in str(e) - def check_function_stats_response(response, running_function, lib_count, function_count): - running_script_info = response["running_script"] - if running_script_info == None and len(running_function) != 0: - pytest.fail("No running function info") - - if running_script_info != None and len(running_function) == 0: - command = running_script_info["command"] - pytest.fail("Unexpected running function info: " + " ".join(command)) - - if running_script_info != None: - command = running_script_info["command"] - assert running_function == command - # command line format is: - # fcall|fcall_ro * * - assert running_function[1] == running_script_info["name"] - - expected = {"LUA": {"libraries_count": lib_count, "functions_count": function_count}} - assert expected == response["engines"] - @pytest.mark.parametrize("cluster_mode", [False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_function_stats(self, redis_client: GlideClient): @@ -7692,17 +7674,20 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): assert await redis_client.function_load(code, True) == lib_name.encode() response = await redis_client.function_stats() - check_function_stats_response(response, [], 1, 1) + for node_response in response.values(): + check_function_stats_response(node_response, [], 1, 1) code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) assert await redis_client.function_stats() - check_function_stats_response(response, [], 2, 3) + for node_response in response.values(): + check_function_stats_response(node_response, [], 2, 3) assert await redis_client.function_flush(FlushMode.SYNC) == OK assert await redis_client.function_stats() - check_function_stats_response(response, [], 0, 0) + for node_response in response.values(): + check_function_stats_response(node_response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -7726,7 +7711,7 @@ async def test_function_stats_with_routing( if single_route: check_function_stats_response(response, [], 1, 1) else: - for node_response in response: + for node_response in response.values(): check_function_stats_response(node_response, [], 1, 1) code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) @@ -7736,7 +7721,7 @@ async def test_function_stats_with_routing( if single_route: check_function_stats_response(response, [], 2, 3) else: - for node_response in response: + for node_response in response.values(): check_function_stats_response(node_response, [], 2, 3) assert await redis_client.function_flush(FlushMode.SYNC, route) == OK @@ -7745,7 +7730,7 @@ async def test_function_stats_with_routing( if single_route: check_function_stats_response(response, [], 0, 0) else: - for node_response in response: + for node_response in response.values(): check_function_stats_response(node_response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index cd1ac3a8c7..7b077135d7 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -269,3 +269,23 @@ def check_function_list_response( break assert has_lib is True + +def check_function_stats_response(response, running_function, lib_count, function_count): + running_script_info = response.get(b"running_script") + if running_script_info == None and len(running_function) != 0: + pytest.fail("No running function info") + + if running_script_info != None and len(running_function) == 0: + command = running_script_info.get(b"command") + pytest.fail("Unexpected running function info: " + " ".join(command)) + + if running_script_info != None: + command = running_script_info.get(b"command") + assert running_function == command + # command line format is: + # fcall|fcall_ro * * + assert running_function[1] == running_script_info.get(b"name") + + expected = {b"LUA": {b"libraries_count": lib_count, b"functions_count": function_count}} + assert expected == response.get(b"engines") + From 5b591f0ac35d7af23e3726e186163cae278211fc Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 12:28:48 -0700 Subject: [PATCH 06/15] Fix tests --- python/python/tests/test_async_client.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index f36219a418..d9a199ad21 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7650,13 +7650,14 @@ async def test_function_stats(self, redis_client: GlideClient): check_function_stats_response(response, [], 1, 1) code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) + assert await redis_client.function_load(code, True) == (lib_name + "_2").encode() - assert await redis_client.function_stats() + response = await redis_client.function_stats() check_function_stats_response(response, [], 2, 3) assert await redis_client.function_flush(FlushMode.SYNC) == OK - assert await redis_client.function_stats() + response = await redis_client.function_stats() check_function_stats_response(response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) @@ -7678,14 +7679,15 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): check_function_stats_response(node_response, [], 1, 1) code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) + assert await redis_client.function_load(code, True) == (lib_name + "_2").encode() - assert await redis_client.function_stats() + response = await redis_client.function_stats() for node_response in response.values(): check_function_stats_response(node_response, [], 2, 3) assert await redis_client.function_flush(FlushMode.SYNC) == OK - assert await redis_client.function_stats() + response = await redis_client.function_stats() for node_response in response.values(): check_function_stats_response(node_response, [], 0, 0) @@ -7699,7 +7701,7 @@ async def test_function_stats_with_routing( if await check_if_server_version_lt(redis_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") - route = SlotKeyRoute(get_random_string(10), SlotType.PRIMARY) if single_route else AllPrimaries() + route = SlotKeyRoute(SlotType.PRIMARY, get_random_string(10)) if single_route else AllPrimaries() lib_name = "functionStats_with_route_" + str(single_route) func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC, route) == OK From 95ca3978c95b888627395a05e8df48fdd14765bd Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 14:12:48 -0700 Subject: [PATCH 07/15] Document check_function_stats_response --- .../glide/async_commands/cluster_commands.py | 7 ++++--- .../async_commands/standalone_commands.py | 14 +++++++------- .../python/glide/async_commands/transaction.py | 2 +- python/python/glide/constants.py | 15 +++++++++++++++ python/python/tests/test_async_client.py | 3 +++ python/python/tests/utils/utils.py | 18 ++++++++++++++++-- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index df520eb67e..27a58f6037 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -17,6 +17,7 @@ TClusterResponse, TEncodable, TFunctionListResponse, + TFunctionStatsResponse, TResult, TSingleNodeRoute, ) @@ -554,7 +555,7 @@ async def fcall_ro_route( await self._execute_command(RequestType.FCallReadOnly, args, route), ) - async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]]: + async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[TFunctionStatsResponse]: """ Returns information about the function that's currently running and information about the available execution engines. @@ -566,7 +567,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons will route the command to the nodes defined by `route`. Returns: - TClusterResponse[Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]]: A `Mapping` with two keys: + TClusterResponse[TFunctionStatsResponse]: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. @@ -590,7 +591,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons Since: Redis version 7.0.0. """ return cast( - Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]], + TClusterResponse[TFunctionStatsResponse], await self._execute_command(RequestType.FunctionStats, [], route) ) diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 2bb0a8f9bb..09e2b61f69 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -12,7 +12,7 @@ _build_sort_args, ) from glide.async_commands.transaction import BaseTransaction, Transaction -from glide.constants import OK, TOK, TEncodable, TFunctionListResponse, TResult +from glide.constants import OK, TOK, TEncodable, TFunctionListResponse, TFunctionStatsResponse, TResult from glide.protobuf.redis_request_pb2 import RequestType @@ -361,7 +361,7 @@ async def function_delete(self, library_name: TEncodable) -> TOK: ), ) - async def function_stats(self) -> Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: + async def function_stats(self) -> TFunctionStatsResponse: """ Returns information about the function that's currently running and information about the available execution engines. @@ -369,10 +369,10 @@ async def function_stats(self) -> Mapping[bytes, Mapping[bytes, Union[bytes, int See https://redis.io/commands/function-stats/ for more details Returns: - Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: A `Mapping` with two keys: - - `running_script` with information about the running script. - - `engines` with information about available engines and their stats. - See example for more details. + TFunctionStatsResponse: A `Mapping` with two keys: + - `running_script` with information about the running script. + - `engines` with information about available engines and their stats. + See example for more details. Examples: >>> await client.function_stats() @@ -393,7 +393,7 @@ async def function_stats(self) -> Mapping[bytes, Mapping[bytes, Union[bytes, int Since: Redis version 7.0.0. """ return cast( - Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]], + TFunctionStatsResponse, await self._execute_command( RequestType.FunctionStats, [] diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index b02bcb1eb6..756753d35f 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2026,7 +2026,7 @@ def function_stats(self: TTransaction) -> TTransaction: See https://redis.io/commands/function-stats/ for more details Command Response: - Mapping[bytes, Mapping[bytes, Union[bytes, int, List[bytes]]]]: A `Mapping` with two keys: + TFunctionStatsResponse: A `Mapping` with two keys: - `running_script` with information about the running script. - `engines` with information about available engines and their stats. See example for more details. diff --git a/python/python/glide/constants.py b/python/python/glide/constants.py index b9f5615a5f..5dc0e0d966 100644 --- a/python/python/glide/constants.py +++ b/python/python/glide/constants.py @@ -42,3 +42,18 @@ Union[bytes, List[Mapping[bytes, Union[bytes, Set[bytes]]]]], ] ] +TFunctionStatsResponse = Mapping[ + bytes, + Union[ + None, + Mapping[ + bytes, + Union[ + Mapping[bytes, Mapping[bytes, int]], + bytes, + int, + List[bytes] + ] + ] + ] +] diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index d9a199ad21..fbb93ec0bf 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7643,6 +7643,7 @@ async def test_function_stats(self, redis_client: GlideClient): func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC) == OK + # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) assert await redis_client.function_load(code, True) == lib_name.encode() @@ -7671,6 +7672,7 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC) == OK + # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) assert await redis_client.function_load(code, True) == lib_name.encode() @@ -7706,6 +7708,7 @@ async def test_function_stats_with_routing( func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC, route) == OK + # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) assert await redis_client.function_load(code, True, route) == lib_name.encode() diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index 7b077135d7..770c981aaf 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -4,7 +4,7 @@ from typing import Any, Dict, List, Mapping, Optional, Set, TypeVar, Union, cast from glide.async_commands.core import InfoSection -from glide.constants import TClusterResponse, TFunctionListResponse, TResult +from glide.constants import TClusterResponse, TFunctionListResponse, TFunctionStatsResponse, TResult from glide.glide_client import TGlideClient from packaging import version @@ -270,7 +270,21 @@ def check_function_list_response( assert has_lib is True -def check_function_stats_response(response, running_function, lib_count, function_count): +def check_function_stats_response( + response: TFunctionStatsResponse, + running_function: List[bytes], + lib_count: int, + function_count: int +): + """ + Validate whether `FUNCTION STATS` response contains required info. + + Args: + response (TFunctionStatsResponse): The response from server. + running_function (List[bytes]): Command line of running function expected. Empty, if nothing expected. + lib_count (int): Expected libraries count. + function_count (int): Expected functions count. + """ running_script_info = response.get(b"running_script") if running_script_info == None and len(running_function) != 0: pytest.fail("No running function info") From ce505722fb4eec684e35a0bd144f1146777647c9 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 14:17:22 -0700 Subject: [PATCH 08/15] Run linters and formatters --- .../glide/async_commands/cluster_commands.py | 8 ++-- .../async_commands/standalone_commands.py | 14 +++--- python/python/glide/constants.py | 12 ++---- python/python/tests/test_async_client.py | 43 ++++++++++++++----- python/python/tests/test_transaction.py | 22 +++++----- python/python/tests/utils/utils.py | 15 +++++-- 6 files changed, 73 insertions(+), 41 deletions(-) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 27a58f6037..64d9c5e089 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -555,7 +555,9 @@ async def fcall_ro_route( await self._execute_command(RequestType.FCallReadOnly, args, route), ) - async def function_stats(self, route: Optional[Route] = None) -> TClusterResponse[TFunctionStatsResponse]: + async def function_stats( + self, route: Optional[Route] = None + ) -> TClusterResponse[TFunctionStatsResponse]: """ Returns information about the function that's currently running and information about the available execution engines. @@ -572,7 +574,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons - `engines` with information about available engines and their stats. See example for more details. - Examples: + Examples: >>> await client.function_stats(RandomNode()) { 'running_script': { @@ -592,7 +594,7 @@ async def function_stats(self, route: Optional[Route] = None) -> TClusterRespons """ return cast( TClusterResponse[TFunctionStatsResponse], - await self._execute_command(RequestType.FunctionStats, [], route) + await self._execute_command(RequestType.FunctionStats, [], route), ) async def time( diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 09e2b61f69..ff25983808 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -12,7 +12,14 @@ _build_sort_args, ) from glide.async_commands.transaction import BaseTransaction, Transaction -from glide.constants import OK, TOK, TEncodable, TFunctionListResponse, TFunctionStatsResponse, TResult +from glide.constants import ( + OK, + TOK, + TEncodable, + TFunctionListResponse, + TFunctionStatsResponse, + TResult, +) from glide.protobuf.redis_request_pb2 import RequestType @@ -394,10 +401,7 @@ async def function_stats(self) -> TFunctionStatsResponse: """ return cast( TFunctionStatsResponse, - await self._execute_command( - RequestType.FunctionStats, - [] - ) + await self._execute_command(RequestType.FunctionStats, []), ) async def time(self) -> List[bytes]: diff --git a/python/python/glide/constants.py b/python/python/glide/constants.py index 5dc0e0d966..c1c9479c96 100644 --- a/python/python/glide/constants.py +++ b/python/python/glide/constants.py @@ -47,13 +47,7 @@ Union[ None, Mapping[ - bytes, - Union[ - Mapping[bytes, Mapping[bytes, int]], - bytes, - int, - List[bytes] - ] - ] - ] + bytes, Union[Mapping[bytes, Mapping[bytes, int]], bytes, int, List[bytes]] + ], + ], ] diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index fbb93ec0bf..5cfe0804cb 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7642,7 +7642,7 @@ async def test_function_stats(self, redis_client: GlideClient): lib_name = "functionStats" func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC) == OK - + # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) assert await redis_client.function_load(code, True) == lib_name.encode() @@ -7650,8 +7650,14 @@ async def test_function_stats(self, redis_client: GlideClient): response = await redis_client.function_stats() check_function_stats_response(response, [], 1, 1) - code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) - assert await redis_client.function_load(code, True) == (lib_name + "_2").encode() + code = generate_lua_lib_code( + lib_name + "_2", + {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, + False, + ) + assert ( + await redis_client.function_load(code, True) == (lib_name + "_2").encode() + ) response = await redis_client.function_stats() check_function_stats_response(response, [], 2, 3) @@ -7671,7 +7677,7 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): lib_name = "functionStats_without_route" func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC) == OK - + # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) assert await redis_client.function_load(code, True) == lib_name.encode() @@ -7680,8 +7686,14 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): for node_response in response.values(): check_function_stats_response(node_response, [], 1, 1) - code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) - assert await redis_client.function_load(code, True) == (lib_name + "_2").encode() + code = generate_lua_lib_code( + lib_name + "_2", + {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, + False, + ) + assert ( + await redis_client.function_load(code, True) == (lib_name + "_2").encode() + ) response = await redis_client.function_stats() for node_response in response.values(): @@ -7703,7 +7715,11 @@ async def test_function_stats_with_routing( if await check_if_server_version_lt(redis_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") - route = SlotKeyRoute(SlotType.PRIMARY, get_random_string(10)) if single_route else AllPrimaries() + route = ( + SlotKeyRoute(SlotType.PRIMARY, get_random_string(10)) + if single_route + else AllPrimaries() + ) lib_name = "functionStats_with_route_" + str(single_route) func_name = lib_name assert await redis_client.function_flush(FlushMode.SYNC, route) == OK @@ -7719,8 +7735,15 @@ async def test_function_stats_with_routing( for node_response in response.values(): check_function_stats_response(node_response, [], 1, 1) - code = generate_lua_lib_code(lib_name + "_2", {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, False) - assert await redis_client.function_load(code, True, route) == (lib_name + "_2").encode() + code = generate_lua_lib_code( + lib_name + "_2", + {func_name + "_2": "return 'OK'", func_name + "_3": "return 42"}, + False, + ) + assert ( + await redis_client.function_load(code, True, route) + == (lib_name + "_2").encode() + ) response = await redis_client.function_stats(route) if single_route: @@ -7728,7 +7751,7 @@ async def test_function_stats_with_routing( else: for node_response in response.values(): check_function_stats_response(node_response, [], 2, 3) - + assert await redis_client.function_flush(FlushMode.SYNC, route) == OK response = await redis_client.function_stats(route) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 348b1b25c6..65380641c9 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -159,17 +159,19 @@ async def transaction_test( transaction.function_flush(FlushMode.SYNC) args.append(OK) transaction.function_stats() - args.append([ - { - b"running_script": None, - b"engines": { - b"LUA": { - b"libraries_count": 1, - b"functions_count": 1, - } + args.append( + [ + { + b"running_script": None, + b"engines": { + b"LUA": { + b"libraries_count": 1, + b"functions_count": 1, + } + }, } - } - ]) + ] + ) transaction.dbsize() args.append(0) diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index 770c981aaf..c9965213c7 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -4,7 +4,12 @@ from typing import Any, Dict, List, Mapping, Optional, Set, TypeVar, Union, cast from glide.async_commands.core import InfoSection -from glide.constants import TClusterResponse, TFunctionListResponse, TFunctionStatsResponse, TResult +from glide.constants import ( + TClusterResponse, + TFunctionListResponse, + TFunctionStatsResponse, + TResult, +) from glide.glide_client import TGlideClient from packaging import version @@ -270,11 +275,12 @@ def check_function_list_response( assert has_lib is True + def check_function_stats_response( response: TFunctionStatsResponse, running_function: List[bytes], lib_count: int, - function_count: int + function_count: int, ): """ Validate whether `FUNCTION STATS` response contains required info. @@ -300,6 +306,7 @@ def check_function_stats_response( # fcall|fcall_ro * * assert running_function[1] == running_script_info.get(b"name") - expected = {b"LUA": {b"libraries_count": lib_count, b"functions_count": function_count}} + expected = { + b"LUA": {b"libraries_count": lib_count, b"functions_count": function_count} + } assert expected == response.get(b"engines") - From b59a91a8cee71f1f040c5991827d2445ceba722f Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 14:44:20 -0700 Subject: [PATCH 09/15] Import pytest --- python/python/tests/utils/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index c9965213c7..89d601409a 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -1,4 +1,5 @@ import json +import pytest import random import string from typing import Any, Dict, List, Mapping, Optional, Set, TypeVar, Union, cast From d10741655aa9e89a8b902c278692046735966ba0 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 14:47:08 -0700 Subject: [PATCH 10/15] Fix import ordering --- python/python/tests/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index 89d601409a..1357fded8e 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -1,9 +1,9 @@ import json -import pytest import random import string from typing import Any, Dict, List, Mapping, Optional, Set, TypeVar, Union, cast +import pytest from glide.async_commands.core import InfoSection from glide.constants import ( TClusterResponse, From 8a96046340e81b0846bad975d3d6a020c1eb683b Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 3 Jul 2024 15:13:40 -0700 Subject: [PATCH 11/15] Fix mypy errors --- python/python/tests/test_async_client.py | 38 +++++++++++++++++------- python/python/tests/utils/utils.py | 8 ++--- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 5cfe0804cb..e6dbc2f19e 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -75,7 +75,7 @@ ProtocolVersion, RedisCredentials, ) -from glide.constants import OK, TEncodable, TResult +from glide.constants import OK, TEncodable, TFunctionStatsResponse, TResult from glide.glide_client import GlideClient, GlideClusterClient, TGlideClient from glide.routes import ( AllNodes, @@ -7684,7 +7684,9 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): response = await redis_client.function_stats() for node_response in response.values(): - check_function_stats_response(node_response, [], 1, 1) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 1, 1 + ) code = generate_lua_lib_code( lib_name + "_2", @@ -7697,13 +7699,17 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): response = await redis_client.function_stats() for node_response in response.values(): - check_function_stats_response(node_response, [], 2, 3) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 2, 3 + ) assert await redis_client.function_flush(FlushMode.SYNC) == OK response = await redis_client.function_stats() for node_response in response.values(): - check_function_stats_response(node_response, [], 0, 0) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 0, 0 + ) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -7730,10 +7736,14 @@ async def test_function_stats_with_routing( response = await redis_client.function_stats(route) if single_route: - check_function_stats_response(response, [], 1, 1) + check_function_stats_response( + cast(TFunctionStatsResponse, response), [], 1, 1 + ) else: for node_response in response.values(): - check_function_stats_response(node_response, [], 1, 1) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 1, 1 + ) code = generate_lua_lib_code( lib_name + "_2", @@ -7747,19 +7757,27 @@ async def test_function_stats_with_routing( response = await redis_client.function_stats(route) if single_route: - check_function_stats_response(response, [], 2, 3) + check_function_stats_response( + cast(TFunctionStatsResponse, response), [], 2, 3 + ) else: for node_response in response.values(): - check_function_stats_response(node_response, [], 2, 3) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 2, 3 + ) assert await redis_client.function_flush(FlushMode.SYNC, route) == OK response = await redis_client.function_stats(route) if single_route: - check_function_stats_response(response, [], 0, 0) + check_function_stats_response( + cast(TFunctionStatsResponse, response), [], 0, 0 + ) else: for node_response in response.values(): - check_function_stats_response(node_response, [], 0, 0) + check_function_stats_response( + cast(TFunctionStatsResponse, node_response), [], 0, 0 + ) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index 1357fded8e..c6af8035b9 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -297,15 +297,15 @@ def check_function_stats_response( pytest.fail("No running function info") if running_script_info != None and len(running_function) == 0: - command = running_script_info.get(b"command") - pytest.fail("Unexpected running function info: " + " ".join(command)) + command = cast(dict, running_script_info).get(b"command") + pytest.fail("Unexpected running function info: " + " ".join(cast(str, command))) if running_script_info != None: - command = running_script_info.get(b"command") + command = cast(dict, running_script_info).get(b"command") assert running_function == command # command line format is: # fcall|fcall_ro * * - assert running_function[1] == running_script_info.get(b"name") + assert running_function[1] == cast(dict, running_script_info).get(b"name") expected = { b"LUA": {b"libraries_count": lib_count, b"functions_count": function_count} From 9c3d57bc9570240e0dac1b31a368a96b1e12a37a Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 16:16:40 -0700 Subject: [PATCH 12/15] Fix transaction tests --- python/python/tests/test_transaction.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 65380641c9..5780a00c7a 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -160,17 +160,15 @@ async def transaction_test( args.append(OK) transaction.function_stats() args.append( - [ - { - b"running_script": None, - b"engines": { - b"LUA": { - b"libraries_count": 1, - b"functions_count": 1, - } - }, - } - ] + { + b"running_script": None, + b"engines": { + b"LUA": { + b"libraries_count": 0, + b"functions_count": 0, + } + }, + } ) transaction.dbsize() From bcf8db7b4e2d198193b177781f6e629d961fc15c Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 16:42:41 -0700 Subject: [PATCH 13/15] Address PR comments --- CHANGELOG.md | 1 + python/python/glide/async_commands/standalone_commands.py | 6 +++--- python/python/tests/utils/utils.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae99d7c34..832c85a6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -201,6 +201,7 @@ * Node: Added LINDEX command ([#999](https://github.com/aws/glide-for-redis/pull/999)) * Python, Node: Added ZPOPMAX command ([#996](https://github.com/aws/glide-for-redis/pull/996), [#1009](https://github.com/aws/glide-for-redis/pull/1009)) * Python: Added DBSIZE command ([#1040](https://github.com/aws/glide-for-redis/pull/1040)) +* Python: Added FUNCTION STATS command ([#1794](https://github.com/aws/glide-for-redis/pull/1794)) #### Features * Python, Node: Added support in Lua Scripts ([#775](https://github.com/aws/glide-for-redis/pull/775), [#860](https://github.com/aws/glide-for-redis/pull/860)) diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index ff25983808..843d29fa5e 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -377,9 +377,9 @@ async def function_stats(self) -> TFunctionStatsResponse: Returns: TFunctionStatsResponse: A `Mapping` with two keys: - - `running_script` with information about the running script. - - `engines` with information about available engines and their stats. - See example for more details. + - `running_script` with information about the running script. + - `engines` with information about available engines and their stats. + See example for more details. Examples: >>> await client.function_stats() diff --git a/python/python/tests/utils/utils.py b/python/python/tests/utils/utils.py index c6af8035b9..5719bf1988 100644 --- a/python/python/tests/utils/utils.py +++ b/python/python/tests/utils/utils.py @@ -293,14 +293,14 @@ def check_function_stats_response( function_count (int): Expected functions count. """ running_script_info = response.get(b"running_script") - if running_script_info == None and len(running_function) != 0: + if running_script_info is None and len(running_function) != 0: pytest.fail("No running function info") - if running_script_info != None and len(running_function) == 0: + if running_script_info is not None and len(running_function) == 0: command = cast(dict, running_script_info).get(b"command") pytest.fail("Unexpected running function info: " + " ".join(cast(str, command))) - if running_script_info != None: + if running_script_info is not None: command = cast(dict, running_script_info).get(b"command") assert running_function == command # command line format is: From 8774ecf95861180a89bfefabee1eca677ffd1291 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 3 Jul 2024 17:06:11 -0700 Subject: [PATCH 14/15] Fix black lint --- python/python/glide/async_commands/cluster_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index d9d9eb95c0..8d6d447014 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -595,7 +595,7 @@ async def function_stats( """ return cast( TClusterResponse[TFunctionStatsResponse], - await self._execute_command(RequestType.FunctionStats, [], route) + await self._execute_command(RequestType.FunctionStats, [], route), ) async def function_dump( From 135395ec707bdbda37590e8c405c499a8481bf50 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Thu, 4 Jul 2024 10:18:50 -0700 Subject: [PATCH 15/15] Address minor PR comments --- .../glide/async_commands/cluster_commands.py | 2 +- .../async_commands/standalone_commands.py | 2 +- .../glide/async_commands/transaction.py | 2 +- python/python/tests/test_async_client.py | 54 +++++++++---------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 8d6d447014..14218fc150 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -563,7 +563,7 @@ async def function_stats( Returns information about the function that's currently running and information about the available execution engines. - See https://redis.io/commands/function-stats/ for more details + See https://valkey.io/commands/function-stats/ for more details Args: route (Optional[Route]): Specifies the routing configuration for the command. The client diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index fd754ba5ae..1a786357e0 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -374,7 +374,7 @@ async def function_stats(self) -> TFunctionStatsResponse: Returns information about the function that's currently running and information about the available execution engines. - See https://redis.io/commands/function-stats/ for more details + See https://valkey.io/commands/function-stats/ for more details Returns: TFunctionStatsResponse: A `Mapping` with two keys: diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 096cb7f7ce..b949153629 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -2024,7 +2024,7 @@ def function_stats(self: TTransaction) -> TTransaction: Returns information about the function that's currently running and information about the available execution engines. - See https://redis.io/commands/function-stats/ for more details + See https://valkey.io/commands/function-stats/ for more details Command Response: TFunctionStatsResponse: A `Mapping` with two keys: diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 72c509172c..72c5a0a362 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -7954,20 +7954,20 @@ async def test_function_delete_with_routing( @pytest.mark.parametrize("cluster_mode", [False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_function_stats(self, redis_client: GlideClient): + async def test_function_stats(self, glide_client: GlideClient): min_version = "7.0.0" - if await check_if_server_version_lt(redis_client, min_version): + if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") lib_name = "functionStats" func_name = lib_name - assert await redis_client.function_flush(FlushMode.SYNC) == OK + assert await glide_client.function_flush(FlushMode.SYNC) == OK # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) - assert await redis_client.function_load(code, True) == lib_name.encode() + assert await glide_client.function_load(code, True) == lib_name.encode() - response = await redis_client.function_stats() + response = await glide_client.function_stats() check_function_stats_response(response, [], 1, 1) code = generate_lua_lib_code( @@ -7976,33 +7976,33 @@ async def test_function_stats(self, redis_client: GlideClient): False, ) assert ( - await redis_client.function_load(code, True) == (lib_name + "_2").encode() + await glide_client.function_load(code, True) == (lib_name + "_2").encode() ) - response = await redis_client.function_stats() + response = await glide_client.function_stats() check_function_stats_response(response, [], 2, 3) - assert await redis_client.function_flush(FlushMode.SYNC) == OK + assert await glide_client.function_flush(FlushMode.SYNC) == OK - response = await redis_client.function_stats() + response = await glide_client.function_stats() check_function_stats_response(response, [], 0, 0) @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_function_stats_cluster(self, redis_client: GlideClusterClient): + async def test_function_stats_cluster(self, glide_client: GlideClusterClient): min_version = "7.0.0" - if await check_if_server_version_lt(redis_client, min_version): + if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") lib_name = "functionStats_without_route" func_name = lib_name - assert await redis_client.function_flush(FlushMode.SYNC) == OK + assert await glide_client.function_flush(FlushMode.SYNC) == OK # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) - assert await redis_client.function_load(code, True) == lib_name.encode() + assert await glide_client.function_load(code, True) == lib_name.encode() - response = await redis_client.function_stats() + response = await glide_client.function_stats() for node_response in response.values(): check_function_stats_response( cast(TFunctionStatsResponse, node_response), [], 1, 1 @@ -8014,18 +8014,18 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): False, ) assert ( - await redis_client.function_load(code, True) == (lib_name + "_2").encode() + await glide_client.function_load(code, True) == (lib_name + "_2").encode() ) - response = await redis_client.function_stats() + response = await glide_client.function_stats() for node_response in response.values(): check_function_stats_response( cast(TFunctionStatsResponse, node_response), [], 2, 3 ) - assert await redis_client.function_flush(FlushMode.SYNC) == OK + assert await glide_client.function_flush(FlushMode.SYNC) == OK - response = await redis_client.function_stats() + response = await glide_client.function_stats() for node_response in response.values(): check_function_stats_response( cast(TFunctionStatsResponse, node_response), [], 0, 0 @@ -8035,10 +8035,10 @@ async def test_function_stats_cluster(self, redis_client: GlideClusterClient): @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @pytest.mark.parametrize("single_route", [True, False]) async def test_function_stats_with_routing( - self, redis_client: GlideClusterClient, single_route: bool + self, glide_client: GlideClusterClient, single_route: bool ): min_version = "7.0.0" - if await check_if_server_version_lt(redis_client, min_version): + if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Redis version required >= {min_version}") route = ( @@ -8048,13 +8048,13 @@ async def test_function_stats_with_routing( ) lib_name = "functionStats_with_route_" + str(single_route) func_name = lib_name - assert await redis_client.function_flush(FlushMode.SYNC, route) == OK + assert await glide_client.function_flush(FlushMode.SYNC, route) == OK # function $funcName returns first argument code = generate_lua_lib_code(lib_name, {func_name: "return args[1]"}, False) - assert await redis_client.function_load(code, True, route) == lib_name.encode() + assert await glide_client.function_load(code, True, route) == lib_name.encode() - response = await redis_client.function_stats(route) + response = await glide_client.function_stats(route) if single_route: check_function_stats_response( cast(TFunctionStatsResponse, response), [], 1, 1 @@ -8071,11 +8071,11 @@ async def test_function_stats_with_routing( False, ) assert ( - await redis_client.function_load(code, True, route) + await glide_client.function_load(code, True, route) == (lib_name + "_2").encode() ) - response = await redis_client.function_stats(route) + response = await glide_client.function_stats(route) if single_route: check_function_stats_response( cast(TFunctionStatsResponse, response), [], 2, 3 @@ -8086,9 +8086,9 @@ async def test_function_stats_with_routing( cast(TFunctionStatsResponse, node_response), [], 2, 3 ) - assert await redis_client.function_flush(FlushMode.SYNC, route) == OK + assert await glide_client.function_flush(FlushMode.SYNC, route) == OK - response = await redis_client.function_stats(route) + response = await glide_client.function_stats(route) if single_route: check_function_stats_response( cast(TFunctionStatsResponse, response), [], 0, 0