Skip to content

Commit

Permalink
Python: add JSON.TYPE command (valkey-io#2409)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Muhammad Awawdi <Mawawdi@amazon.com>
Signed-off-by: Muhammad-awawdi-amazon <mawawdi@amazon.com>
Co-authored-by: Shoham Elias <116083498+shohamazon@users.noreply.github.com>
  • Loading branch information
Muhammad-awawdi-amazon and shohamazon authored Oct 9, 2024
1 parent 862dd18 commit 667d93a
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
42 changes: 42 additions & 0 deletions python/python/glide/async_commands/server_modules/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,45 @@ async def toggle(
TJsonResponse[bool],
await client.custom_command(["JSON.TOGGLE", key, path]),
)


async def type(
client: TGlideClient,
key: TEncodable,
path: Optional[TEncodable] = None,
) -> Optional[Union[bytes, List[bytes]]]:
"""
Retrieves the type of the JSON value 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]): Represents the path within the JSON document where the type will be retrieved.
Defaults to None.
Returns:
Optional[Union[bytes, List[bytes]]]:
For JSONPath ('path' starts with '$'):
Returns a list of byte string replies for every possible path, indicating the type of the JSON value.
If `path` doesn't exist, an empty array will be returned.
For legacy path (`path` doesn't starts with `$`):
Returns the type of the JSON value at `path`.
If multiple paths match, the type of the first JSON value match is returned.
If `path` doesn't exist, None will be returned.
If `key` doesn't exist, None is returned.
Examples:
>>> from glide import json
>>> await json.set(client, "doc", "$", '{"a": 1, "nested": {"a": 2, "b": 3}}')
>>> await json.type(client, "doc", "$.nested")
[b'object'] # Indicates the type of the value at path '$.nested' in the key stored at `doc`.
>>> await json.type(client, "doc", "$.nested.a")
[b'integer'] # Indicates the type of the value at path '$.nested.a' in the key stored at `doc`.
>>> await json.type(client, "doc", "$[*]")
[b'integer', b'object'] # Array of types in all top level elements.
"""
args = ["JSON.TYPE", key]
if path:
args.append(path)

return cast(Optional[Union[bytes, List[bytes]]], await client.custom_command(args))
80 changes: 80 additions & 0 deletions python/python/tests/tests_server_modules/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,83 @@ async def test_json_toggle(self, glide_client: TGlideClient):

with pytest.raises(RequestError):
assert await json.toggle(glide_client, "non_exiting_key", "$")

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_json_type(self, glide_client: TGlideClient):
key = get_random_string(10)

json_value = {
"key1": "value1",
"key2": 2,
"key3": [1, 2, 3],
"key4": {"nested_key": {"key1": [4, 5]}},
"key5": None,
"key6": True,
}
assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK

result = await json.type(glide_client, key, "$")
assert result == [b"object"]

result = await json.type(glide_client, key, "$..key1")
assert result == [b"string", b"array"]

result = await json.type(glide_client, key, "$.key2")
assert result == [b"integer"]

result = await json.type(glide_client, key, "$.key3")
assert result == [b"array"]

result = await json.type(glide_client, key, "$.key4")
assert result == [b"object"]

result = await json.type(glide_client, key, "$.key4.nested_key")
assert result == [b"object"]

result = await json.type(glide_client, key, "$.key5")
assert result == [b"null"]

result = await json.type(glide_client, key, "$.key6")
assert result == [b"boolean"]

# Check for non-existent path in enhanced mode $.key7
result = await json.type(glide_client, key, "$.key7")
assert result == []

# Check for non-existent path within an existing key (array bound)
result = await json.type(
glide_client, key, "$.key3[3]"
) # Out of bounds for the array
assert result == []

# Legacy path (without $) - will return None for non-existing path
result = await json.type(glide_client, key, "key7")
assert result is None # Legacy path returns None for non-existent key

# Check for multiple path match in legacy
result = await json.type(glide_client, key, "..key1")
assert result == b"string"

# Check for non-existent key with enhanced path
result = await json.type(glide_client, "non_existent_key", "$.key1")
assert result is None

# Check for non-existent key with legacy path
result = await json.type(glide_client, "non_existent_key", "key1")
assert result is None # Returns None for legacy path when the key doesn't exist

# Check for all types in the JSON document using JSON Path
result = await json.type(glide_client, key, "$[*]")
assert result == [
b"string",
b"integer",
b"array",
b"object",
b"null",
b"boolean",
]

# Check for all types in the JSON document using legacy path
result = await json.type(glide_client, key, "[*]")
assert result == b"string" # Expecting only the first type (string for key1)

0 comments on commit 667d93a

Please sign in to comment.