From 52dd474029b9747d342b1ad39e2a0563591b5fe5 Mon Sep 17 00:00:00 2001 From: BoazBD <50696333+BoazBD@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:18:54 +0200 Subject: [PATCH] Python - Implement JSON.OBJLEN command functionality (#2495) --------- Signed-off-by: BoazBD Signed-off-by: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Co-authored-by: Shoham Elias <116083498+shohamazon@users.noreply.github.com> --- CHANGELOG.md | 1 + .../async_commands/server_modules/json.py | 52 +++++++++++++++++++ .../tests/tests_server_modules/test_json.py | 50 ++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1bec5d60b..18a23c1d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### Changes +* Python: Add JSON.OBJLEN command ([#2495](https://github.com/valkey-io/valkey-glide/pull/2495)) * Python: FT.EXPLAIN and FT.EXPLAINCLI commands added([#2508](https://github.com/valkey-io/valkey-glide/pull/2508)) * Python: Python FT.INFO command added([#2429](https://github.com/valkey-io/valkey-glide/pull/2494)) * Python: Add FT.SEARCH command([#2470](https://github.com/valkey-io/valkey-glide/pull/2470)) diff --git a/python/python/glide/async_commands/server_modules/json.py b/python/python/glide/async_commands/server_modules/json.py index 40e4e80c51..eb75b6bfb3 100644 --- a/python/python/glide/async_commands/server_modules/json.py +++ b/python/python/glide/async_commands/server_modules/json.py @@ -615,6 +615,58 @@ async def nummultby( return cast(Optional[bytes], await client.custom_command(args)) +async def objlen( + client: TGlideClient, + key: TEncodable, + path: Optional[TEncodable] = None, +) -> Optional[TJsonResponse[int]]: + """ + Retrieves the number of key-value pairs in the object stored at the specified `path` within the JSON document stored at `key`. + + Args: + client (TGlideClient): The client to execute the command. + key (TEncodable): The key of the JSON document. + path (Optional[TEncodable]): The path within the JSON document. Defaults to None. + + Returns: + Optional[TJsonResponse[int]]: + For JSONPath (`path` starts with `$`): + Returns a list of integer replies for every possible path, indicating the length of the object, + or None for JSON values matching the path that are not an object. + If `path` doesn't exist, an empty array will be returned. + For legacy path (`path` doesn't starts with `$`): + Returns the length of the object at `path`. + If multiple paths match, the length of the first object match is returned. + If the JSON value at `path` is not an object or if `path` doesn't exist, an error is raised. + If `key` doesn't exist, None is returned. + + + Examples: + >>> from glide import json + >>> await json.set(client, "doc", "$", '{"a": 1.0, "b": {"a": {"x": 1, "y": 2}, "b": 2.5, "c": true}}') + b'OK' # Indicates successful setting of the value at the root path '$' in the key `doc`. + >>> await json.objlen(client, "doc", "$") + [2] # Returns the number of key-value pairs at the root object, which has 2 keys: 'a' and 'b'. + >>> await json.objlen(client, "doc", ".") + 2 # Returns the number of key-value pairs for the object matching the path '.', which has 2 keys: 'a' and 'b'. + >>> await json.objlen(client, "doc", "$.b") + [3] # Returns the length of the object at path '$.b', which has 3 keys: 'a', 'b', and 'c'. + >>> await json.objlen(client, "doc", ".b") + 3 # Returns the length of the nested object at path '.b', which has 3 keys. + >>> await json.objlen(client, "doc", "$..a") + [None, 2] + >>> await json.objlen(client, "doc") + 2 # Returns the number of key-value pairs for the object matching the path '.', which has 2 keys: 'a' and 'b'. + """ + args = ["JSON.OBJLEN", key] + if path: + args.append(path) + return cast( + Optional[TJsonResponse[int]], + await client.custom_command(args), + ) + + async def objkeys( client: TGlideClient, key: TEncodable, diff --git a/python/python/tests/tests_server_modules/test_json.py b/python/python/tests/tests_server_modules/test_json.py index 5989cb9f2e..c1ad3dfbfc 100644 --- a/python/python/tests/tests_server_modules/test_json.py +++ b/python/python/tests/tests_server_modules/test_json.py @@ -357,6 +357,56 @@ async def test_json_type(self, glide_client: TGlideClient): result = await json.type(glide_client, key, "[*]") assert result == b"string" # Expecting only the first type (string for key1) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_json_objlen(self, glide_client: TGlideClient): + key = get_random_string(5) + + json_value = {"a": 1.0, "b": {"a": {"x": 1, "y": 2}, "b": 2.5, "c": True}} + + assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK + + len = await json.objlen(glide_client, key, "$") + assert len == [2] + + len = await json.objlen(glide_client, key, ".") + assert len == 2 + + len = await json.objlen(glide_client, key, "$..") + assert len == [2, 3, 2] + + len = await json.objlen(glide_client, key, "..") + assert len == 2 + + len = await json.objlen(glide_client, key, "$..b") + assert len == [3, None] + + len = await json.objlen(glide_client, key, "..b") + assert len == 3 + + len = await json.objlen(glide_client, key, "..a") + assert len == 2 + + len = await json.objlen(glide_client, key) + assert len == 2 + + # path doesn't exist + assert await json.objlen(glide_client, key, "$.non_existing_path") == [] + with pytest.raises(RequestError): + await json.objlen(glide_client, key, "non_existing_path") + + # Value at path isnt an object + assert await json.objlen(glide_client, key, "$.a") == [None] + with pytest.raises(RequestError): + await json.objlen(glide_client, key, ".a") + + # Non-existing key + assert await json.objlen(glide_client, "non_exiting_key", "$") == None + assert await json.objlen(glide_client, "non_exiting_key", ".") == None + + assert await json.set(glide_client, key, "$", '{"a": 1, "b": 2, "c":3, "d":4}') + assert await json.objlen(glide_client, key) == 4 + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_json_arrlen(self, glide_client: TGlideClient):