Skip to content

Commit d737c95

Browse files
shohamazonavifenesh
authored andcommitted
Python: adds JSON.ARRLEN command (#2403)
--------- Signed-off-by: Shoham Elias <shohame@amazon.com> Signed-off-by: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Signed-off-by: avifenesh <aviarchi1994@gmail.com>
1 parent f71052c commit d737c95

File tree

4 files changed

+95
-11
lines changed

4 files changed

+95
-11
lines changed

.github/workflows/python.yml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -283,23 +283,16 @@ jobs:
283283
ec2-instance-id: ${{ secrets.AWS_EC2_INSTANCE_ID }}
284284

285285
test-modules:
286-
needs: [start-self-hosted-runner, load-engine-matrix]
286+
needs: [start-self-hosted-runner, get-matrices]
287287
name: Running Module Tests
288288
runs-on: ${{ matrix.host.RUNNER }}
289289
timeout-minutes: 35
290290
strategy:
291291
fail-fast: false
292292
matrix:
293-
engine: ${{ fromJson(needs.load-engine-matrix.outputs.matrix) }}
294-
python:
295-
- "3.12"
296-
host:
297-
- {
298-
OS: "ubuntu",
299-
NAMED_OS: "linux",
300-
RUNNER: ["self-hosted", "Linux", "ARM64"],
301-
TARGET: "aarch64-unknown-linux-gnu",
302-
}
293+
engine: ${{ fromJson(needs.get-matrices.outputs.engine-matrix-output) }}
294+
python: ${{ fromJson(needs.get-matrices.outputs.version-matrix-output) }}
295+
host: ${{ fromJson(needs.get-matrices.outputs.host-matrix-output).include }}
303296

304297
steps:
305298
- name: Setup self-hosted runner access

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#### Changes
2+
* Python: Add JSON.ARRLEN command ([#2403](https://github.com/valkey-io/valkey-glide/pull/2403))
23

34
#### Breaking Changes
45

python/python/glide/async_commands/server_modules/json.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,60 @@ async def get(
139139
return cast(bytes, await client.custom_command(args))
140140

141141

142+
async def arrlen(
143+
client: TGlideClient,
144+
key: TEncodable,
145+
path: Optional[TEncodable] = None,
146+
) -> Optional[TJsonResponse[int]]:
147+
"""
148+
Retrieves the length of the array at the specified `path` within the JSON document stored at `key`.
149+
150+
Args:
151+
client (TGlideClient): The client to execute the command.
152+
key (TEncodable): The key of the JSON document.
153+
path (Optional[TEncodable]): The path within the JSON document. Defaults to None.
154+
155+
Returns:
156+
Optional[TJsonResponse[int]]:
157+
For JSONPath (`path` starts with `$`):
158+
Returns a list of integer replies for every possible path, indicating the length of the array,
159+
or None for JSON values matching the path that are not an array.
160+
If `path` doesn't exist, an empty array will be returned.
161+
For legacy path (`path` doesn't starts with `$`):
162+
Returns the length of the array at `path`.
163+
If multiple paths match, the length of the first array match is returned.
164+
If the JSON value at `path` is not a array or if `path` doesn't exist, an error is raised.
165+
If `key` doesn't exist, None is returned.
166+
167+
Examples:
168+
>>> from glide import json
169+
>>> await json.set(client, "doc", "$", '{"a": [1, 2, 3], "b": {"a": [1, 2], "c": {"a": 42}}}')
170+
b'OK' # JSON is successfully set for doc
171+
>>> await json.arrlen(client, "doc", "$")
172+
[None] # No array at the root path.
173+
>>> await json.arrlen(client, "doc", "$.a")
174+
[3] # Retrieves the length of the array at path $.a.
175+
>>> await json.arrlen(client, "doc", "$..a")
176+
[3, 2, None] # Retrieves lengths of arrays found at all levels of the path `..a`.
177+
>>> await json.arrlen(client, "doc", "..a")
178+
3 # Legacy path retrieves the first array match at path `..a`.
179+
>>> await json.arrlen(client, "non_existing_key", "$.a")
180+
None # Returns None because the key does not exist.
181+
182+
>>> await json.set(client, "doc", "$", '[1, 2, 3, 4]')
183+
b'OK' # JSON is successfully set for doc
184+
>>> await json.arrlen(client, "doc")
185+
4 # Retrieves lengths of arrays in root.
186+
"""
187+
args = ["JSON.ARRLEN", key]
188+
if path:
189+
args.append(path)
190+
return cast(
191+
Optional[TJsonResponse[int]],
192+
await client.custom_command(args),
193+
)
194+
195+
142196
async def delete(
143197
client: TGlideClient,
144198
key: TEncodable,

python/python/tests/tests_server_modules/test_json.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,39 @@ async def test_json_type(self, glide_client: TGlideClient):
276276
# Check for all types in the JSON document using legacy path
277277
result = await json.type(glide_client, key, "[*]")
278278
assert result == b"string" # Expecting only the first type (string for key1)
279+
280+
@pytest.mark.parametrize("cluster_mode", [True, False])
281+
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
282+
async def test_json_arrlen(self, glide_client: TGlideClient):
283+
key = get_random_string(5)
284+
285+
json_value = '{"a": [1, 2, 3], "b": {"a": [1, 2], "c": {"a": 42}}}'
286+
assert await json.set(glide_client, key, "$", json_value) == OK
287+
288+
assert await json.arrlen(glide_client, key, "$.a") == [3]
289+
290+
assert await json.arrlen(glide_client, key, "$..a") == [3, 2, None]
291+
292+
# Legacy path retrieves the first array match at ..a
293+
assert await json.arrlen(glide_client, key, "..a") == 3
294+
295+
# Value at path is not an array
296+
assert await json.arrlen(glide_client, key, "$") == [None]
297+
with pytest.raises(RequestError):
298+
assert await json.arrlen(glide_client, key, ".")
299+
300+
# Path doesn't exist
301+
assert await json.arrlen(glide_client, key, "$.non_existing_path") == []
302+
with pytest.raises(RequestError):
303+
assert await json.arrlen(glide_client, key, "non_existing_path")
304+
305+
# Non-existing key
306+
assert await json.arrlen(glide_client, "non_existing_key", "$.a") is None
307+
assert await json.arrlen(glide_client, "non_existing_key", ".a") is None
308+
309+
# No path
310+
with pytest.raises(RequestError):
311+
assert await json.arrlen(glide_client, key)
312+
313+
assert await json.set(glide_client, key, "$", "[1, 2, 3, 4]") == OK
314+
assert await json.arrlen(glide_client, key) == 4

0 commit comments

Comments
 (0)