From 27679704fe350892275b272aabc91d38f54e30d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Sun, 14 Dec 2025 15:01:26 +0100 Subject: [PATCH 1/2] Ensure list operator produces canonical woql --- terminusdb_client/woqlquery/woql_query.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/terminusdb_client/woqlquery/woql_query.py b/terminusdb_client/woqlquery/woql_query.py index 588ed197..3f7cf61b 100644 --- a/terminusdb_client/woqlquery/woql_query.py +++ b/terminusdb_client/woqlquery/woql_query.py @@ -411,9 +411,10 @@ def _clean_object(self, user_obj, target=None): obj["node"] = user_obj elif type(user_obj) is list: elts = [] - for obj in user_obj: - elts.append(self._clean_object(obj)) - return elts + for item in user_obj: + elts.append(self._clean_object(item)) + obj["list"] = elts + return obj elif isinstance(user_obj, Var): return self._expand_value_variable(user_obj) elif isinstance(user_obj, Doc): From 52647d998a1ef907027491599a14e82b08523bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20H=C3=B6ij?= Date: Sun, 14 Dec 2025 19:52:26 +0100 Subject: [PATCH 2/2] Add comments and disable obsolete functionality --- .../tests/integration_tests/conftest.py | 31 ++++++++--- terminusdb_client/woqlquery/woql_query.py | 51 +++++++++++++++---- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/terminusdb_client/tests/integration_tests/conftest.py b/terminusdb_client/tests/integration_tests/conftest.py index c5e5d6e6..4a70bfed 100644 --- a/terminusdb_client/tests/integration_tests/conftest.py +++ b/terminusdb_client/tests/integration_tests/conftest.py @@ -13,7 +13,8 @@ def is_local_server_running(): """Check if local TerminusDB server is running at http://127.0.0.1:6363""" try: response = requests.get("http://127.0.0.1:6363", timeout=2) - # Server responds with 404 for root path, which means it's running + # Server responds with 200 (success) or 404 (not found but server is up) + # 401 (unauthorized) also indicates server is running but needs auth return response.status_code in [200, 404] except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): return False @@ -73,7 +74,9 @@ def docker_url_jwt(pytestconfig): # Check if JWT server is already running (port 6367) if is_jwt_server_running(): - print("\n✓ Using existing JWT Docker TerminusDB server at http://127.0.0.1:6367") + print( + "\n✓ Using existing JWT Docker TerminusDB server at http://127.0.0.1:6367" + ) yield ("http://127.0.0.1:6367", jwt_token) return # Don't clean up - server was already running @@ -128,7 +131,9 @@ def docker_url_jwt(pytestconfig): if seconds_waited > MAX_CONTAINER_STARTUP_TIME: clean_up_container() - raise RuntimeError(f"JWT Container was too slow to startup (waited {MAX_CONTAINER_STARTUP_TIME}s)") + raise RuntimeError( + f"JWT Container was too slow to startup (waited {MAX_CONTAINER_STARTUP_TIME}s)" + ) yield (test_url, jwt_token) clean_up_container() @@ -145,9 +150,15 @@ def docker_url(pytestconfig): """ # Check if local test server is already running (port 6363) if is_local_server_running(): - print("\n✓ Using existing local TerminusDB test server at http://127.0.0.1:6363") - print("⚠️ WARNING: Local server should be started with TERMINUSDB_AUTOLOGIN=true") - print(" Or use: TERMINUSDB_SERVER_AUTOLOGIN=true ./tests/terminusdb-test-server.sh restart") + print( + "\n✓ Using existing local TerminusDB test server at http://127.0.0.1:6363" + ) + print( + "⚠️ WARNING: Local server should be started with TERMINUSDB_AUTOLOGIN=true" + ) + print( + " Or use: TERMINUSDB_SERVER_AUTOLOGIN=true ./tests/terminusdb-test-server.sh restart" + ) yield "http://127.0.0.1:6363" return # Don't clean up - server was already running @@ -200,7 +211,9 @@ def docker_url(pytestconfig): response = requests.get(test_url) # Server responds with 404 for root path, which means it's running assert response.status_code in [200, 404] - print(f"✓ Docker container started successfully after {seconds_waited}s") + print( + f"✓ Docker container started successfully after {seconds_waited}s" + ) break except (requests.exceptions.ConnectionError, AssertionError): pass @@ -210,7 +223,9 @@ def docker_url(pytestconfig): if seconds_waited > MAX_CONTAINER_STARTUP_TIME: clean_up_container() - raise RuntimeError(f"Container was too slow to startup (waited {MAX_CONTAINER_STARTUP_TIME}s)") + raise RuntimeError( + f"Container was too slow to startup (waited {MAX_CONTAINER_STARTUP_TIME}s)" + ) yield test_url clean_up_container() diff --git a/terminusdb_client/woqlquery/woql_query.py b/terminusdb_client/woqlquery/woql_query.py index 3f7cf61b..03829760 100644 --- a/terminusdb_client/woqlquery/woql_query.py +++ b/terminusdb_client/woqlquery/woql_query.py @@ -413,8 +413,7 @@ def _clean_object(self, user_obj, target=None): elts = [] for item in user_obj: elts.append(self._clean_object(item)) - obj["list"] = elts - return obj + return elts elif isinstance(user_obj, Var): return self._expand_value_variable(user_obj) elif isinstance(user_obj, Doc): @@ -1511,10 +1510,14 @@ def woql_as(self, *args): def file(self, fpath, opts=None): """Provides details of a file source in a JSON format that includes a URL property + Note: CSV files can no longer be read from the filesystem. Only files submitted + as part of the request can be processed. Use remote() for URLs or submit files + via the API. + Parameters ---------- - fpath : dict - file data source in a JSON format + fpath : dict or str + file data source in a JSON format or file path opts : input options optional @@ -1524,7 +1527,7 @@ def file(self, fpath, opts=None): query object that can be chained and/or execute Example ------- - To load a local csv file: + To reference a file (must be submitted with request): >>> WOQLQuery().file("/app/local_files/my.csv") See Also -------- @@ -1538,8 +1541,10 @@ def file(self, fpath, opts=None): if self._cursor.get("@type"): self._wrap_cursor_with_and() self._cursor["@type"] = "QueryResource" - fpath["@type"] = "Source" - self._cursor["source"] = fpath + if isinstance(fpath, str): + self._cursor["source"] = {"@type": "Source", "file": fpath} + else: + self._cursor["source"] = fpath self._cursor["format"] = "csv" if opts: self._cursor["options"] = opts @@ -1601,21 +1606,45 @@ def remote(self, uri, opts=None): if self._cursor.get("@type"): self._wrap_cursor_with_and() self._cursor["@type"] = "QueryResource" - uri["@type"] = "Source" - self._cursor["source"] = uri + if isinstance(uri, dict): + uri["@type"] = "Source" + self._cursor["source"] = uri + else: + self._cursor["source"] = {"@type": "Source", "url": uri} self._cursor["format"] = "csv" if opts: self._cursor["options"] = opts return self def post(self, fpath, opts=None): + """Specifies a file to be posted as part of the request for processing. + + Note: CSV files can no longer be read from the filesystem. Only files submitted + as part of the request can be processed. This method should be used with files + that are uploaded via the API. + + Parameters + ---------- + fpath : str or dict + file path/identifier or dict with file details + opts : dict, optional + additional options for file processing + + Returns + ------- + WOQLQuery object + query object that can be chained and/or execute + """ if fpath and fpath == "args": return ["source", "format", "options"] if self._cursor.get("@type"): self._wrap_cursor_with_and() self._cursor["@type"] = "QueryResource" - fpath["@type"] = "Source" - self._cursor["source"] = fpath + if isinstance(fpath, dict): + fpath["@type"] = "Source" + self._cursor["source"] = fpath + else: + self._cursor["source"] = {"@type": "Source", "post": fpath} self._cursor["format"] = "csv" if opts: self._cursor["options"] = opts