turingdb 1.12__cp311-cp311-manylinux_2_35_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of turingdb might be problematic. Click here for more details.
- bin/turing-import +0 -0
- bin/turingdb +0 -0
- lib/cmake/tabulate/tabulateConfig.cmake +31 -0
- lib/cmake/tabulate/tabulateConfigVersion.cmake +43 -0
- lib/cmake/tabulate/tabulateTargets.cmake +107 -0
- lib/cmake/termcolor/termcolor-config.cmake +28 -0
- lib/cmake/termcolor/termcolor-targets.cmake +107 -0
- turingdb/__init__.py +92 -0
- turingdb/exceptions.py +3 -0
- turingdb/path.py +16 -0
- turingdb/protocol.py +7 -0
- turingdb/s3.py +137 -0
- turingdb/turingdb.py +242 -0
- turingdb/turingsh/__init__.py +3 -0
- turingdb/turingsh/command.py +24 -0
- turingdb/turingsh/greeter.py +42 -0
- turingdb/turingsh/turingsh.py +367 -0
- turingdb-1.12.dist-info/METADATA +19 -0
- turingdb-1.12.dist-info/RECORD +51 -0
- turingdb-1.12.dist-info/WHEEL +5 -0
- turingdb-1.12.dist-info/entry_points.txt +3 -0
- turingdb-1.12.dist-info/licenses/LICENSE +5 -0
- turingdb-1.12.dist-info/sboms/auditwheel.cdx.json +1 -0
- turingdb.libs/libbrotlicommon-abf86ae9.so.1.0.9 +0 -0
- turingdb.libs/libbrotlidec-db9dbda7.so.1.0.9 +0 -0
- turingdb.libs/libcom_err-f196091d.so.2.1 +0 -0
- turingdb.libs/libcrypto-ef58def3.so.3 +0 -0
- turingdb.libs/libcurl-ef05c8ea.so.4.7.0 +0 -0
- turingdb.libs/libffi-247da4d5.so.8.1.0 +0 -0
- turingdb.libs/libgmp-4dc20a90.so.10.4.1 +0 -0
- turingdb.libs/libgnutls-a1c10edc.so.30.31.0 +0 -0
- turingdb.libs/libgssapi_krb5-74c938dc.so.2.2 +0 -0
- turingdb.libs/libhogweed-47d56894.so.6.4 +0 -0
- turingdb.libs/libidn2-1420c60a.so.0.3.7 +0 -0
- turingdb.libs/libk5crypto-43d6a714.so.3.1 +0 -0
- turingdb.libs/libkeyutils-ad20d5fb.so.1.9 +0 -0
- turingdb.libs/libkrb5-7ccebba4.so.3.3 +0 -0
- turingdb.libs/libkrb5support-134342ea.so.0.1 +0 -0
- turingdb.libs/liblber-2-ff904533.5.so.0.1.14 +0 -0
- turingdb.libs/libldap-2-22fb2ed0.5.so.0.1.14 +0 -0
- turingdb.libs/libnettle-2d3bda6c.so.8.4 +0 -0
- turingdb.libs/libnghttp2-1cc16764.so.14.20.1 +0 -0
- turingdb.libs/libp11-kit-d2b01eaa.so.0.3.0 +0 -0
- turingdb.libs/libpsl-95ca960e.so.5.3.2 +0 -0
- turingdb.libs/librtmp-2401c4fc.so.1 +0 -0
- turingdb.libs/libsasl2-344870a9.so.2.0.25 +0 -0
- turingdb.libs/libssh-f4c6722f.so.4.8.7 +0 -0
- turingdb.libs/libssl-660a6abe.so.3 +0 -0
- turingdb.libs/libtasn1-5982aae4.so.6.6.2 +0 -0
- turingdb.libs/libunistring-9c28d595.so.2.2.0 +0 -0
- turingdb.libs/libzstd-5df4f4df.so.1.4.8 +0 -0
turingdb/turingdb.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Literal, Optional
|
|
3
|
+
|
|
4
|
+
from .exceptions import TuringDBException
|
|
5
|
+
from .s3 import S3Client
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TuringDB:
|
|
9
|
+
DEFAULT_HEADERS = {
|
|
10
|
+
"Accept": "application/json",
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
instance_id: str = "",
|
|
17
|
+
auth_token: str = "",
|
|
18
|
+
host: str = "https://engines.turingdb.ai/sdk",
|
|
19
|
+
timeout: Optional[int] = None,
|
|
20
|
+
):
|
|
21
|
+
import copy
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
|
|
25
|
+
self.host = host
|
|
26
|
+
self._client = httpx.Client(
|
|
27
|
+
auth=None,
|
|
28
|
+
verify=False,
|
|
29
|
+
timeout=timeout,
|
|
30
|
+
)
|
|
31
|
+
self._s3_client: Optional[S3Client] = None
|
|
32
|
+
self._query_exec_time: Optional[float] = None
|
|
33
|
+
self._total_exec_time: Optional[float] = None
|
|
34
|
+
self._t0: float = 0
|
|
35
|
+
self._t1: float = 0
|
|
36
|
+
self._timeout = timeout
|
|
37
|
+
|
|
38
|
+
self._params = {
|
|
39
|
+
"graph": "default",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
self._headers = copy.deepcopy(TuringDB.DEFAULT_HEADERS)
|
|
43
|
+
|
|
44
|
+
if instance_id != "":
|
|
45
|
+
self._headers["Turing-Instance-Id"] = instance_id
|
|
46
|
+
|
|
47
|
+
if auth_token != "":
|
|
48
|
+
self._headers["Authorization"] = f"Bearer {auth_token}"
|
|
49
|
+
|
|
50
|
+
def try_reach(self, timeout: int = 5):
|
|
51
|
+
self._client.timeout = timeout
|
|
52
|
+
self.list_available_graphs()
|
|
53
|
+
self._client.timeout = self._timeout
|
|
54
|
+
|
|
55
|
+
def warmup(self, timeout: int = 5):
|
|
56
|
+
self._client.timeout = timeout
|
|
57
|
+
self.query("LIST GRAPH")
|
|
58
|
+
self._client.timeout = self._timeout
|
|
59
|
+
|
|
60
|
+
def list_available_graphs(self) -> list[str]:
|
|
61
|
+
return self._send_request("list_avail_graphs")["data"]
|
|
62
|
+
|
|
63
|
+
def list_loaded_graphs(self) -> list[str]:
|
|
64
|
+
return self._send_request("list_loaded_graphs")["data"][0][0]
|
|
65
|
+
|
|
66
|
+
def is_graph_loaded(self) -> bool:
|
|
67
|
+
return self._send_request(
|
|
68
|
+
"is_graph_loaded", params={"graph": self.get_graph()}
|
|
69
|
+
)["data"]
|
|
70
|
+
|
|
71
|
+
def load_graph(self, graph_name: str, raise_if_loaded: bool = True):
|
|
72
|
+
try:
|
|
73
|
+
return self._send_request("load_graph", params={"graph": graph_name})
|
|
74
|
+
except TuringDBException as e:
|
|
75
|
+
if raise_if_loaded or e.__str__() != "GRAPH_ALREADY_EXISTS":
|
|
76
|
+
raise e
|
|
77
|
+
|
|
78
|
+
def create_graph(self, graph_name: str):
|
|
79
|
+
return self.query(f"create graph {graph_name}")
|
|
80
|
+
|
|
81
|
+
def query(self, query: str):
|
|
82
|
+
json = self._send_request("query", data=query, params=self._params)
|
|
83
|
+
|
|
84
|
+
if not isinstance(json, dict):
|
|
85
|
+
raise TuringDBException("Invalid response from the server")
|
|
86
|
+
|
|
87
|
+
return self._parse_chunks(json)
|
|
88
|
+
|
|
89
|
+
def set_commit(self, commit: str):
|
|
90
|
+
self._params["commit"] = commit
|
|
91
|
+
|
|
92
|
+
def set_change(self, change: int | str):
|
|
93
|
+
if isinstance(change, int):
|
|
94
|
+
change = f"{change:x}"
|
|
95
|
+
self._params["change"] = change
|
|
96
|
+
|
|
97
|
+
def checkout(self, change: int | Literal["main"] = "main", commit: str = "HEAD"):
|
|
98
|
+
if change == "main":
|
|
99
|
+
if "change" in self._params:
|
|
100
|
+
del self._params["change"]
|
|
101
|
+
else:
|
|
102
|
+
self.set_change(change)
|
|
103
|
+
|
|
104
|
+
if commit == "HEAD":
|
|
105
|
+
if "commit" in self._params:
|
|
106
|
+
del self._params["commit"]
|
|
107
|
+
else:
|
|
108
|
+
self.set_commit(commit)
|
|
109
|
+
|
|
110
|
+
def new_change(self) -> int:
|
|
111
|
+
if self._params.get("change") is not None:
|
|
112
|
+
raise TuringDBException("Cannot create a new change while working on one")
|
|
113
|
+
|
|
114
|
+
if self._params.get("commit") is not None:
|
|
115
|
+
raise TuringDBException("Cannot create a new change while working on a commit")
|
|
116
|
+
|
|
117
|
+
res = self.query("CHANGE NEW")
|
|
118
|
+
self._params["change"] = res.values["changeID"][0]
|
|
119
|
+
change_id = int(self._params["change"])
|
|
120
|
+
return change_id
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def set_graph(self, graph_name: str):
|
|
124
|
+
self._params["graph"] = graph_name
|
|
125
|
+
|
|
126
|
+
def get_graph(self) -> str:
|
|
127
|
+
return self._params["graph"]
|
|
128
|
+
|
|
129
|
+
def s3_connect(
|
|
130
|
+
self,
|
|
131
|
+
bucket_name: str,
|
|
132
|
+
access_key: Optional[str] = None,
|
|
133
|
+
secret_key: Optional[str] = None,
|
|
134
|
+
region: Optional[str] = None,
|
|
135
|
+
use_scratch: bool = True,
|
|
136
|
+
):
|
|
137
|
+
from .s3 import S3Client
|
|
138
|
+
|
|
139
|
+
self._s3_client = S3Client(
|
|
140
|
+
bucket_name, access_key, secret_key, region, use_scratch
|
|
141
|
+
)
|
|
142
|
+
self._s3_client.connect(self)
|
|
143
|
+
|
|
144
|
+
def transfer(self, src: str, dst: str):
|
|
145
|
+
if self._s3_client is None:
|
|
146
|
+
raise TuringDBException("S3 client is not connected")
|
|
147
|
+
|
|
148
|
+
self._s3_client.transfer(src, dst)
|
|
149
|
+
|
|
150
|
+
def _send_request(
|
|
151
|
+
self,
|
|
152
|
+
path: str,
|
|
153
|
+
data: Optional[dict | str] = None,
|
|
154
|
+
params: Optional[dict] = None,
|
|
155
|
+
):
|
|
156
|
+
import orjson
|
|
157
|
+
|
|
158
|
+
self._query_exec_time = None
|
|
159
|
+
self._total_exec_time = None
|
|
160
|
+
self._t0 = time.time()
|
|
161
|
+
|
|
162
|
+
if data is None:
|
|
163
|
+
data = ""
|
|
164
|
+
|
|
165
|
+
url = f"{self.host}/{path}"
|
|
166
|
+
|
|
167
|
+
if isinstance(data, dict):
|
|
168
|
+
response = self._client.post(
|
|
169
|
+
url, json=data, params=params, headers=self._headers
|
|
170
|
+
)
|
|
171
|
+
else:
|
|
172
|
+
response = self._client.post(
|
|
173
|
+
url, content=data, params=params, headers=self._headers
|
|
174
|
+
)
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
|
|
177
|
+
json = orjson.loads(response.text)
|
|
178
|
+
|
|
179
|
+
if isinstance(json, dict):
|
|
180
|
+
err = json.get("error")
|
|
181
|
+
if err is not None:
|
|
182
|
+
details = json.get("error_details")
|
|
183
|
+
if details is not None:
|
|
184
|
+
err = f"{err}: {details}"
|
|
185
|
+
raise TuringDBException(err)
|
|
186
|
+
|
|
187
|
+
self._t1 = time.time()
|
|
188
|
+
self._total_exec_time = (self._t1 - self._t0) * 1000
|
|
189
|
+
|
|
190
|
+
return json
|
|
191
|
+
|
|
192
|
+
def _parse_chunks(self, json: dict):
|
|
193
|
+
import pandas as pd
|
|
194
|
+
|
|
195
|
+
self._query_exec_time = json["time"]
|
|
196
|
+
|
|
197
|
+
header = json["header"]
|
|
198
|
+
column_names = header["column_names"]
|
|
199
|
+
column_types = header["column_types"]
|
|
200
|
+
|
|
201
|
+
if len(column_names) != len(column_types):
|
|
202
|
+
raise Exception("Query response column names and types do not match")
|
|
203
|
+
|
|
204
|
+
dtype_map = {
|
|
205
|
+
"String": "string",
|
|
206
|
+
"Int64": "Int64",
|
|
207
|
+
"UInt64": "UInt64",
|
|
208
|
+
"Double": "float64",
|
|
209
|
+
"Bool": "boolean",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
df = pd.DataFrame()
|
|
213
|
+
|
|
214
|
+
for chunk in json["data"]:
|
|
215
|
+
df_chunk = pd.DataFrame({
|
|
216
|
+
cname: pd.Series(col, dtype=dtype_map.get(ctype, "object"))
|
|
217
|
+
for (cname, ctype), col in zip(zip(column_names, column_types), chunk)
|
|
218
|
+
})
|
|
219
|
+
df = pd.concat([df, df_chunk], ignore_index=True)
|
|
220
|
+
|
|
221
|
+
self._t1 = time.time()
|
|
222
|
+
self._total_exec_time = (self._t1 - self._t0) * 1000
|
|
223
|
+
|
|
224
|
+
return df
|
|
225
|
+
|
|
226
|
+
def get_query_exec_time(self) -> Optional[float]:
|
|
227
|
+
return self._query_exec_time
|
|
228
|
+
|
|
229
|
+
def get_total_exec_time(self) -> Optional[float]:
|
|
230
|
+
return self._total_exec_time
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def current_graph(self) -> str:
|
|
234
|
+
return self._params["graph"]
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def current_commit(self) -> str:
|
|
238
|
+
return self._params.get("commit") or "HEAD"
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def current_change(self) -> str:
|
|
242
|
+
return self._params.get("change") or "main"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
from click import ClickException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def shell_command(click_cmd):
|
|
7
|
+
"""
|
|
8
|
+
Decorator that wraps a Click command to be callable from a shell
|
|
9
|
+
with exception handling instead of sys.exit().
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@wraps(click_cmd)
|
|
13
|
+
def wrapper(client, *args, **kwargs):
|
|
14
|
+
try:
|
|
15
|
+
return click_cmd.main(
|
|
16
|
+
args=list(args),
|
|
17
|
+
prog_name=click_cmd.name,
|
|
18
|
+
obj=client,
|
|
19
|
+
standalone_mode=False,
|
|
20
|
+
)
|
|
21
|
+
except ClickException as e:
|
|
22
|
+
raise RuntimeError(e.format_message()) from e
|
|
23
|
+
|
|
24
|
+
return wrapper
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from prompt_toolkit.formatted_text import HTML
|
|
4
|
+
from prompt_toolkit.shortcuts import print_formatted_text
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_visible_width(text):
|
|
8
|
+
"""Remove HTML-like tags and get actual visible character count"""
|
|
9
|
+
return len(re.sub(r"<[^>]*>", "", text)) - 2
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def center_with_html(text, total_width):
|
|
13
|
+
"""Center text accounting for HTML markup"""
|
|
14
|
+
visible_width = get_visible_width(text)
|
|
15
|
+
padding = (total_width - visible_width) // 2
|
|
16
|
+
return " " * padding + text + " " * (total_width - visible_width - padding)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def greet():
|
|
20
|
+
|
|
21
|
+
topbar = (
|
|
22
|
+
"<skyblue>╔═════════════════════════════════════════════════════╗</skyblue>\n"
|
|
23
|
+
)
|
|
24
|
+
botbar = (
|
|
25
|
+
"<skyblue>╚═════════════════════════════════════════════════════╝</skyblue>"
|
|
26
|
+
)
|
|
27
|
+
delimiter = "<skyblue>║</skyblue>"
|
|
28
|
+
title = "<b>TuringDB Interactive Shell</b>"
|
|
29
|
+
help_msg = "Type <i>help</i> for help"
|
|
30
|
+
quit_msg = "Type <i>quit</i> or press <i>Ctrl+D</i> to quit"
|
|
31
|
+
|
|
32
|
+
box_content_width = 51
|
|
33
|
+
|
|
34
|
+
print_formatted_text(
|
|
35
|
+
HTML(
|
|
36
|
+
f"{topbar}"
|
|
37
|
+
f"{delimiter}{center_with_html(title, box_content_width)}{delimiter}\n"
|
|
38
|
+
f"{delimiter}{center_with_html(help_msg, box_content_width)}{delimiter}\n"
|
|
39
|
+
f"{delimiter}{center_with_html(quit_msg, box_content_width)}{delimiter}\n"
|
|
40
|
+
f"{botbar}"
|
|
41
|
+
)
|
|
42
|
+
)
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TuringDB Shell Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import traceback
|
|
6
|
+
from html import escape
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from prompt_toolkit import prompt
|
|
10
|
+
from prompt_toolkit.completion import WordCompleter
|
|
11
|
+
from prompt_toolkit.formatted_text import HTML
|
|
12
|
+
from prompt_toolkit.history import InMemoryHistory
|
|
13
|
+
from prompt_toolkit.shortcuts import print_formatted_text
|
|
14
|
+
|
|
15
|
+
from turingdb import TuringDB, TuringDBException
|
|
16
|
+
from turingdb.turingsh.command import shell_command
|
|
17
|
+
|
|
18
|
+
from . import greeter
|
|
19
|
+
|
|
20
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ShellException(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_completer():
|
|
28
|
+
"""Create command completer"""
|
|
29
|
+
commands = [
|
|
30
|
+
# Shell keywords
|
|
31
|
+
"cd",
|
|
32
|
+
"LIST_AVAIL_GRAPHS",
|
|
33
|
+
"LIST_LOADED_GRAPHS",
|
|
34
|
+
"help",
|
|
35
|
+
"quit",
|
|
36
|
+
"checkout",
|
|
37
|
+
# Query keywords
|
|
38
|
+
"DESCENDING",
|
|
39
|
+
"CONSTRAINT",
|
|
40
|
+
"MANDATORY",
|
|
41
|
+
"ASCENDING",
|
|
42
|
+
"OPTIONAL",
|
|
43
|
+
"CONTAINS",
|
|
44
|
+
"DISTINCT",
|
|
45
|
+
"EXTRACT",
|
|
46
|
+
"REQUIRE",
|
|
47
|
+
"COLLECT",
|
|
48
|
+
"STARTS",
|
|
49
|
+
"UNIQUE",
|
|
50
|
+
"FILTER",
|
|
51
|
+
"SINGLE",
|
|
52
|
+
"SCALAR",
|
|
53
|
+
"UNWIND",
|
|
54
|
+
"REMOVE",
|
|
55
|
+
"RETURN",
|
|
56
|
+
"CREATE",
|
|
57
|
+
"DELETE",
|
|
58
|
+
"DETACH",
|
|
59
|
+
"EXISTS",
|
|
60
|
+
"IS NOT",
|
|
61
|
+
"LIMIT",
|
|
62
|
+
"YIELD",
|
|
63
|
+
"MATCH",
|
|
64
|
+
"MERGE",
|
|
65
|
+
"ORDER",
|
|
66
|
+
"WHERE",
|
|
67
|
+
"UNION",
|
|
68
|
+
"FALSE",
|
|
69
|
+
"COUNT",
|
|
70
|
+
"DESC",
|
|
71
|
+
"CALL",
|
|
72
|
+
"NULL",
|
|
73
|
+
"TRUE",
|
|
74
|
+
"WHEN",
|
|
75
|
+
"NONE",
|
|
76
|
+
"THEN",
|
|
77
|
+
"ELSE",
|
|
78
|
+
"CASE",
|
|
79
|
+
"ENDS",
|
|
80
|
+
"DROP",
|
|
81
|
+
"SKIP",
|
|
82
|
+
"WITH",
|
|
83
|
+
"ANY",
|
|
84
|
+
"SET",
|
|
85
|
+
"ALL",
|
|
86
|
+
"ASC",
|
|
87
|
+
"NOT",
|
|
88
|
+
"END",
|
|
89
|
+
"XOR",
|
|
90
|
+
"FOR",
|
|
91
|
+
"ADD",
|
|
92
|
+
"AND",
|
|
93
|
+
"OR",
|
|
94
|
+
"IN",
|
|
95
|
+
"IS",
|
|
96
|
+
"BY",
|
|
97
|
+
"DO",
|
|
98
|
+
"OF",
|
|
99
|
+
"ON",
|
|
100
|
+
"IF",
|
|
101
|
+
"AS",
|
|
102
|
+
]
|
|
103
|
+
return WordCompleter(commands, ignore_case=True)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
|
107
|
+
@click.option(
|
|
108
|
+
"--host",
|
|
109
|
+
"-l",
|
|
110
|
+
default="https://engines.turingdb.ai/sdk",
|
|
111
|
+
help="Change the address host",
|
|
112
|
+
)
|
|
113
|
+
@click.option("--local", "-L", is_flag=True, default=False, help="Use a local instance")
|
|
114
|
+
@click.option("--auth-token", "-p", default="", help="Authentication token")
|
|
115
|
+
@click.option("--instance-id", "-i", default="", help="Instance ID")
|
|
116
|
+
@click.pass_context
|
|
117
|
+
def main(ctx, host, local, auth_token, instance_id):
|
|
118
|
+
"""TuringDB Shell - Interactive database client"""
|
|
119
|
+
if host == "https://engines.turingdb.ai/sdk":
|
|
120
|
+
# Host was not specified, use default
|
|
121
|
+
if local:
|
|
122
|
+
host = "http://localhost:6666"
|
|
123
|
+
else:
|
|
124
|
+
if auth_token == "" or instance_id == "":
|
|
125
|
+
print_formatted_text(
|
|
126
|
+
HTML("<red>✘ Missing authentication information</red>")
|
|
127
|
+
)
|
|
128
|
+
print_formatted_text(
|
|
129
|
+
HTML("<red>✘ Provide --auth-token and --instance-id</red>")
|
|
130
|
+
)
|
|
131
|
+
print()
|
|
132
|
+
exit(1)
|
|
133
|
+
|
|
134
|
+
client = TuringDB(
|
|
135
|
+
host=host,
|
|
136
|
+
auth_token=auth_token,
|
|
137
|
+
instance_id=instance_id,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
start_shell(client)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def start_shell(client: TuringDB):
|
|
144
|
+
"""Start the interactive shell"""
|
|
145
|
+
history = InMemoryHistory()
|
|
146
|
+
completer = create_completer()
|
|
147
|
+
|
|
148
|
+
# Welcome message
|
|
149
|
+
greeter.greet()
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
client.try_reach()
|
|
153
|
+
client.warmup()
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print_formatted_text(HTML(f"<red>✘ Could not connect to TuringDB.\n{e}</red>"))
|
|
156
|
+
print()
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
print_formatted_text(HTML("<green>✓ Connected to TuringDB</green>"))
|
|
160
|
+
print()
|
|
161
|
+
|
|
162
|
+
shell_commands = {
|
|
163
|
+
"cd": change_graph,
|
|
164
|
+
"list_avail_graphs": list_available_graphs,
|
|
165
|
+
"list_loaded_graphs": list_loaded_graphs,
|
|
166
|
+
"checkout": checkout,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
while True:
|
|
170
|
+
graph = client.get_graph()
|
|
171
|
+
checkedout = ""
|
|
172
|
+
|
|
173
|
+
if client.current_commit != "HEAD":
|
|
174
|
+
checkedout = f"@{client.current_commit}"
|
|
175
|
+
elif client.current_change != "main":
|
|
176
|
+
checkedout = f"@change-{client.current_change}"
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
# Get user input with styling
|
|
180
|
+
user_input = prompt(
|
|
181
|
+
HTML(
|
|
182
|
+
f"<b><cyan>turingdb:{graph}{checkedout}</cyan></b><gray>></gray> "
|
|
183
|
+
),
|
|
184
|
+
completer=completer,
|
|
185
|
+
history=history,
|
|
186
|
+
multiline=False,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Handle commands
|
|
190
|
+
cmd = user_input.strip()
|
|
191
|
+
|
|
192
|
+
if not cmd:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
cmd_lower = cmd.lower()
|
|
196
|
+
cmd_words = cmd_lower.split()
|
|
197
|
+
|
|
198
|
+
if len(cmd_words) == 0:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
if len(cmd_words) == 1:
|
|
202
|
+
match cmd_lower:
|
|
203
|
+
case "quit":
|
|
204
|
+
print_formatted_text(HTML("<yellow>Goodbye! 👋</yellow>"))
|
|
205
|
+
break
|
|
206
|
+
case "help":
|
|
207
|
+
show_help()
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
if cmd_words[0] in shell_commands:
|
|
211
|
+
try:
|
|
212
|
+
shell_commands[cmd_words[0]](client, *cmd_words[1:])
|
|
213
|
+
except ShellException as e:
|
|
214
|
+
print_formatted_text(HTML(f"<red>✘ {e}</red>"))
|
|
215
|
+
except Exception as e:
|
|
216
|
+
content = f"{e}\n{traceback.format_exc()}"
|
|
217
|
+
print_formatted_text(
|
|
218
|
+
HTML(f"<red>✘ Fatal error: {escape(str(content))}</red>")
|
|
219
|
+
)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
# Execute query
|
|
223
|
+
try:
|
|
224
|
+
result = client.query(cmd)
|
|
225
|
+
print_formatted_text(HTML(f"<white>{escape(str(result))}</white>"))
|
|
226
|
+
except TuringDBException as e:
|
|
227
|
+
content = f"{e}"
|
|
228
|
+
print_formatted_text(HTML(f"<red>✘ {escape(str(content))}</red>"))
|
|
229
|
+
if "CHANGE_NOT_FOUND" in str(e):
|
|
230
|
+
print_formatted_text(
|
|
231
|
+
HTML("<yellow>⚠ Checking out to the latest commit...</yellow>")
|
|
232
|
+
)
|
|
233
|
+
checkout(client)
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
content = f"{e}\n{traceback.format_exc()}"
|
|
237
|
+
print_formatted_text(
|
|
238
|
+
HTML(f"<red>✘ Fatal error: {escape(str(content))}</red>")
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
exec_time = client.get_total_exec_time()
|
|
242
|
+
if exec_time is not None:
|
|
243
|
+
print_formatted_text(
|
|
244
|
+
HTML(
|
|
245
|
+
f"<white>Total execution time: <yellow>{exec_time:.3f}</yellow> milliseconds</white>"
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
query_time = client.get_query_exec_time()
|
|
249
|
+
if query_time is not None:
|
|
250
|
+
print_formatted_text(
|
|
251
|
+
HTML(
|
|
252
|
+
f"<white>Including query execution time: <yellow>{query_time:.3f}</yellow> milliseconds</white>"
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
print() # Add spacing
|
|
257
|
+
|
|
258
|
+
except KeyboardInterrupt:
|
|
259
|
+
print_formatted_text(
|
|
260
|
+
HTML('\n<yellow>Use "quit" or Ctrl+D to quit</yellow>')
|
|
261
|
+
)
|
|
262
|
+
continue
|
|
263
|
+
except EOFError:
|
|
264
|
+
print_formatted_text(HTML("\n<yellow>Goodbye! 👋</yellow>"))
|
|
265
|
+
break
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def show_help():
|
|
269
|
+
"""Display help information"""
|
|
270
|
+
help_text = """<b>Available Commands:</b>
|
|
271
|
+
<cyan>cd my_graph</cyan> - Change active graph
|
|
272
|
+
<cyan>checkout --change change-0</cyan> - Checkout to a specific change
|
|
273
|
+
<cyan>checkout --commit a2daef1f</cyan> - Checkout to a specific commit
|
|
274
|
+
<cyan>checkout</cyan> - Checkout to the latest commit
|
|
275
|
+
<cyan>LIST_AVAIL_GRAPHS</cyan> - List available graphs
|
|
276
|
+
<cyan>LIST_LOADED_GRAPHS</cyan> - List loaded graphs
|
|
277
|
+
<cyan>help</cyan> - Show this help
|
|
278
|
+
<cyan>EXIT</cyan> or <cyan>\\q</cyan> - Quit shell
|
|
279
|
+
|
|
280
|
+
<b>Example queries:</b>
|
|
281
|
+
<cyan>LOAD GRAPH my_graph</cyan> - Load graph
|
|
282
|
+
<cyan>MATCH (n) RETURN n.name</cyan> - Return all node names
|
|
283
|
+
|
|
284
|
+
<b>Tips:</b>
|
|
285
|
+
• Use <i>Tab</i> for command completion
|
|
286
|
+
• Use <i>↑/↓</i> arrows for command history"""
|
|
287
|
+
|
|
288
|
+
print_formatted_text(HTML(help_text))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@shell_command
|
|
292
|
+
@click.command()
|
|
293
|
+
@click.option("--commit", default="HEAD", help="Commit hash or 'HEAD'")
|
|
294
|
+
@click.option("--change", help="ID of the change")
|
|
295
|
+
@click.pass_obj
|
|
296
|
+
def checkout(client, commit: str, change: int):
|
|
297
|
+
"""Checkout a commit or a change"""
|
|
298
|
+
try:
|
|
299
|
+
client.checkout(change=change or "main", commit=commit)
|
|
300
|
+
client.query("CALL db.history()")
|
|
301
|
+
except TuringDBException as e:
|
|
302
|
+
content = f"{e}"
|
|
303
|
+
print_formatted_text(HTML(f"<red>✘ {escape(str(content))}</red>"))
|
|
304
|
+
client.checkout()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@shell_command
|
|
308
|
+
@click.command()
|
|
309
|
+
@click.argument("graph-name", default="default")
|
|
310
|
+
@click.pass_obj
|
|
311
|
+
def change_graph(client: TuringDB, graph_name: str):
|
|
312
|
+
"""Change the current graph"""
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
client.set_graph(graph_name)
|
|
316
|
+
if not client.is_graph_loaded():
|
|
317
|
+
print_formatted_text(
|
|
318
|
+
HTML(
|
|
319
|
+
f"<red>✘ Graph {graph_name} needs to be loaded with 'load graph \"{graph_name}\"'</red>"
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
client.set_graph("default")
|
|
323
|
+
else:
|
|
324
|
+
print_formatted_text(
|
|
325
|
+
HTML(f"<green>✓ Changed graph to {graph_name}</green>")
|
|
326
|
+
)
|
|
327
|
+
except TuringDBException as e:
|
|
328
|
+
print_formatted_text(HTML(f"<red>✘ {e}</red>"))
|
|
329
|
+
except Exception as e:
|
|
330
|
+
content = f"{e}\n{traceback.format_exc()}"
|
|
331
|
+
print_formatted_text(HTML(f"<red>✘ Fatal error: {escape(str(content))}</red>"))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@shell_command
|
|
335
|
+
@click.command()
|
|
336
|
+
@click.pass_obj
|
|
337
|
+
def list_available_graphs(client: TuringDB):
|
|
338
|
+
"""List available graphs"""
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
graphs = client.list_available_graphs()
|
|
342
|
+
print_formatted_text(HTML(f"<white>{graphs}</white>"))
|
|
343
|
+
except TuringDBException as e:
|
|
344
|
+
content = f"{e}"
|
|
345
|
+
print_formatted_text(HTML(f"<red>✘ {escape(str(content))}</red>"))
|
|
346
|
+
except Exception as e:
|
|
347
|
+
content = f"{e}\n{traceback.format_exc()}"
|
|
348
|
+
print_formatted_text(HTML(f"<red>✘ Fatal error: {escape(str(content))}</red>"))
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@shell_command
|
|
352
|
+
@click.command()
|
|
353
|
+
@click.pass_obj
|
|
354
|
+
def list_loaded_graphs(client: TuringDB):
|
|
355
|
+
"""List loaded graphs"""
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
graphs = client.list_loaded_graphs()
|
|
359
|
+
print_formatted_text(HTML(f"<white>{graphs}</white>"))
|
|
360
|
+
except TuringDBException as e:
|
|
361
|
+
print_formatted_text(HTML(f"<red>✘ {e}</red>"))
|
|
362
|
+
except Exception as e:
|
|
363
|
+
print_formatted_text(HTML(f"<red>✘ Fatal error: {e}</red>"))
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
if __name__ == "__main__":
|
|
367
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: turingdb
|
|
3
|
+
Version: 1.12
|
|
4
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
5
|
+
Project-URL: Homepage, https://github.com/turing-db/turingdb-python
|
|
6
|
+
Project-URL: Repository, https://github.com/turing-db/turingdb-python
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: auditwheel>=6.6.0
|
|
9
|
+
Requires-Dist: boto3>=1.42.22
|
|
10
|
+
Requires-Dist: boto3-stubs>=1.42.22
|
|
11
|
+
Requires-Dist: click>=8.3.1
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: mypy>=1.19.1
|
|
14
|
+
Requires-Dist: orjson>=3.11.5
|
|
15
|
+
Requires-Dist: pandas>=2.3.3
|
|
16
|
+
Requires-Dist: pandas-stubs>=2.3.3.251219
|
|
17
|
+
Requires-Dist: patchelf>=0.17.2.4
|
|
18
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
|
19
|
+
|