From 37a8e9fe7e8e9d87a71eabccfc777636ef1b042e Mon Sep 17 00:00:00 2001 From: Muhammad Awawdi Date: Tue, 22 Oct 2024 11:42:43 +0000 Subject: [PATCH] Updated per Code Review comments Signed-off-by: Muhammad Awawdi --- .../async_commands/server_modules/json.py | 89 ++++-- .../tests/tests_server_modules/test_json.py | 260 +++++++++--------- 2 files changed, 203 insertions(+), 146 deletions(-) diff --git a/python/python/glide/async_commands/server_modules/json.py b/python/python/glide/async_commands/server_modules/json.py index ea9bdcc34a..07b5349e44 100644 --- a/python/python/glide/async_commands/server_modules/json.py +++ b/python/python/glide/async_commands/server_modules/json.py @@ -245,61 +245,108 @@ async def clear( return cast(int, await client.custom_command(args)) -async def debug( +async def debug_fields( client: TGlideClient, subcommand: str, key: TEncodable, path: Optional[TEncodable] = None, ) -> Optional[Union[int, List[int]]]: """ - Provides diagnostic information about a JSON document stored in Redis. - Supported subcommands are `MEMORY` and `FIELDS. - - Subcommands: - MEMORY - Reports memory usage in bytes of a JSON value. - FIELDS - Reports the number of fields at the specified document path. + Reports the number of fields of the JSON value at the specified `path` within the JSON document stored at `key`. Args: client (TGlideClient): The client to execute the command. - subcommand (str): The subcommand to execute. Must be one of `MEMORY` or `FIELDS`. + subcommand (str): The subcommand to execute. Must be `FIELDS`. key (TEncodable): The key of the JSON document. path (Optional[TEncodable]): The path within the JSON document. Defaults to root if not provided. Returns: Optional[Union[bytes, List[bytes]]]: For JSONPath (`path` starts with `$`): - Returns an array of integers. - for `MEMORY` command, it indicates the memory usage in bytes of a JSON value for each matched `path`. - for `FIELDS` command, it indicates the number of fields for each matched `path`. + Returns an array of integers. indicating the number of fields for each matched `path`. If `path` doesn't exist, an empty array will be returned. For legacy path (`path` doesn't start with `$`): - Returns an integer. - for `MEMORY` command, it indicates the memory usage in bytes of a JSON value for each matched `path`. - for `FIELDS` command, it indicates the number of fields for each matched `path`. - If multiple paths match, the memory usage/number of fields (depending on the executed command) of the first JSON value match is returned. + Returns an integer, indicating the number of fields for each matched `path`. + If multiple paths match, number of fields of the first JSON value match is returned. If `path` doesn't exist, an error is raised. + If `path` is not provided, it reports the total number of fields in the entire JSON document. If `key` doesn't exist, None is returned. Examples: >>> from glide import json >>> await json.set(client, "k1", "$", '[1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]]') 'OK' - >>> await json.debug(client, "MEMORY", "k1", "$[*]") - [16,16,19,16,16,16,16,66,64] >>> await json.debug(client, "FIELDS", "k1", "$[*]") [1,1,1,1,1,0,0,2,3] >>> await json.set(client, "k1", "$", '{"firstName":"John","lastName":"Smith","age":27,"weight":135.25,"isAlive":true,"address":{"street":"21 2nd Street","city":"New York","state":"NY","zipcode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"}],"children":[],"spouse":null}') 'OK' - >>> await json.debug(client, "MEMORY", "k1") - 472 - >>> await json.debug(client, "MEMORY", "k1", ".phoneNumbers") - 164 >>> await json.debug(client, "FIELDS", "k1") 19 >>> await json.debug(client, "FIELDS", "k1", ".address") 4 """ + valid_subcommand = {"FIELDS"} + subcommand_upper = subcommand.upper() + if subcommand_upper not in valid_subcommand: + raise ValueError( + f"Invalid subcommand '{subcommand}'. Must be {valid_subcommand}." + ) + + args = ["JSON.DEBUG", subcommand, key] + if path: + args.append(path) + + return cast(Optional[Union[int, List[int]]], await client.custom_command(args)) + + +async def debug_memory( + client: TGlideClient, + subcommand: str, + key: TEncodable, + path: Optional[TEncodable] = None, +) -> Optional[Union[int, List[int]]]: + """ + Reports memory usage in bytes of a JSON value. at the specified `path` within the JSON document stored at `key`. + + Args: + client (TGlideClient): The client to execute the command. + subcommand (str): The subcommand to execute. Must be `MEMORY`. + key (TEncodable): The key of the JSON document. + path (Optional[TEncodable]): The path within the JSON document. Defaults to root if not provided. + + Returns: + Optional[Union[bytes, List[bytes]]]: + For JSONPath (`path` starts with `$`): + Returns an array of integers. indicating the memory usage in bytes of a JSON value for each matched `path`. + If `path` doesn't exist, an empty array will be returned. + For legacy path (`path` doesn't start with `$`): + Returns an integer, indicating the number of fields for each matched `path`. + If multiple paths match, number of fields of the first JSON value match is returned. + If `path` doesn't exist, an error is raised. + If `path` is not provided, it reports the total memory usage in bytes in the entire JSON document. + If `key` doesn't exist, None is returned. + Examples: + >>> from glide import json + >>> await json.set(client, "k1", "$", '[1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]]') + 'OK' + >>> await json.debug(client, "MEMORY", "k1", "$[*]") + [16,16,19,16,16,16,16,66,64] + + >>> await json.set(client, "k1", "$", '{"firstName":"John","lastName":"Smith","age":27,"weight":135.25,"isAlive":true,"address":{"street":"21 2nd Street","city":"New York","state":"NY","zipcode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"}],"children":[],"spouse":null}') + 'OK' + >>> await json.debug(client, "MEMORY", "k1") + 472 + >>> await json.debug(client, "MEMORY", "k1", ".phoneNumbers") + 164 + """ + valid_subcommand = {"MEMORY"} + subcommand_upper = subcommand.upper() + if subcommand_upper not in valid_subcommand: + raise ValueError( + f"Invalid subcommand '{subcommand}'. Must be {valid_subcommand}." + ) + args = ["JSON.DEBUG", subcommand, key] if path: args.append(path) diff --git a/python/python/tests/tests_server_modules/test_json.py b/python/python/tests/tests_server_modules/test_json.py index aab9661a40..56f57562bd 100644 --- a/python/python/tests/tests_server_modules/test_json.py +++ b/python/python/tests/tests_server_modules/test_json.py @@ -627,7 +627,7 @@ async def test_json_nummultby(self, glide_client: TGlideClient): @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_json_debug(self, glide_client: TGlideClient): + async def test_json_debug_fields(self, glide_client: TGlideClient): key = get_random_string(10) json_value = { @@ -645,203 +645,213 @@ async def test_json_debug(self, glide_client: TGlideClient): assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK - # Test JSONPath - Memory Subcommand + # Test JSONPath - Fields Subcommand # Test integer - result = await json.debug(glide_client, "memory", key, "$.key1") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key1") + assert result == [1] # Test float - result = await json.debug(glide_client, "memory", key, "$.key2") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key2") + assert result == [1] # Test Nested Value - result = await json.debug( - glide_client, "memory", key, "$.key3.nested_key.key1[0]" + result = await json.debug_fields( + glide_client, "fields", key, "$.key3.nested_key.key1" ) - assert result == [16] + assert result == [2] # Test Array - result = await json.debug(glide_client, "memory", key, "$.key4[2]") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key4[2]") + assert result == [1] # Test String - result = await json.debug(glide_client, "memory", key, "$.key6") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key6") + assert result == [1] # Test Null - result = await json.debug(glide_client, "memory", key, "$.key7") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key7") + assert result == [1] # Test Bool - result = await json.debug(glide_client, "memory", key, "$.key10") - assert result == [16] + result = await json.debug_fields(glide_client, "fields", key, "$.key10") + assert result == [1] # Test all keys - result = await json.debug(glide_client, "memory", key, "$[*]") - assert result == [16, 16, 110, 64, 16, 16, 16, 101, 39, 16] + result = await json.debug_fields(glide_client, "fields", key, "$[*]") + assert result == [1, 1, 4, 3, 1, 1, 1, 2, 1, 1] # Test multiple paths - result = await json.debug(glide_client, "memory", key, "$..key1") - assert result == [16, 48, 39] + result = await json.debug_fields(glide_client, "fields", key, "$..key1") + assert result == [1, 2, 1] # Test for non-existent path - result = await json.debug(glide_client, "memory", key, "$.key11") + result = await json.debug_fields(glide_client, "fields", key, "$.key11") assert result == [] # Test for non-existent key - result = await json.debug(glide_client, "memory", "non_existent_key", "$.key10") + result = await json.debug_fields( + glide_client, "fields", "non_existent_key", "$.key10" + ) assert result == None # Test no provided path - # Total Memory (504 bytes) - visual breakdown: - # ├── Root Object Overhead (129 bytes) - # └── JSON Elements (374 bytes) - # ├── key1: 16 bytes - # ├── key2: 16 bytes - # ├── key3: 110 bytes - # ├── key4: 64 bytes - # ├── key5: 16 bytes - # ├── key6: 16 bytes - # ├── key7: 16 bytes - # ├── key8: 101 bytes - # └── key9: 39 bytes - result = await json.debug(glide_client, "memory", key) - assert result == 504 + # Total Fields (19) - breakdown: + # Top-Level Fields: 10 + # Fields within key3: 4 ($.key3, $.key3.nested_key, $.key3.nested_key.key1, $.key3.nested_key.key1) + # Fields within key4: 3 ($.key4[0], $.key4[1], $.key4[2]) + # Fields within key8: 2 ($.key8, $.key8.nested_key) + result = await json.debug_fields(glide_client, "fields", key) + assert result == 19 - # Test Legacy Path - Memory Subcommand + # Test legacy path - Fields Subcommand # Test integer - result = await json.debug(glide_client, "memory", key, ".key1") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key1") + assert result == 1 # Test float - result = await json.debug(glide_client, "memory", key, ".key2") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key2") + assert result == 1 # Test Nested Value - result = await json.debug( - glide_client, "memory", key, ".key3.nested_key.key1[0]" + result = await json.debug_fields( + glide_client, "fields", key, ".key3.nested_key.key1" ) - assert result == 16 + assert result == 2 # Test Array - result = await json.debug(glide_client, "memory", key, ".key4[2]") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key4[2]") + assert result == 1 # Test String - result = await json.debug(glide_client, "memory", key, ".key6") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key6") + assert result == 1 # Test Null - result = await json.debug(glide_client, "memory", key, ".key7") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key7") + assert result == 1 # Test Bool - result = await json.debug(glide_client, "memory", key, ".key10") - assert result == 16 + result = await json.debug_fields(glide_client, "fields", key, ".key10") + assert result == 1 # Test multiple paths - result = await json.debug(glide_client, "memory", key, "..key1") - assert result == 16 # Returns the memory usage of the first JSON value + result = await json.debug_fields(glide_client, "fields", key, "..key1") + assert result == 1 # Returns number of fields of the first JSON value # Test for non-existent path with pytest.raises(RequestError): - await json.debug(glide_client, "memory", key, ".key11") + await json.debug_fields(glide_client, "fields", key, ".key11") # Test for non-existent key - result = await json.debug(glide_client, "memory", "non_existent_key", ".key10") + result = await json.debug_fields( + glide_client, "fields", "non_existent_key", ".key10" + ) assert result == None - # Test JSONPath - Fields Subcommand - # Test integer - result = await json.debug(glide_client, "fields", key, "$.key1") - assert result == [1] + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_json_debug_memory(self, glide_client: TGlideClient): + key = get_random_string(10) - # Test float - result = await json.debug(glide_client, "fields", key, "$.key2") - assert result == [1] + json_value = { + "key1": 1, + "key2": 3.5, + "key3": {"nested_key": {"key1": [4, 5]}}, + "key4": [1, 2, 3], + "key5": 0, + "key6": "hello", + "key7": None, + "key8": {"nested_key": {"key1": 3.5953862697246314e307}}, + "key9": 3.5953862697246314e307, + "key10": True, + } + assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK + # Test JSONPath - Memory Subcommand + # Test integer + result = await json.debug_memory(glide_client, "memory", key, "$.key1") + assert result == [16] + # Test float + result = await json.debug_memory(glide_client, "memory", key, "$.key2") + assert result == [16] # Test Nested Value - result = await json.debug(glide_client, "fields", key, "$.key3.nested_key.key1") - assert result == [2] - + result = await json.debug_memory( + glide_client, "memory", key, "$.key3.nested_key.key1[0]" + ) + assert result == [16] # Test Array - result = await json.debug(glide_client, "fields", key, "$.key4[2]") - assert result == [1] - + result = await json.debug_memory(glide_client, "memory", key, "$.key4[2]") + assert result == [16] # Test String - result = await json.debug(glide_client, "fields", key, "$.key6") - assert result == [1] - + result = await json.debug_memory(glide_client, "memory", key, "$.key6") + assert result == [16] # Test Null - result = await json.debug(glide_client, "fields", key, "$.key7") - assert result == [1] - + result = await json.debug_memory(glide_client, "memory", key, "$.key7") + assert result == [16] # Test Bool - result = await json.debug(glide_client, "fields", key, "$.key10") - assert result == [1] - + result = await json.debug_memory(glide_client, "memory", key, "$.key10") + assert result == [16] # Test all keys - result = await json.debug(glide_client, "fields", key, "$[*]") - assert result == [1, 1, 4, 3, 1, 1, 1, 2, 1, 1] - + result = await json.debug_memory(glide_client, "memory", key, "$[*]") + assert result == [16, 16, 110, 64, 16, 16, 16, 101, 39, 16] # Test multiple paths - result = await json.debug(glide_client, "fields", key, "$..key1") - assert result == [1, 2, 1] - + result = await json.debug_memory(glide_client, "memory", key, "$..key1") + assert result == [16, 48, 39] # Test for non-existent path - result = await json.debug(glide_client, "fields", key, "$.key11") + result = await json.debug_memory(glide_client, "memory", key, "$.key11") assert result == [] - # Test for non-existent key - result = await json.debug(glide_client, "fields", "non_existent_key", "$.key10") + result = await json.debug_memory( + glide_client, "memory", "non_existent_key", "$.key10" + ) assert result == None - # Test no provided path - # Total Fields (19) - breakdown: - # Top-Level Fields: 10 - # Fields within key3: 4 ($.key3, $.key3.nested_key, $.key3.nested_key.key1, $.key3.nested_key.key1) - # Fields within key4: 3 ($.key4[0], $.key4[1], $.key4[2]) - # Fields within key8: 2 ($.key8, $.key8.nested_key) - result = await json.debug(glide_client, "fields", key) - assert result == 19 - - # Test legacy path - Fields Subcommand + # Total Memory (504 bytes) - visual breakdown: + # ├── Root Object Overhead (129 bytes) + # └── JSON Elements (374 bytes) + # ├── key1: 16 bytes + # ├── key2: 16 bytes + # ├── key3: 110 bytes + # ├── key4: 64 bytes + # ├── key5: 16 bytes + # ├── key6: 16 bytes + # ├── key7: 16 bytes + # ├── key8: 101 bytes + # └── key9: 39 bytes + result = await json.debug_memory(glide_client, "memory", key) + assert result == 504 + # Test Legacy Path - Memory Subcommand # Test integer - result = await json.debug(glide_client, "fields", key, ".key1") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key1") + assert result == 16 # Test float - result = await json.debug(glide_client, "fields", key, ".key2") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key2") + assert result == 16 # Test Nested Value - result = await json.debug(glide_client, "fields", key, ".key3.nested_key.key1") - assert result == 2 - + result = await json.debug_memory( + glide_client, "memory", key, ".key3.nested_key.key1[0]" + ) + assert result == 16 # Test Array - result = await json.debug(glide_client, "fields", key, ".key4[2]") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key4[2]") + assert result == 16 # Test String - result = await json.debug(glide_client, "fields", key, ".key6") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key6") + assert result == 16 # Test Null - result = await json.debug(glide_client, "fields", key, ".key7") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key7") + assert result == 16 # Test Bool - result = await json.debug(glide_client, "fields", key, ".key10") - assert result == 1 - + result = await json.debug_memory(glide_client, "memory", key, ".key10") + assert result == 16 # Test multiple paths - result = await json.debug(glide_client, "fields", key, "..key1") - assert result == 1 # Returns number of fields of the first JSON value - + result = await json.debug_memory(glide_client, "memory", key, "..key1") + assert result == 16 # Returns the memory usage of the first JSON value # Test for non-existent path with pytest.raises(RequestError): - await json.debug(glide_client, "fields", key, ".key11") - + await json.debug_memory(glide_client, "memory", key, ".key11") # Test for non-existent key - result = await json.debug(glide_client, "fields", "non_existent_key", ".key10") + result = await json.debug_memory( + glide_client, "memory", "non_existent_key", ".key10" + ) assert result == None