Skip to content

Commit 1eee61b

Browse files
committed
Add slice operator support in python
1 parent 68c5092 commit 1eee61b

File tree

4 files changed

+206
-1
lines changed

4 files changed

+206
-1
lines changed

CONTRIBUTING.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ Thanks for interested to contribute to TerminusDB Client, to get started, fork t
66

77
Make sure you have Python>=3.9 installed. We use [pipenv](https://pipenv-fork.readthedocs.io/en/latest/) for dev environment, to install pipenv:
88

9-
`pip3 install pipenv --upgrade`
9+
```
10+
python3 -m venv venv
11+
source venv/bin/activate
12+
pip3 install pipenv --upgrade
13+
```
1014

1115
[Fork and clone](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) this repo, then in your local repo:
1216

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Unit tests for WOQL slice operator
3+
4+
Tests the Python client binding for slice(input_list, result, start, end=None)
5+
"""
6+
7+
import pytest
8+
from terminusdb_client.woqlquery.woql_query import WOQLQuery
9+
10+
from .woqljson.woqlSliceJson import WOQL_SLICE_JSON
11+
12+
13+
class TestWOQLSlice:
14+
"""Test cases for the slice operator"""
15+
16+
def test_basic_slice(self):
17+
"""AC-1: Basic slicing - slice([a,b,c,d], result, 1, 3) returns [b,c]"""
18+
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 3)
19+
assert woql_object.to_dict() == WOQL_SLICE_JSON["basicSlice"]
20+
21+
def test_negative_indices(self):
22+
"""AC-3: Negative indices - slice([a,b,c,d], result, -2, -1) returns [c]"""
23+
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", -2, -1)
24+
assert woql_object.to_dict() == WOQL_SLICE_JSON["negativeIndices"]
25+
26+
def test_without_end(self):
27+
"""Optional end - slice([a,b,c,d], result, 1) returns [b,c,d]"""
28+
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1)
29+
assert woql_object.to_dict() == WOQL_SLICE_JSON["withoutEnd"]
30+
31+
def test_variable_list(self):
32+
"""Variable list input - slice(v:MyList, result, 0, 2)"""
33+
woql_object = WOQLQuery().slice("v:MyList", "v:Result", 0, 2)
34+
assert woql_object.to_dict() == WOQL_SLICE_JSON["variableList"]
35+
36+
def test_variable_indices(self):
37+
"""Variable indices - slice([x,y,z], result, v:Start, v:End)"""
38+
woql_object = WOQLQuery().slice(["x", "y", "z"], "v:Result", "v:Start", "v:End")
39+
assert woql_object.to_dict() == WOQL_SLICE_JSON["variableIndices"]
40+
41+
def test_empty_list(self):
42+
"""AC-6: Empty list - slice([], result, 0, 1) returns []"""
43+
woql_object = WOQLQuery().slice([], "v:Result", 0, 1)
44+
assert woql_object.to_dict() == WOQL_SLICE_JSON["emptyList"]
45+
46+
def test_slice_type(self):
47+
"""Verify the @type is correctly set to 'Slice'"""
48+
woql_object = WOQLQuery().slice(["a", "b"], "v:Result", 0, 1)
49+
assert woql_object.to_dict()["@type"] == "Slice"
50+
51+
def test_chaining_with_and(self):
52+
"""Test that slice works with method chaining via woql_and"""
53+
woql_object = WOQLQuery().woql_and(
54+
WOQLQuery().eq("v:MyList", ["a", "b", "c"]),
55+
WOQLQuery().slice("v:MyList", "v:Result", 1, 3),
56+
)
57+
result = woql_object.to_dict()
58+
assert result["@type"] == "And"
59+
assert len(result["and"]) == 2
60+
assert result["and"][1]["@type"] == "Slice"
61+
62+
def test_single_element_slice(self):
63+
"""AC-2: Single element - slice([a,b,c,d], result, 1, 2) returns [b]"""
64+
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 2)
65+
result = woql_object.to_dict()
66+
assert result["@type"] == "Slice"
67+
assert result["start"]["data"]["@value"] == 1
68+
assert result["end"]["data"]["@value"] == 2
69+
70+
def test_full_range(self):
71+
"""AC-7: Full range - slice([a,b,c,d], result, 0, 4) returns [a,b,c,d]"""
72+
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 0, 4)
73+
result = woql_object.to_dict()
74+
assert result["@type"] == "Slice"
75+
assert result["start"]["data"]["@value"] == 0
76+
assert result["end"]["data"]["@value"] == 4
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Expected JSON output for WOQL slice operator tests"""
2+
3+
WOQL_SLICE_JSON = {
4+
"basicSlice": {
5+
"@type": "Slice",
6+
"list": {
7+
"@type": "DataValue",
8+
"list": [
9+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
10+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
11+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
12+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
13+
],
14+
},
15+
"result": {"@type": "DataValue", "variable": "Result"},
16+
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
17+
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 3}},
18+
},
19+
"negativeIndices": {
20+
"@type": "Slice",
21+
"list": {
22+
"@type": "DataValue",
23+
"list": [
24+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
25+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
26+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
27+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
28+
],
29+
},
30+
"result": {"@type": "DataValue", "variable": "Result"},
31+
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": -2}},
32+
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": -1}},
33+
},
34+
"withoutEnd": {
35+
"@type": "Slice",
36+
"list": {
37+
"@type": "DataValue",
38+
"list": [
39+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
40+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
41+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
42+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
43+
],
44+
},
45+
"result": {"@type": "DataValue", "variable": "Result"},
46+
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
47+
# Note: no 'end' property when end is omitted
48+
},
49+
"variableList": {
50+
"@type": "Slice",
51+
"list": {"@type": "DataValue", "variable": "MyList"},
52+
"result": {"@type": "DataValue", "variable": "Result"},
53+
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 0}},
54+
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 2}},
55+
},
56+
"variableIndices": {
57+
"@type": "Slice",
58+
"list": {
59+
"@type": "DataValue",
60+
"list": [
61+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "x"}},
62+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "y"}},
63+
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "z"}},
64+
],
65+
},
66+
"result": {"@type": "DataValue", "variable": "Result"},
67+
"start": {"@type": "DataValue", "variable": "Start"},
68+
"end": {"@type": "DataValue", "variable": "End"},
69+
},
70+
"emptyList": {
71+
"@type": "Slice",
72+
"list": {"@type": "DataValue", "list": []},
73+
"result": {"@type": "DataValue", "variable": "Result"},
74+
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 0}},
75+
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
76+
},
77+
}

terminusdb_client/woqlquery/woql_query.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,6 +2492,54 @@ def sum(self, user_input, output):
24922492
self._cursor["sum"] = self._clean_data_value(output)
24932493
return self
24942494

2495+
def slice(self, input_list, result, start, end=None):
2496+
"""
2497+
Extracts a contiguous subsequence from a list, following JavaScript's slice() semantics.
2498+
2499+
Parameters
2500+
----------
2501+
input_list : list or str
2502+
A list of values or a variable representing a list
2503+
result : str
2504+
A variable that stores the sliced result
2505+
start : int or str
2506+
The start index (0-based, supports negative indices)
2507+
end : int or str, optional
2508+
The end index (exclusive). If omitted, takes the rest of the list
2509+
2510+
Returns
2511+
-------
2512+
WOQLQuery object
2513+
query object that can be chained and/or execute
2514+
2515+
Examples
2516+
--------
2517+
>>> WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 3) # ["b", "c"]
2518+
>>> WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", -2) # ["c", "d"]
2519+
"""
2520+
if input_list and input_list == "args":
2521+
return ["list", "result", "start", "end"]
2522+
if self._cursor.get("@type"):
2523+
self._wrap_cursor_with_and()
2524+
self._cursor["@type"] = "Slice"
2525+
self._cursor["list"] = self._data_list(input_list)
2526+
self._cursor["result"] = self._clean_data_value(result)
2527+
if isinstance(start, int):
2528+
self._cursor["start"] = self._clean_data_value(
2529+
{"@type": "xsd:integer", "@value": start}
2530+
)
2531+
else:
2532+
self._cursor["start"] = self._clean_data_value(start)
2533+
# end is optional
2534+
if end is not None:
2535+
if isinstance(end, int):
2536+
self._cursor["end"] = self._clean_data_value(
2537+
{"@type": "xsd:integer", "@value": end}
2538+
)
2539+
else:
2540+
self._cursor["end"] = self._clean_data_value(end)
2541+
return self
2542+
24952543
def start(self, start, query=None):
24962544
"""Specifies that the start of the query returned
24972545

0 commit comments

Comments
 (0)