Skip to content

Commit 62abdb8

Browse files
authored
Merge pull request #463 from terminusdb/collect
Add WOQL collect predicate
2 parents 91854bf + bf12b91 commit 62abdb8

11 files changed

Lines changed: 262 additions & 50 deletions

File tree

.github/workflows/python.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,3 @@ jobs:
7070
7171
- name: Deploy to pypi
7272
uses: pypa/gh-action-pypi-publish@release/v1
73-
with:
74-
user: __token__
75-
password: ${{ secrets.PYPI_API_TOKEN }}

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/TerminusDB?style=social)](https://www.reddit.com/r/TerminusDB/)
99
[![Twitter](https://img.shields.io/twitter/follow/terminusdb?color=skyblue&label=Follow%20on%20Twitter&logo=twitter&style=flat)](https://twitter.com/TerminusDB)
1010

11-
[![release version](https://img.shields.io/pypi/v/terminusdb-client.svg?logo=pypi)](https://pypi.python.org/pypi/terminusdb-client/)
12-
[![downloads](https://img.shields.io/pypi/dm/terminusdb-client.svg?logo=pypi)](https://pypi.python.org/pypi/terminusdb-client/)
11+
[![release version](https://img.shields.io/pypi/v/terminusdb.svg?logo=pypi)](https://pypi.python.org/pypi/terminusdb/)
12+
[![downloads](https://img.shields.io/pypi/dm/terminusdb.svg?logo=pypi)](https://pypi.python.org/pypi/terminusdb/)
1313

1414
[![build status](https://img.shields.io/github/workflow/status/terminusdb/terminusdb-client-python/Python%20package?logo=github)](https://github.com/terminusdb/terminusdb-client-python/actions)
1515
[![documentation](https://img.shields.io/github/deployments/terminusdb/terminusdb-client-python/github-pages?label=documentation&logo=github)](https://terminusdb.org/docs/python)
@@ -18,6 +18,10 @@
1818

1919
> Python client for TerminusDB and TerminusCMS.
2020
21+
> **Migrating from `terminusdb-client`?** This package was formerly known as
22+
> `terminusdb-client`. Simply install `terminusdb` instead — both `import terminusdb`
23+
> and `import terminusdb_client` continue to work, so no code changes are required.
24+
2125
[**TerminusDB**][terminusdb] is an [open-source][terminusdb-repo] graph database
2226
and document store. It allows you to link JSON documents in a powerful knowledge
2327
graph all through a simple document API, with full git-for-data version control.
@@ -28,26 +32,26 @@ graph all through a simple document API, with full git-for-data version control.
2832

2933
## Requirements
3034

31-
- [TerminusDB v11.1](https://github.com/terminusdb/terminusdb-server)
35+
- [TerminusDB v12](https://github.com/terminusdb/terminusdb-server)
3236
- [Python >=3.9](https://www.python.org/downloads)
3337

3438
## Release Notes and Previous Versions
3539

36-
TerminusDB Client v11.1 works with TerminusDB v11.1 and the [DFRNT cloud service](https://dfrnt.com). Please check the [Release Notes](RELEASE_NOTES.md) to find out what has changed.
40+
TerminusDB Client v12 works with TerminusDB v12 onwards and the [DFRNT cloud service](https://dfrnt.com). Please check the [Release Notes](RELEASE_NOTES.md) to find out what has changed.
3741

3842
## Installation
3943
- TerminusDB Client can be downloaded from PyPI using pip:
40-
`python -m pip install terminusdb-client`
44+
`python -m pip install terminusdb`
4145

4246
This only includes the core Python Client (Client) and WOQLQuery.
4347

4448
If you want to use woqlDataframe or the import and export CSV function in the Scaffolding CLI tool:
4549

46-
`python -m pip install terminusdb-client[dataframe]`
50+
`python -m pip install terminusdb[dataframe]`
4751

4852
*if you are installing from `zsh` you have to quote the argument like this:*
4953

50-
`python -m pip install 'terminusdb-client[dataframe]'`
54+
`python -m pip install 'terminusdb[dataframe]'`
5155

5256
- Install from source:
5357

@@ -66,19 +70,21 @@ If you want to use woqlDataframe or the import and export CSV function in the Sc
6670
Connect to local host
6771

6872
```Python
69-
from terminusdb_client import Client
73+
from terminusdb import Client
7074

7175
client = Client("http://127.0.0.1:6363/")
7276
client.connect()
7377
```
7478

79+
The previous import path `from terminusdb_client import Client` also continues to work.
80+
7581
Connect to TerminusDB in the cloud
7682

7783
*check the documentation on the DFRNT support page about how to add your [API token](https://support.dfrnt.com/portal/en/kb/articles/api) to the environment variable*
7884

7985

8086
```Python
81-
from terminusdb_client import Client
87+
from terminusdb import Client
8288

8389
team="MyTeam"
8490
client = Client(f"https://studio.dfrnt.com/api/hosted/{team}/")
@@ -94,7 +100,7 @@ client.create_database("MyDatabase")
94100
#### Create a schema
95101

96102
```Python
97-
from terminusdb_client.schema import Schema, DocumentTemplate, RandomKey
103+
from terminusdb.schema import Schema, DocumentTemplate, RandomKey
98104

99105
my_schema = Schema()
100106

pyproject.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
[tool.poetry]
2-
name = "terminusdb-client"
2+
name = "terminusdb"
33
version = "12.0.3"
4-
description = "Python client for Terminus DB"
5-
authors = ["TerminusDB group"]
4+
description = "Terminus DB Python client"
5+
authors = ["TerminusDB group", "DFRNT AB"]
66
license = "Apache Software License"
77
readme = "README.md"
8+
packages = [
9+
{include = "terminusdb_client"},
10+
{include = "terminusdb"},
11+
]
812

913
[tool.poetry.dependencies]
1014
python = ">=3.9.0,<3.13"

terminusdb/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Re-export everything from terminusdb_client for the new import path.
2+
# Both `import terminusdb` and `import terminusdb_client` are supported.
3+
from terminusdb_client import * # noqa
4+
from terminusdb_client import Client, WOQLClient, WOQLQuery, Var, Vars, Patch, GraphType, WOQLDataFrame, WOQLSchema # noqa

terminusdb/client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from terminusdb_client.client import * # noqa
2+
from terminusdb_client.client import Client, GraphType, Patch # noqa
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from terminusdb_client.query_syntax import * # noqa

terminusdb/schema/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from terminusdb_client.schema import * # noqa

terminusdb_client/scripts/scripts.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,6 @@ def importcsv(
486486
embedded = [x.lower().replace(" ", "_") for x in embedded]
487487
try:
488488
pd = import_module("pandas")
489-
np = import_module("numpy")
490489
except ImportError:
491490
raise ImportError(
492491
"Library 'pandas' is required to import csv, either install 'pandas' or install woqlDataframe requirements as follows: python -m pip install -U terminus-client-python[dataframe]"
@@ -500,6 +499,48 @@ def importcsv(
500499
# "not schema" make it always False if adding the schema option
501500
has_schema = not schema and class_name in client.get_existing_classes()
502501

502+
def _df_to_schema(class_name, df):
503+
class_dict = {"@type": "Class", "@id": class_name}
504+
for col, dtype in dict(df.dtypes).items():
505+
if embedded and col in embedded:
506+
converted_type = class_name
507+
else:
508+
# Map pandas/numpy dtype to Python type
509+
# Uses dtype.kind for compatibility with numpy 2.0+ and pandas 3.0+
510+
dtype_kind = getattr(dtype, "kind", "O")
511+
if dtype.type is str or dtype_kind in ("U", "O", "S", "T"):
512+
converted_type = str
513+
elif dtype_kind in ("i", "u"):
514+
converted_type = int
515+
elif dtype_kind == "f":
516+
converted_type = float
517+
elif dtype_kind == "b":
518+
converted_type = bool
519+
elif dtype_kind == "M":
520+
converted_type = dt.datetime
521+
elif dtype_kind == "m":
522+
converted_type = dt.timedelta
523+
else:
524+
converted_type = str
525+
converted_type = wt.to_woql_type(converted_type)
526+
527+
if id_ and col == id_:
528+
class_dict[col] = converted_type
529+
elif na == "optional" and col not in keys:
530+
class_dict[col] = {"@type": "Optional", "@class": converted_type}
531+
else:
532+
class_dict[col] = converted_type
533+
# if id_ is not None:
534+
# pass # don't need key if id is specified
535+
# elif keys:
536+
# class_dict["@key"] = {"@type": "Random"}
537+
# elif na == "optional":
538+
# # have to use random key cause keys will be optional
539+
# class_dict["@key"] = {"@type": "Random"}
540+
# else:
541+
# class_dict["@key"] = {"@type": "Random"}
542+
return class_dict
543+
503544
with pd.read_csv(csv_file, sep=sep, chunksize=chunksize, dtype=dtype) as reader:
504545
for df in tqdm(reader):
505546
if any(df.isna().any()) and na == "error":
@@ -512,15 +553,7 @@ def importcsv(
512553
converted_col = col.lower().replace(" ", "_").replace(".", "_")
513554
df.rename(columns={col: converted_col}, inplace=True)
514555
if not has_schema:
515-
class_dict = _df_to_schema(
516-
class_name,
517-
df,
518-
np,
519-
embedded=embedded,
520-
id_col=id_,
521-
na_mode=na,
522-
keys=keys,
523-
)
556+
class_dict = _df_to_schema(class_name, df)
524557
if message is None:
525558
schema_msg = f"Schema object insert/ update with {csv_file} by Python client."
526559
else:

terminusdb_client/tests/integration_tests/test_conftest.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,18 @@ class TestServerDetection:
1414
"""Test server detection helper functions"""
1515

1616
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
17-
def test_local_server_running_200(self, mock_get):
18-
"""Test local server detection returns True for HTTP 200"""
19-
mock_response = Mock()
20-
mock_response.status_code = 200
21-
mock_get.return_value = mock_response
17+
def test_local_server_running_any_response(self, mock_get):
18+
"""Test local server detection returns True for any HTTP response"""
19+
mock_get.return_value = Mock()
2220

2321
assert is_local_server_running() is True
2422
mock_get.assert_called_once_with("http://127.0.0.1:6363/api/", timeout=2)
2523

2624
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
27-
def test_local_server_running_404(self, mock_get):
28-
"""Test local server detection returns True for HTTP 404"""
25+
def test_local_server_running_401(self, mock_get):
26+
"""Test local server detection returns True for HTTP 401 (unauthorized)"""
2927
mock_response = Mock()
30-
mock_response.status_code = 404
28+
mock_response.status_code = 401
3129
mock_get.return_value = mock_response
3230

3331
assert is_local_server_running() is True
@@ -47,20 +45,18 @@ def test_local_server_not_running_timeout(self, mock_get):
4745
assert is_local_server_running() is False
4846

4947
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
50-
def test_docker_server_running_200(self, mock_get):
51-
"""Test Docker server detection returns True for HTTP 200"""
52-
mock_response = Mock()
53-
mock_response.status_code = 200
54-
mock_get.return_value = mock_response
48+
def test_docker_server_running_any_response(self, mock_get):
49+
"""Test Docker server detection returns True for any HTTP response"""
50+
mock_get.return_value = Mock()
5551

5652
assert is_docker_server_running() is True
5753
mock_get.assert_called_once_with("http://127.0.0.1:6366/api/", timeout=2)
5854

5955
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
60-
def test_docker_server_running_404(self, mock_get):
61-
"""Test Docker server detection returns True for HTTP 404"""
56+
def test_docker_server_running_401(self, mock_get):
57+
"""Test Docker server detection returns True for HTTP 401 (unauthorized)"""
6258
mock_response = Mock()
63-
mock_response.status_code = 404
59+
mock_response.status_code = 401
6460
mock_get.return_value = mock_response
6561

6662
assert is_docker_server_running() is True
@@ -73,20 +69,18 @@ def test_docker_server_not_running(self, mock_get):
7369
assert is_docker_server_running() is False
7470

7571
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
76-
def test_jwt_server_running_200(self, mock_get):
77-
"""Test JWT server detection returns True for HTTP 200"""
78-
mock_response = Mock()
79-
mock_response.status_code = 200
80-
mock_get.return_value = mock_response
72+
def test_jwt_server_running_any_response(self, mock_get):
73+
"""Test JWT server detection returns True for any HTTP response"""
74+
mock_get.return_value = Mock()
8175

8276
assert is_jwt_server_running() is True
8377
mock_get.assert_called_once_with("http://127.0.0.1:6367/api/", timeout=2)
8478

8579
@patch("terminusdb_client.tests.integration_tests.conftest.requests.get")
86-
def test_jwt_server_running_404(self, mock_get):
87-
"""Test JWT server detection returns True for HTTP 404"""
80+
def test_jwt_server_running_401(self, mock_get):
81+
"""Test JWT server detection returns True for HTTP 401 (unauthorized)"""
8882
mock_response = Mock()
89-
mock_response.status_code = 404
83+
mock_response.status_code = 401
9084
mock_get.return_value = mock_response
9185

9286
assert is_jwt_server_running() is True

0 commit comments

Comments
 (0)