triggerware 0.1.2__tar.gz → 1.0.0__tar.gz
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.
- triggerware-1.0.0/PKG-INFO +38 -0
- triggerware-1.0.0/README.md +27 -0
- triggerware-1.0.0/pyproject.toml +14 -0
- triggerware-1.0.0/src/triggerware/__init__.py +39 -0
- triggerware-1.0.0/src/triggerware/_client.py +47 -0
- triggerware-1.0.0/src/triggerware/_configuration.py +50 -0
- triggerware-1.0.0/src/triggerware/_connectors.py +171 -0
- triggerware-1.0.0/src/triggerware/_cursors.py +113 -0
- triggerware-1.0.0/src/triggerware/_extensions.py +97 -0
- triggerware-1.0.0/src/triggerware/_logging.py +16 -0
- triggerware-1.0.0/src/triggerware/_prompts.py +25 -0
- triggerware-1.0.0/src/triggerware/_queries.py +149 -0
- triggerware-1.0.0/src/triggerware/_runtimes.py +23 -0
- triggerware-1.0.0/src/triggerware/_system.py +18 -0
- triggerware-1.0.0/src/triggerware/_triggers.py +231 -0
- triggerware-0.1.2/PKG-INFO +0 -12
- triggerware-0.1.2/README.md +0 -1
- triggerware-0.1.2/pyproject.toml +0 -15
- triggerware-0.1.2/src/triggerware/__init__.py +0 -6
- triggerware-0.1.2/src/triggerware/_interfaces.py +0 -26
- triggerware-0.1.2/src/triggerware/_queries.py +0 -403
- triggerware-0.1.2/src/triggerware/_result_set.py +0 -86
- triggerware-0.1.2/src/triggerware/_subscriptions.py +0 -232
- triggerware-0.1.2/src/triggerware/_triggerware_client.py +0 -140
- triggerware-0.1.2/src/triggerware/_types.py +0 -103
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: triggerware
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async Python SDK for Triggerware
|
|
5
|
+
Requires-Dist: tw2-jsonrpc
|
|
6
|
+
Requires-Dist: pyyaml
|
|
7
|
+
Requires-Dist: tw2-jsonrpc[websockets] ; extra == 'websockets'
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Provides-Extra: websockets
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Triggerware Python SDK
|
|
13
|
+
|
|
14
|
+
Async Python SDK for Triggerware.
|
|
15
|
+
|
|
16
|
+
Install with the `websockets` extra when using cloud/WebSocket connections:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
uv add "triggerware[websockets]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from triggerware import TriggerwareClient
|
|
24
|
+
|
|
25
|
+
client = await TriggerwareClient.connect_tcp("localhost:5221")
|
|
26
|
+
|
|
27
|
+
health = await client.system.health()
|
|
28
|
+
cursor = await client.cursors.open(sql="select * from my_connector")
|
|
29
|
+
|
|
30
|
+
async for row in cursor:
|
|
31
|
+
print(row)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
WebSocket connections authorize immediately with the provided API key:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
client = await TriggerwareClient.connect_ws("wss://example.com/rpc", "your-api-key-here")
|
|
38
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Triggerware Python SDK
|
|
2
|
+
|
|
3
|
+
Async Python SDK for Triggerware.
|
|
4
|
+
|
|
5
|
+
Install with the `websockets` extra when using cloud/WebSocket connections:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
uv add "triggerware[websockets]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from triggerware import TriggerwareClient
|
|
13
|
+
|
|
14
|
+
client = await TriggerwareClient.connect_tcp("localhost:5221")
|
|
15
|
+
|
|
16
|
+
health = await client.system.health()
|
|
17
|
+
cursor = await client.cursors.open(sql="select * from my_connector")
|
|
18
|
+
|
|
19
|
+
async for row in cursor:
|
|
20
|
+
print(row)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
WebSocket connections authorize immediately with the provided API key:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
client = await TriggerwareClient.connect_ws("wss://example.com/rpc", "your-api-key-here")
|
|
27
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "triggerware"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Async Python SDK for Triggerware"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = ["tw2-jsonrpc", "pyyaml"]
|
|
8
|
+
|
|
9
|
+
[project.optional-dependencies]
|
|
10
|
+
websockets = ["tw2-jsonrpc[websockets]"]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.10.0,<0.11.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from ._client import TriggerwareClient
|
|
2
|
+
from ._configuration import Configuration
|
|
3
|
+
from ._connectors import (
|
|
4
|
+
ConnectorColumn,
|
|
5
|
+
ConnectorGenerator,
|
|
6
|
+
ConnectorMetadata,
|
|
7
|
+
ConnectorRuntime,
|
|
8
|
+
connector_metadata_from_string,
|
|
9
|
+
)
|
|
10
|
+
from ._cursors import CursorColumn
|
|
11
|
+
from ._extensions import Extension, ExtensionDescription, ExtensionRuntime
|
|
12
|
+
from ._logging import LogLevel
|
|
13
|
+
from ._queries import QuerySignature
|
|
14
|
+
from ._runtimes import Runtime
|
|
15
|
+
from ._triggers import (
|
|
16
|
+
TriggerCallback,
|
|
17
|
+
TriggerDelta,
|
|
18
|
+
TriggerSignature,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Configuration",
|
|
23
|
+
"ConnectorColumn",
|
|
24
|
+
"ConnectorGenerator",
|
|
25
|
+
"ConnectorMetadata",
|
|
26
|
+
"ConnectorRuntime",
|
|
27
|
+
"connector_metadata_from_string",
|
|
28
|
+
"CursorColumn",
|
|
29
|
+
"Extension",
|
|
30
|
+
"ExtensionDescription",
|
|
31
|
+
"ExtensionRuntime",
|
|
32
|
+
"LogLevel",
|
|
33
|
+
"QuerySignature",
|
|
34
|
+
"Runtime",
|
|
35
|
+
"TriggerCallback",
|
|
36
|
+
"TriggerDelta",
|
|
37
|
+
"TriggerSignature",
|
|
38
|
+
"TriggerwareClient",
|
|
39
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
import tw2_jsonrpc as rpc
|
|
6
|
+
|
|
7
|
+
from ._configuration import ConfigurationService
|
|
8
|
+
from ._connectors import ConnectorsService
|
|
9
|
+
from ._cursors import CursorsService
|
|
10
|
+
from ._extensions import ExtensionsService
|
|
11
|
+
from ._logging import LoggingService
|
|
12
|
+
from ._prompts import PromptsService
|
|
13
|
+
from ._queries import QueriesService
|
|
14
|
+
from ._runtimes import RuntimesService
|
|
15
|
+
from ._system import SystemService
|
|
16
|
+
from ._triggers import TriggersService
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TriggerwareClient:
|
|
20
|
+
|
|
21
|
+
def __init__(self, peer: rpc.Peer) -> None:
|
|
22
|
+
self._peer = peer
|
|
23
|
+
self.configuration = ConfigurationService(peer)
|
|
24
|
+
self.connectors = ConnectorsService(peer)
|
|
25
|
+
self.cursors = CursorsService(peer)
|
|
26
|
+
self.extensions = ExtensionsService(peer)
|
|
27
|
+
self.logging = LoggingService(peer)
|
|
28
|
+
self.prompts = PromptsService(peer)
|
|
29
|
+
self.queries = QueriesService(peer)
|
|
30
|
+
self.runtimes = RuntimesService(peer)
|
|
31
|
+
self.system = SystemService(peer)
|
|
32
|
+
self.triggers = TriggersService(peer)
|
|
33
|
+
|
|
34
|
+
# do we need enter? exit? close?
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
async def connect_tcp(cls, address: str) -> Self:
|
|
38
|
+
host, port_text = address.rsplit(":", 1)
|
|
39
|
+
peer = await rpc.connect_tcp(host, int(port_text))
|
|
40
|
+
return cls(peer)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
async def connect_ws(cls, url: str, api_key: str) -> Self:
|
|
44
|
+
peer = await rpc.connect_ws(url)
|
|
45
|
+
client = cls(peer)
|
|
46
|
+
await client.system.authorize(api_key)
|
|
47
|
+
return client
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
import tw2_jsonrpc as rpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Configuration(TypedDict):
|
|
9
|
+
default_timeout_ms: NotRequired[int]
|
|
10
|
+
default_batch_size: NotRequired[int]
|
|
11
|
+
default_trigger_batch_size: NotRequired[int]
|
|
12
|
+
llm_provider: NotRequired[str]
|
|
13
|
+
llm_model: NotRequired[str]
|
|
14
|
+
max_log_file_size: NotRequired[str]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigurationService:
|
|
18
|
+
def __init__(self, peer: rpc.Peer):
|
|
19
|
+
self._peer = peer
|
|
20
|
+
|
|
21
|
+
async def get(self) -> Configuration:
|
|
22
|
+
return await self._peer.call("configuration.get")
|
|
23
|
+
|
|
24
|
+
async def set(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
default_timeout_ms: int | None = None,
|
|
28
|
+
default_batch_size: int | None = None,
|
|
29
|
+
default_trigger_batch_size: int | None = None,
|
|
30
|
+
llm_provider: str | None = None,
|
|
31
|
+
llm_model: str | None = None,
|
|
32
|
+
llm_api_key: str | None = None,
|
|
33
|
+
max_log_file_size: str | None = None,
|
|
34
|
+
) -> Configuration:
|
|
35
|
+
params = {
|
|
36
|
+
"default_timeout_ms": default_timeout_ms,
|
|
37
|
+
"default_batch_size": default_batch_size,
|
|
38
|
+
"default_trigger_batch_size": default_trigger_batch_size,
|
|
39
|
+
"llm_provider": llm_provider,
|
|
40
|
+
"llm_model": llm_model,
|
|
41
|
+
"llm_api_key": llm_api_key,
|
|
42
|
+
"max_log_file_size": max_log_file_size,
|
|
43
|
+
}
|
|
44
|
+
return await self._peer.call(
|
|
45
|
+
"configuration.set",
|
|
46
|
+
{key: value for key, value in params.items() if value is not None},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def refresh(self) -> Configuration:
|
|
50
|
+
return await self._peer.call("configuration.refresh")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Literal, NotRequired, TypedDict
|
|
5
|
+
|
|
6
|
+
import tw2_jsonrpc as rpc
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ConnectorRuntime = Literal["lua", "python", "node"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConnectorColumn(TypedDict):
|
|
14
|
+
name: str
|
|
15
|
+
type: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectorGenerator(TypedDict):
|
|
19
|
+
name: str
|
|
20
|
+
inputs: list[str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConnectorMetadata(TypedDict):
|
|
24
|
+
description: NotRequired[str]
|
|
25
|
+
columns: list[ConnectorColumn]
|
|
26
|
+
generators: list[ConnectorGenerator]
|
|
27
|
+
config_schema: NotRequired[dict[str, Any]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ConnectorExtension(TypedDict):
|
|
31
|
+
name: str
|
|
32
|
+
tag: NotRequired[str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Connector(TypedDict):
|
|
36
|
+
name: str
|
|
37
|
+
meta: ConnectorMetadata
|
|
38
|
+
code: str
|
|
39
|
+
runtime: NotRequired[ConnectorRuntime]
|
|
40
|
+
extension: NotRequired[ConnectorExtension]
|
|
41
|
+
configured: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConnectorsService:
|
|
45
|
+
def __init__(self, peer: rpc.Peer):
|
|
46
|
+
self._peer = peer
|
|
47
|
+
|
|
48
|
+
async def create(
|
|
49
|
+
self,
|
|
50
|
+
name: str,
|
|
51
|
+
*,
|
|
52
|
+
prompt: str | None = None,
|
|
53
|
+
code: str | None = None,
|
|
54
|
+
meta: ConnectorMetadata | str | None = None,
|
|
55
|
+
runtime: ConnectorRuntime | None = None,
|
|
56
|
+
) -> Connector:
|
|
57
|
+
connector = await self._peer.call(
|
|
58
|
+
"connectors.create",
|
|
59
|
+
self._definition_params(
|
|
60
|
+
name=name,
|
|
61
|
+
prompt=prompt,
|
|
62
|
+
code=code,
|
|
63
|
+
meta=meta,
|
|
64
|
+
runtime=runtime,
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
connector["name"] = name
|
|
68
|
+
return connector
|
|
69
|
+
|
|
70
|
+
async def get(self, name: str) -> Connector:
|
|
71
|
+
connector = await self._peer.call("connectors.get", {"name": name})
|
|
72
|
+
connector["name"] = name
|
|
73
|
+
return connector
|
|
74
|
+
|
|
75
|
+
async def update(
|
|
76
|
+
self,
|
|
77
|
+
name: str,
|
|
78
|
+
*,
|
|
79
|
+
prompt: str | None = None,
|
|
80
|
+
code: str | None = None,
|
|
81
|
+
meta: ConnectorMetadata | str | None = None,
|
|
82
|
+
runtime: ConnectorRuntime | None = None,
|
|
83
|
+
) -> Connector:
|
|
84
|
+
connector = await self._peer.call(
|
|
85
|
+
"connectors.update",
|
|
86
|
+
self._definition_params(
|
|
87
|
+
name=name,
|
|
88
|
+
prompt=prompt,
|
|
89
|
+
code=code,
|
|
90
|
+
meta=meta,
|
|
91
|
+
runtime=runtime,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
connector["name"] = name
|
|
95
|
+
return connector
|
|
96
|
+
|
|
97
|
+
async def list(self) -> list[str]:
|
|
98
|
+
response = await self._peer.call("connectors.list")
|
|
99
|
+
return response["connectors"]
|
|
100
|
+
|
|
101
|
+
async def delete(self, name: str) -> None:
|
|
102
|
+
await self._peer.call("connectors.delete", {"name": name})
|
|
103
|
+
|
|
104
|
+
async def sync(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
connectors: list[str] | None = None,
|
|
108
|
+
extensions: list[str] | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
params = {
|
|
111
|
+
"connectors": connectors,
|
|
112
|
+
"extensions": extensions,
|
|
113
|
+
}
|
|
114
|
+
await self._peer.call(
|
|
115
|
+
"connectors.sync",
|
|
116
|
+
{key: value for key, value in params.items() if value is not None},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def configure(self, connector: str | Connector, **config: Any) -> None:
|
|
120
|
+
if isinstance(connector, str):
|
|
121
|
+
name = connector
|
|
122
|
+
attached_connector = None
|
|
123
|
+
else:
|
|
124
|
+
name = connector["name"]
|
|
125
|
+
attached_connector = connector
|
|
126
|
+
|
|
127
|
+
await self._peer.call(
|
|
128
|
+
"connectors.configure",
|
|
129
|
+
{
|
|
130
|
+
"name": name,
|
|
131
|
+
"config": config,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if attached_connector is not None:
|
|
136
|
+
attached_connector["configured"] = True
|
|
137
|
+
|
|
138
|
+
def _definition_params(
|
|
139
|
+
self,
|
|
140
|
+
*,
|
|
141
|
+
name: str,
|
|
142
|
+
prompt: str | None,
|
|
143
|
+
code: str | None,
|
|
144
|
+
meta: ConnectorMetadata | str | None,
|
|
145
|
+
runtime: ConnectorRuntime | None,
|
|
146
|
+
) -> dict[str, Any]:
|
|
147
|
+
params: dict[str, Any] = {"name": name}
|
|
148
|
+
|
|
149
|
+
if prompt is not None:
|
|
150
|
+
params["prompt"] = prompt
|
|
151
|
+
else:
|
|
152
|
+
if code is None or meta is None:
|
|
153
|
+
raise ValueError("either prompt or both code and meta must be supplied")
|
|
154
|
+
params["code"] = code
|
|
155
|
+
params["meta"] = connector_metadata_from_string(meta) if isinstance(meta, str) else meta
|
|
156
|
+
|
|
157
|
+
if runtime is not None:
|
|
158
|
+
params["runtime"] = runtime
|
|
159
|
+
|
|
160
|
+
return params
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def connector_metadata_from_string(contents: str) -> ConnectorMetadata:
|
|
164
|
+
try:
|
|
165
|
+
parsed = yaml.safe_load(contents)
|
|
166
|
+
except yaml.YAMLError:
|
|
167
|
+
parsed = json.loads(contents)
|
|
168
|
+
|
|
169
|
+
if not isinstance(parsed, dict):
|
|
170
|
+
raise ValueError("connector metadata must be an object")
|
|
171
|
+
return parsed # type: ignore[return-value]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
import tw2_jsonrpc as rpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CursorColumn(TypedDict):
|
|
9
|
+
name: str
|
|
10
|
+
type: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CursorOpen(TypedDict):
|
|
14
|
+
sql: str
|
|
15
|
+
signature: list[CursorColumn]
|
|
16
|
+
rows: list[list[Any]]
|
|
17
|
+
cursor: str
|
|
18
|
+
exhausted: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CursorFetch(TypedDict):
|
|
22
|
+
rows: list[list[Any]]
|
|
23
|
+
exhausted: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Cursor:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
peer: rpc.Peer,
|
|
30
|
+
*,
|
|
31
|
+
cursor: str,
|
|
32
|
+
sql: str,
|
|
33
|
+
signature: list[CursorColumn],
|
|
34
|
+
rows: list[list[Any]],
|
|
35
|
+
exhausted: bool,
|
|
36
|
+
):
|
|
37
|
+
self._peer = peer
|
|
38
|
+
self._cursor = cursor
|
|
39
|
+
self.sql = sql
|
|
40
|
+
self.signature = signature
|
|
41
|
+
self.exhausted = exhausted
|
|
42
|
+
self._cache = rows
|
|
43
|
+
self._cache_position = 0
|
|
44
|
+
|
|
45
|
+
async def fetch(self) -> list[list[Any]]:
|
|
46
|
+
if self.exhausted:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
response: CursorFetch = await self._peer.call(
|
|
50
|
+
"cursors.fetch",
|
|
51
|
+
{"cursor": self._cursor},
|
|
52
|
+
)
|
|
53
|
+
self.exhausted = response["exhausted"]
|
|
54
|
+
self._cache.extend(response["rows"])
|
|
55
|
+
return response["rows"]
|
|
56
|
+
|
|
57
|
+
async def close(self) -> None:
|
|
58
|
+
await self._peer.call("cursors.close", {"cursor": self._cursor})
|
|
59
|
+
self.exhausted = True
|
|
60
|
+
|
|
61
|
+
def __aiter__(self) -> Cursor:
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
async def __anext__(self) -> list[Any]:
|
|
65
|
+
if self._cache_position < len(self._cache):
|
|
66
|
+
row = self._cache[self._cache_position]
|
|
67
|
+
self._cache_position += 1
|
|
68
|
+
return row
|
|
69
|
+
|
|
70
|
+
if self.exhausted:
|
|
71
|
+
raise StopAsyncIteration
|
|
72
|
+
|
|
73
|
+
await self.fetch()
|
|
74
|
+
|
|
75
|
+
if self._cache_position < len(self._cache):
|
|
76
|
+
row = self._cache[self._cache_position]
|
|
77
|
+
self._cache_position += 1
|
|
78
|
+
return row
|
|
79
|
+
|
|
80
|
+
raise StopAsyncIteration
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CursorsService:
|
|
84
|
+
def __init__(self, peer: rpc.Peer):
|
|
85
|
+
self._peer = peer
|
|
86
|
+
|
|
87
|
+
async def open(
|
|
88
|
+
self,
|
|
89
|
+
*,
|
|
90
|
+
sql: str | None = None,
|
|
91
|
+
prompt: str | None = None,
|
|
92
|
+
batch_size: int | None = None,
|
|
93
|
+
timeout_ms: int | None = None,
|
|
94
|
+
) -> Cursor:
|
|
95
|
+
params = _run_params(
|
|
96
|
+
sql=sql,
|
|
97
|
+
prompt=prompt,
|
|
98
|
+
batch_size=batch_size,
|
|
99
|
+
timeout_ms=timeout_ms,
|
|
100
|
+
)
|
|
101
|
+
response: CursorOpen = await self._peer.call("cursors.open", params)
|
|
102
|
+
return Cursor(
|
|
103
|
+
self._peer,
|
|
104
|
+
cursor=response["cursor"],
|
|
105
|
+
sql=response["sql"],
|
|
106
|
+
signature=response["signature"],
|
|
107
|
+
rows=response["rows"],
|
|
108
|
+
exhausted=response["exhausted"],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _run_params(**values: Any) -> dict[str, Any]:
|
|
113
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal, NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
import tw2_jsonrpc as rpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ExtensionRuntime = Literal["lua", "python", "node"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Extension(TypedDict):
|
|
12
|
+
name: str
|
|
13
|
+
tag: NotRequired[str]
|
|
14
|
+
config: dict[str, Any] | None
|
|
15
|
+
connectors: list[str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExtensionDescription(TypedDict):
|
|
19
|
+
description: str
|
|
20
|
+
config_schema: dict[str, Any]
|
|
21
|
+
runtime: NotRequired[ExtensionRuntime]
|
|
22
|
+
dependencies: NotRequired[list[str]]
|
|
23
|
+
connectors: list[str]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExtensionsService:
|
|
27
|
+
def __init__(self, peer: rpc.Peer):
|
|
28
|
+
self._peer = peer
|
|
29
|
+
|
|
30
|
+
async def install(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
name: str | None = None,
|
|
34
|
+
url: str | None = None,
|
|
35
|
+
tag: str | None = None,
|
|
36
|
+
config: dict[str, Any] | None = None,
|
|
37
|
+
) -> list[str]:
|
|
38
|
+
response = await self._peer.call(
|
|
39
|
+
"extensions.install",
|
|
40
|
+
_extension_params(name=name, url=url, tag=tag, config=config or {}),
|
|
41
|
+
)
|
|
42
|
+
return response["connectors"]
|
|
43
|
+
|
|
44
|
+
async def get(self, name: str, *, tag: str | None = None) -> Extension:
|
|
45
|
+
response = await self._peer.call(
|
|
46
|
+
"extensions.get",
|
|
47
|
+
_extension_params(name=name, tag=tag),
|
|
48
|
+
)
|
|
49
|
+
extension: Extension = {
|
|
50
|
+
"name": name,
|
|
51
|
+
"config": response["config"],
|
|
52
|
+
"connectors": response["connectors"],
|
|
53
|
+
}
|
|
54
|
+
if tag is not None:
|
|
55
|
+
extension["tag"] = tag
|
|
56
|
+
return extension
|
|
57
|
+
|
|
58
|
+
async def list(self) -> list[str]:
|
|
59
|
+
response = await self._peer.call("extensions.list")
|
|
60
|
+
return response["extensions"]
|
|
61
|
+
|
|
62
|
+
async def configure(
|
|
63
|
+
self,
|
|
64
|
+
name: str,
|
|
65
|
+
*,
|
|
66
|
+
tag: str | None = None,
|
|
67
|
+
config: dict[str, Any],
|
|
68
|
+
) -> None:
|
|
69
|
+
await self._peer.call(
|
|
70
|
+
"extensions.configure",
|
|
71
|
+
_extension_params(name=name, tag=tag, config=config),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def catalog(self) -> list[str]:
|
|
75
|
+
response = await self._peer.call("extensions.catalog")
|
|
76
|
+
return response["extensions"]
|
|
77
|
+
|
|
78
|
+
async def describe(
|
|
79
|
+
self,
|
|
80
|
+
*,
|
|
81
|
+
name: str | None = None,
|
|
82
|
+
url: str | None = None,
|
|
83
|
+
) -> ExtensionDescription:
|
|
84
|
+
return await self._peer.call(
|
|
85
|
+
"extensions.describe",
|
|
86
|
+
_extension_params(name=name, url=url),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def uninstall(self, name: str, *, tag: str | None = None) -> None:
|
|
90
|
+
await self._peer.call(
|
|
91
|
+
"extensions.uninstall",
|
|
92
|
+
_extension_params(name=name, tag=tag),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _extension_params(**values: Any) -> dict[str, Any]:
|
|
97
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import tw2_jsonrpc as rpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
LogLevel = Literal["debug", "info", "error"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoggingService:
|
|
12
|
+
def __init__(self, peer: rpc.Peer):
|
|
13
|
+
self._peer = peer
|
|
14
|
+
|
|
15
|
+
async def set(self, level: LogLevel) -> None:
|
|
16
|
+
await self._peer.call("logging.set", {"level": level})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tw2_jsonrpc as rpc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PromptsService:
|
|
7
|
+
def __init__(self, peer: rpc.Peer):
|
|
8
|
+
self._peer = peer
|
|
9
|
+
|
|
10
|
+
async def get(self, name: str) -> str:
|
|
11
|
+
response = await self._peer.call("prompts.get", {"name": name})
|
|
12
|
+
return response["prompt"]
|
|
13
|
+
|
|
14
|
+
async def set(self, name: str, prompt: str) -> None:
|
|
15
|
+
await self._peer.call(
|
|
16
|
+
"prompts.set",
|
|
17
|
+
{
|
|
18
|
+
"name": name,
|
|
19
|
+
"prompt": prompt,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
async def reset(self, name: str | None = None) -> None:
|
|
24
|
+
params = {} if name is None else {"name": name}
|
|
25
|
+
await self._peer.call("prompts.reset", params)
|