Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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