Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ Thanks for interested to contribute to TerminusDB Client, to get started, fork t

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

`pip3 install pipenv --upgrade`
```
python3 -m venv venv
source venv/bin/activate
pip3 install pipenv --upgrade
```

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

Expand Down
75 changes: 75 additions & 0 deletions terminusdb_client/tests/test_slice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Unit tests for WOQL slice operator

Tests the Python client binding for slice(input_list, result, start, end=None)
"""

from terminusdb_client.woqlquery.woql_query import WOQLQuery

from .woqljson.woqlSliceJson import WOQL_SLICE_JSON


class TestWOQLSlice:
"""Test cases for the slice operator"""

def test_basic_slice(self):
"""Basic slicing - slice([a,b,c,d], result, 1, 3) returns [b,c]"""
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 3)
assert woql_object.to_dict() == WOQL_SLICE_JSON["basicSlice"]

def test_negative_indices(self):
"""Negative indices - slice([a,b,c,d], result, -2, -1) returns [c]"""
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", -2, -1)
assert woql_object.to_dict() == WOQL_SLICE_JSON["negativeIndices"]

def test_without_end(self):
"""Optional end - slice([a,b,c,d], result, 1) returns [b,c,d]"""
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1)
assert woql_object.to_dict() == WOQL_SLICE_JSON["withoutEnd"]

def test_variable_list(self):
"""Variable list input - slice(v:MyList, result, 0, 2)"""
woql_object = WOQLQuery().slice("v:MyList", "v:Result", 0, 2)
assert woql_object.to_dict() == WOQL_SLICE_JSON["variableList"]

def test_variable_indices(self):
"""Variable indices - slice([x,y,z], result, v:Start, v:End)"""
woql_object = WOQLQuery().slice(["x", "y", "z"], "v:Result", "v:Start", "v:End")
assert woql_object.to_dict() == WOQL_SLICE_JSON["variableIndices"]

def test_empty_list(self):
"""Empty list - slice([], result, 0, 1) returns []"""
woql_object = WOQLQuery().slice([], "v:Result", 0, 1)
assert woql_object.to_dict() == WOQL_SLICE_JSON["emptyList"]

def test_slice_type(self):
"""Verify the @type is correctly set to 'Slice'"""
woql_object = WOQLQuery().slice(["a", "b"], "v:Result", 0, 1)
assert woql_object.to_dict()["@type"] == "Slice"

def test_chaining_with_and(self):
"""Test that slice works with method chaining via woql_and"""
woql_object = WOQLQuery().woql_and(
WOQLQuery().eq("v:MyList", ["a", "b", "c"]),
WOQLQuery().slice("v:MyList", "v:Result", 1, 3),
)
result = woql_object.to_dict()
assert result["@type"] == "And"
assert len(result["and"]) == 2
assert result["and"][1]["@type"] == "Slice"

def test_single_element_slice(self):
"""Single element - slice([a,b,c,d], result, 1, 2) returns [b]"""
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 2)
result = woql_object.to_dict()
assert result["@type"] == "Slice"
assert result["start"]["data"]["@value"] == 1
assert result["end"]["data"]["@value"] == 2

def test_full_range(self):
"""Full range - slice([a,b,c,d], result, 0, 4) returns [a,b,c,d]"""
woql_object = WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 0, 4)
result = woql_object.to_dict()
assert result["@type"] == "Slice"
assert result["start"]["data"]["@value"] == 0
assert result["end"]["data"]["@value"] == 4
77 changes: 77 additions & 0 deletions terminusdb_client/tests/woqljson/woqlSliceJson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Expected JSON output for WOQL slice operator tests"""

WOQL_SLICE_JSON = {
"basicSlice": {
"@type": "Slice",
"list": {
"@type": "DataValue",
"list": [
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
],
},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 3}},
},
"negativeIndices": {
"@type": "Slice",
"list": {
"@type": "DataValue",
"list": [
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
],
},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": -2}},
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": -1}},
},
"withoutEnd": {
"@type": "Slice",
"list": {
"@type": "DataValue",
"list": [
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "a"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "b"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "c"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "d"}},
],
},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
# Note: no 'end' property when end is omitted
},
"variableList": {
"@type": "Slice",
"list": {"@type": "DataValue", "variable": "MyList"},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 0}},
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 2}},
},
"variableIndices": {
"@type": "Slice",
"list": {
"@type": "DataValue",
"list": [
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "x"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "y"}},
{"@type": "DataValue", "data": {"@type": "xsd:string", "@value": "z"}},
],
},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "variable": "Start"},
"end": {"@type": "DataValue", "variable": "End"},
},
"emptyList": {
"@type": "Slice",
"list": {"@type": "DataValue", "list": []},
"result": {"@type": "DataValue", "variable": "Result"},
"start": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 0}},
"end": {"@type": "DataValue", "data": {"@type": "xsd:integer", "@value": 1}},
},
}
48 changes: 48 additions & 0 deletions terminusdb_client/woqlquery/woql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,54 @@ def sum(self, user_input, output):
self._cursor["sum"] = self._clean_data_value(output)
return self

def slice(self, input_list, result, start, end=None):
"""
Extracts a contiguous subsequence from a list, following slice() semantics.

Parameters
----------
input_list : list or str
A list of values or a variable representing a list
result : str
A variable that stores the sliced result
start : int or str
The start index (0-based, supports negative indices)
end : int or str, optional
The end index (exclusive). If omitted, takes the rest of the list

Returns
-------
WOQLQuery object
query object that can be chained and/or execute

Examples
--------
>>> WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", 1, 3) # ["b", "c"]
>>> WOQLQuery().slice(["a", "b", "c", "d"], "v:Result", -2) # ["c", "d"]
"""
if input_list and input_list == "args":
return ["list", "result", "start", "end"]
if self._cursor.get("@type"):
self._wrap_cursor_with_and()
self._cursor["@type"] = "Slice"
self._cursor["list"] = self._data_list(input_list)
self._cursor["result"] = self._clean_data_value(result)
if isinstance(start, int):
self._cursor["start"] = self._clean_data_value(
{"@type": "xsd:integer", "@value": start}
)
else:
self._cursor["start"] = self._clean_data_value(start)
# end is optional
if end is not None:
if isinstance(end, int):
self._cursor["end"] = self._clean_data_value(
{"@type": "xsd:integer", "@value": end}
)
else:
self._cursor["end"] = self._clean_data_value(end)
return self

def start(self, start, query=None):
"""Specifies that the start of the query returned

Expand Down