sigmashake 0.1.1__py3-none-any.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.
- sigmashake/__init__.py +25 -0
- sigmashake/_version.py +1 -0
- sigmashake/accounts.py +91 -0
- sigmashake/agents.py +47 -0
- sigmashake/auth.py +57 -0
- sigmashake/client.py +217 -0
- sigmashake/db.py +213 -0
- sigmashake/exceptions.py +106 -0
- sigmashake/fleet.py +199 -0
- sigmashake/gateway.py +85 -0
- sigmashake/memory.py +81 -0
- sigmashake/models.py +576 -0
- sigmashake/shield.py +69 -0
- sigmashake/soc.py +79 -0
- sigmashake-0.1.1.dist-info/METADATA +74 -0
- sigmashake-0.1.1.dist-info/RECORD +18 -0
- sigmashake-0.1.1.dist-info/WHEEL +4 -0
- sigmashake-0.1.1.dist-info/licenses/LICENSE +21 -0
sigmashake/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""SigmaShake Python SDK -- agent-first, async-native, type-safe."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .client import SigmaShake
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
AuthorizationError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ServerError,
|
|
11
|
+
SigmaShakeError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"__version__",
|
|
17
|
+
"SigmaShake",
|
|
18
|
+
"SigmaShakeError",
|
|
19
|
+
"AuthenticationError",
|
|
20
|
+
"AuthorizationError",
|
|
21
|
+
"NotFoundError",
|
|
22
|
+
"ValidationError",
|
|
23
|
+
"RateLimitError",
|
|
24
|
+
"ServerError",
|
|
25
|
+
]
|
sigmashake/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
sigmashake/accounts.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Account management operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .models import (
|
|
8
|
+
Account,
|
|
9
|
+
AddSeatBody,
|
|
10
|
+
Seat,
|
|
11
|
+
Subscription,
|
|
12
|
+
TenantUsage,
|
|
13
|
+
Tier,
|
|
14
|
+
UpdateSubscriptionBody,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .client import _HTTPTransport
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AccountsResource:
|
|
22
|
+
"""Account CRUD and subscription management."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
25
|
+
self._t = transport
|
|
26
|
+
|
|
27
|
+
# -- accounts -------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
def create(self, name: str, tier: str = "free") -> Account:
|
|
30
|
+
body = {"name": name, "tier": tier}
|
|
31
|
+
data = self._t.request("POST", "/v1/accounts", json=body)
|
|
32
|
+
return Account.model_validate(data)
|
|
33
|
+
|
|
34
|
+
async def async_create(self, name: str, tier: str = "free") -> Account:
|
|
35
|
+
body = {"name": name, "tier": tier}
|
|
36
|
+
data = await self._t.async_request("POST", "/v1/accounts", json=body)
|
|
37
|
+
return Account.model_validate(data)
|
|
38
|
+
|
|
39
|
+
def get(self, account_id: str) -> Account:
|
|
40
|
+
data = self._t.request("GET", f"/v1/accounts/{account_id}")
|
|
41
|
+
return Account.model_validate(data)
|
|
42
|
+
|
|
43
|
+
async def async_get(self, account_id: str) -> Account:
|
|
44
|
+
data = await self._t.async_request("GET", f"/v1/accounts/{account_id}")
|
|
45
|
+
return Account.model_validate(data)
|
|
46
|
+
|
|
47
|
+
def get_usage(self, account_id: str) -> TenantUsage:
|
|
48
|
+
data = self._t.request("GET", f"/v1/accounts/{account_id}/usage")
|
|
49
|
+
return TenantUsage.model_validate(data)
|
|
50
|
+
|
|
51
|
+
async def async_get_usage(self, account_id: str) -> TenantUsage:
|
|
52
|
+
data = await self._t.async_request("GET", f"/v1/accounts/{account_id}/usage")
|
|
53
|
+
return TenantUsage.model_validate(data)
|
|
54
|
+
|
|
55
|
+
# -- subscriptions --------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
def get_subscription(self, account_id: str) -> Subscription:
|
|
58
|
+
data = self._t.request("GET", f"/v1/accounts/{account_id}/subscription")
|
|
59
|
+
return Subscription.model_validate(data)
|
|
60
|
+
|
|
61
|
+
async def async_get_subscription(self, account_id: str) -> Subscription:
|
|
62
|
+
data = await self._t.async_request("GET", f"/v1/accounts/{account_id}/subscription")
|
|
63
|
+
return Subscription.model_validate(data)
|
|
64
|
+
|
|
65
|
+
def update_subscription(self, account_id: str, **kwargs: Any) -> Subscription:
|
|
66
|
+
data = self._t.request("PATCH", f"/v1/accounts/{account_id}/subscription", json=kwargs)
|
|
67
|
+
return Subscription.model_validate(data)
|
|
68
|
+
|
|
69
|
+
async def async_update_subscription(self, account_id: str, **kwargs: Any) -> Subscription:
|
|
70
|
+
data = await self._t.async_request("PATCH", f"/v1/accounts/{account_id}/subscription", json=kwargs)
|
|
71
|
+
return Subscription.model_validate(data)
|
|
72
|
+
|
|
73
|
+
# -- seats ----------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
def add_seat(self, account_id: str, user_email: str, role: str = "member") -> Seat:
|
|
76
|
+
body = {"user_email": user_email, "role": role}
|
|
77
|
+
data = self._t.request("POST", f"/v1/accounts/{account_id}/seats", json=body)
|
|
78
|
+
return Seat.model_validate(data)
|
|
79
|
+
|
|
80
|
+
async def async_add_seat(self, account_id: str, user_email: str, role: str = "member") -> Seat:
|
|
81
|
+
body = {"user_email": user_email, "role": role}
|
|
82
|
+
data = await self._t.async_request("POST", f"/v1/accounts/{account_id}/seats", json=body)
|
|
83
|
+
return Seat.model_validate(data)
|
|
84
|
+
|
|
85
|
+
def list_seats(self, account_id: str) -> List[Seat]:
|
|
86
|
+
data = self._t.request("GET", f"/v1/accounts/{account_id}/seats")
|
|
87
|
+
return [Seat.model_validate(s) for s in data.get("seats", data if isinstance(data, list) else [])]
|
|
88
|
+
|
|
89
|
+
async def async_list_seats(self, account_id: str) -> List[Seat]:
|
|
90
|
+
data = await self._t.async_request("GET", f"/v1/accounts/{account_id}/seats")
|
|
91
|
+
return [Seat.model_validate(s) for s in data.get("seats", data if isinstance(data, list) else [])]
|
sigmashake/agents.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Agent lifecycle operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .client import _HTTPTransport
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentsResource:
|
|
12
|
+
"""Agent registration and management."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
15
|
+
self._t = transport
|
|
16
|
+
|
|
17
|
+
def register(
|
|
18
|
+
self,
|
|
19
|
+
agent_id: str,
|
|
20
|
+
agent_type: str,
|
|
21
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
22
|
+
) -> Dict[str, Any]:
|
|
23
|
+
body = {"agent_id": agent_id, "agent_type": agent_type, "metadata": metadata or {}}
|
|
24
|
+
return self._t.request("POST", "/v1/agents", json=body)
|
|
25
|
+
|
|
26
|
+
async def async_register(
|
|
27
|
+
self,
|
|
28
|
+
agent_id: str,
|
|
29
|
+
agent_type: str,
|
|
30
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
body = {"agent_id": agent_id, "agent_type": agent_type, "metadata": metadata or {}}
|
|
33
|
+
return await self._t.async_request("POST", "/v1/agents", json=body)
|
|
34
|
+
|
|
35
|
+
def get(self, agent_id: str) -> Dict[str, Any]:
|
|
36
|
+
return self._t.request("GET", f"/v1/agents/{agent_id}")
|
|
37
|
+
|
|
38
|
+
async def async_get(self, agent_id: str) -> Dict[str, Any]:
|
|
39
|
+
return await self._t.async_request("GET", f"/v1/agents/{agent_id}")
|
|
40
|
+
|
|
41
|
+
def list(self, limit: int = 100, offset: int = 0) -> List[Dict[str, Any]]:
|
|
42
|
+
data = self._t.request("GET", "/v1/agents", params={"limit": limit, "offset": offset})
|
|
43
|
+
return data.get("agents", []) if isinstance(data, dict) else data
|
|
44
|
+
|
|
45
|
+
async def async_list(self, limit: int = 100, offset: int = 0) -> List[Dict[str, Any]]:
|
|
46
|
+
data = await self._t.async_request("GET", "/v1/agents", params={"limit": limit, "offset": offset})
|
|
47
|
+
return data.get("agents", []) if isinstance(data, dict) else data
|
sigmashake/auth.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Auth and Identity token management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, List, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .models import (
|
|
8
|
+
IdentityTokenResponse,
|
|
9
|
+
TokenResponse,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .client import _HTTPTransport
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthResource:
|
|
17
|
+
"""Token creation and validation."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
20
|
+
self._t = transport
|
|
21
|
+
|
|
22
|
+
def create_token(self, agent_id: str, scopes: List[str] | None = None) -> TokenResponse:
|
|
23
|
+
body = {"agent_id": agent_id, "scopes": scopes or []}
|
|
24
|
+
data = self._t.request("POST", "/v1/auth/token", json=body)
|
|
25
|
+
return TokenResponse.model_validate(data)
|
|
26
|
+
|
|
27
|
+
async def async_create_token(self, agent_id: str, scopes: List[str] | None = None) -> TokenResponse:
|
|
28
|
+
body = {"agent_id": agent_id, "scopes": scopes or []}
|
|
29
|
+
data = await self._t.async_request("POST", "/v1/auth/token", json=body)
|
|
30
|
+
return TokenResponse.model_validate(data)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class IdentityResource:
|
|
34
|
+
"""Agent identity issuance."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
37
|
+
self._t = transport
|
|
38
|
+
|
|
39
|
+
def issue(
|
|
40
|
+
self,
|
|
41
|
+
agent_id: str,
|
|
42
|
+
capabilities: List[str] | None = None,
|
|
43
|
+
ttl_secs: int = 3600,
|
|
44
|
+
) -> IdentityTokenResponse:
|
|
45
|
+
body = {"agent_id": agent_id, "capabilities": capabilities or [], "ttl_secs": ttl_secs}
|
|
46
|
+
data = self._t.request("POST", "/v1/identity/issue", json=body)
|
|
47
|
+
return IdentityTokenResponse.model_validate(data)
|
|
48
|
+
|
|
49
|
+
async def async_issue(
|
|
50
|
+
self,
|
|
51
|
+
agent_id: str,
|
|
52
|
+
capabilities: List[str] | None = None,
|
|
53
|
+
ttl_secs: int = 3600,
|
|
54
|
+
) -> IdentityTokenResponse:
|
|
55
|
+
body = {"agent_id": agent_id, "capabilities": capabilities or [], "ttl_secs": ttl_secs}
|
|
56
|
+
data = await self._t.async_request("POST", "/v1/identity/issue", json=body)
|
|
57
|
+
return IdentityTokenResponse.model_validate(data)
|
sigmashake/client.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Main SigmaShake client -- sync and async HTTP transport."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from .auth import AuthResource, IdentityResource
|
|
10
|
+
from .accounts import AccountsResource
|
|
11
|
+
from .agents import AgentsResource
|
|
12
|
+
from .db import DBResource
|
|
13
|
+
from .exceptions import raise_for_status
|
|
14
|
+
from .fleet import FleetResource
|
|
15
|
+
from .gateway import GatewayResource
|
|
16
|
+
from .memory import MemoryResource
|
|
17
|
+
from .shield import ShieldResource
|
|
18
|
+
from .soc import SOCResource
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_DEFAULT_BASE_URL = "https://api.sigmashake.com"
|
|
22
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _HTTPTransport:
|
|
26
|
+
"""Thin wrapper around httpx providing sync and async request methods."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
api_key: str,
|
|
32
|
+
base_url: str,
|
|
33
|
+
timeout: float,
|
|
34
|
+
async_mode: bool,
|
|
35
|
+
) -> None:
|
|
36
|
+
self._base_url = base_url.rstrip("/")
|
|
37
|
+
self._async_mode = async_mode
|
|
38
|
+
headers = {
|
|
39
|
+
"Authorization": f"Bearer {api_key}",
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"User-Agent": "sigmashake-python/0.1.0",
|
|
42
|
+
}
|
|
43
|
+
if async_mode:
|
|
44
|
+
self._async_client = httpx.AsyncClient(
|
|
45
|
+
base_url=self._base_url,
|
|
46
|
+
headers=headers,
|
|
47
|
+
timeout=timeout,
|
|
48
|
+
)
|
|
49
|
+
self._sync_client = None
|
|
50
|
+
else:
|
|
51
|
+
self._sync_client = httpx.Client(
|
|
52
|
+
base_url=self._base_url,
|
|
53
|
+
headers=headers,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
)
|
|
56
|
+
self._async_client = None
|
|
57
|
+
|
|
58
|
+
# -- sync -----------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def request(
|
|
61
|
+
self,
|
|
62
|
+
method: str,
|
|
63
|
+
path: str,
|
|
64
|
+
*,
|
|
65
|
+
json: Optional[Dict[str, Any]] = None,
|
|
66
|
+
params: Optional[Dict[str, Any]] = None,
|
|
67
|
+
) -> Dict[str, Any]:
|
|
68
|
+
assert self._sync_client is not None, "Use async_request in async_mode"
|
|
69
|
+
resp = self._sync_client.request(method, path, json=json, params=params)
|
|
70
|
+
body = resp.json() if resp.content else {}
|
|
71
|
+
raise_for_status(resp.status_code, body)
|
|
72
|
+
return body
|
|
73
|
+
|
|
74
|
+
# -- async ----------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
async def async_request(
|
|
77
|
+
self,
|
|
78
|
+
method: str,
|
|
79
|
+
path: str,
|
|
80
|
+
*,
|
|
81
|
+
json: Optional[Dict[str, Any]] = None,
|
|
82
|
+
params: Optional[Dict[str, Any]] = None,
|
|
83
|
+
) -> Dict[str, Any]:
|
|
84
|
+
assert self._async_client is not None, "Use request in sync mode"
|
|
85
|
+
resp = await self._async_client.request(method, path, json=json, params=params)
|
|
86
|
+
body = resp.json() if resp.content else {}
|
|
87
|
+
raise_for_status(resp.status_code, body)
|
|
88
|
+
return body
|
|
89
|
+
|
|
90
|
+
# -- lifecycle ------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def close(self) -> None:
|
|
93
|
+
if self._sync_client is not None:
|
|
94
|
+
self._sync_client.close()
|
|
95
|
+
|
|
96
|
+
async def aclose(self) -> None:
|
|
97
|
+
if self._async_client is not None:
|
|
98
|
+
await self._async_client.aclose()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SigmaShake:
|
|
102
|
+
"""Top-level SigmaShake client.
|
|
103
|
+
|
|
104
|
+
Usage (sync)::
|
|
105
|
+
|
|
106
|
+
client = SigmaShake(api_key="sk-...")
|
|
107
|
+
token = client.auth.create_token(agent_id="a1", scopes=["read"])
|
|
108
|
+
|
|
109
|
+
Usage (async)::
|
|
110
|
+
|
|
111
|
+
async with SigmaShake(api_key="sk-...", async_mode=True) as client:
|
|
112
|
+
token = await client.auth.create_token(agent_id="a1", scopes=["read"])
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
api_key: str,
|
|
119
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
120
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
121
|
+
async_mode: bool = False,
|
|
122
|
+
) -> None:
|
|
123
|
+
self._transport = _HTTPTransport(
|
|
124
|
+
api_key=api_key,
|
|
125
|
+
base_url=base_url,
|
|
126
|
+
timeout=timeout,
|
|
127
|
+
async_mode=async_mode,
|
|
128
|
+
)
|
|
129
|
+
self._async_mode = async_mode
|
|
130
|
+
|
|
131
|
+
# Resource namespaces
|
|
132
|
+
self.auth = AuthResource(self._transport)
|
|
133
|
+
self.identity = IdentityResource(self._transport)
|
|
134
|
+
self.accounts = AccountsResource(self._transport)
|
|
135
|
+
self.agents = AgentsResource(self._transport)
|
|
136
|
+
self.shield = ShieldResource(self._transport)
|
|
137
|
+
self.documents = AgentsResource(self._transport) # alias kept for back-compat
|
|
138
|
+
self.soc = SOCResource(self._transport)
|
|
139
|
+
self.gateway = GatewayResource(self._transport)
|
|
140
|
+
self.db = DBResource(self._transport)
|
|
141
|
+
self.memory = MemoryResource(self._transport)
|
|
142
|
+
self.fleet = FleetResource(self._transport)
|
|
143
|
+
# documents uses its own resource
|
|
144
|
+
self.documents = _DocumentsResource(self._transport)
|
|
145
|
+
|
|
146
|
+
# -- context managers -----------------------------------------------------
|
|
147
|
+
|
|
148
|
+
def __enter__(self) -> "SigmaShake":
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
def __exit__(self, *_: Any) -> None:
|
|
152
|
+
self.close()
|
|
153
|
+
|
|
154
|
+
async def __aenter__(self) -> "SigmaShake":
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
async def __aexit__(self, *_: Any) -> None:
|
|
158
|
+
await self.aclose()
|
|
159
|
+
|
|
160
|
+
def close(self) -> None:
|
|
161
|
+
self._transport.close()
|
|
162
|
+
|
|
163
|
+
async def aclose(self) -> None:
|
|
164
|
+
await self._transport.aclose()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class _DocumentsResource:
|
|
168
|
+
"""Document mutation and search operations."""
|
|
169
|
+
|
|
170
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
171
|
+
self._t = transport
|
|
172
|
+
|
|
173
|
+
def create(
|
|
174
|
+
self,
|
|
175
|
+
resource: str,
|
|
176
|
+
action: str,
|
|
177
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
178
|
+
) -> Any:
|
|
179
|
+
from .models import MutationResponse
|
|
180
|
+
body = {"resource": resource, "action": action, "payload": payload or {}}
|
|
181
|
+
data = self._t.request("POST", "/v1/documents", json=body)
|
|
182
|
+
return MutationResponse.model_validate(data)
|
|
183
|
+
|
|
184
|
+
async def async_create(
|
|
185
|
+
self,
|
|
186
|
+
resource: str,
|
|
187
|
+
action: str,
|
|
188
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
189
|
+
) -> Any:
|
|
190
|
+
from .models import MutationResponse
|
|
191
|
+
body = {"resource": resource, "action": action, "payload": payload or {}}
|
|
192
|
+
data = await self._t.async_request("POST", "/v1/documents", json=body)
|
|
193
|
+
return MutationResponse.model_validate(data)
|
|
194
|
+
|
|
195
|
+
def search(
|
|
196
|
+
self,
|
|
197
|
+
query: str,
|
|
198
|
+
limit: int = 10,
|
|
199
|
+
offset: int = 0,
|
|
200
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
201
|
+
) -> Any:
|
|
202
|
+
from .models import SearchResponse
|
|
203
|
+
body = {"query": query, "limit": limit, "offset": offset, "filters": filters or {}}
|
|
204
|
+
data = self._t.request("POST", "/v1/documents/search", json=body)
|
|
205
|
+
return SearchResponse.model_validate(data)
|
|
206
|
+
|
|
207
|
+
async def async_search(
|
|
208
|
+
self,
|
|
209
|
+
query: str,
|
|
210
|
+
limit: int = 10,
|
|
211
|
+
offset: int = 0,
|
|
212
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
213
|
+
) -> Any:
|
|
214
|
+
from .models import SearchResponse
|
|
215
|
+
body = {"query": query, "limit": limit, "offset": offset, "filters": filters or {}}
|
|
216
|
+
data = await self._t.async_request("POST", "/v1/documents/search", json=body)
|
|
217
|
+
return SearchResponse.model_validate(data)
|
sigmashake/db.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Database operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .models import (
|
|
8
|
+
ClusterStatusResponse,
|
|
9
|
+
QueryResponse,
|
|
10
|
+
ScrollQueryResponse,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .client import _HTTPTransport
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DBResource:
|
|
18
|
+
"""SigmaShake database operations."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, transport: _HTTPTransport) -> None:
|
|
21
|
+
self._t = transport
|
|
22
|
+
|
|
23
|
+
# -- tables ---------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def create_table(
|
|
26
|
+
self,
|
|
27
|
+
table_name: str,
|
|
28
|
+
columns: List[Dict[str, str]],
|
|
29
|
+
) -> Dict[str, Any]:
|
|
30
|
+
body = {"table_name": table_name, "columns": columns}
|
|
31
|
+
return self._t.request("POST", "/v1/db/tables", json=body)
|
|
32
|
+
|
|
33
|
+
async def async_create_table(
|
|
34
|
+
self,
|
|
35
|
+
table_name: str,
|
|
36
|
+
columns: List[Dict[str, str]],
|
|
37
|
+
) -> Dict[str, Any]:
|
|
38
|
+
body = {"table_name": table_name, "columns": columns}
|
|
39
|
+
return await self._t.async_request("POST", "/v1/db/tables", json=body)
|
|
40
|
+
|
|
41
|
+
# -- insert ---------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def insert(
|
|
44
|
+
self,
|
|
45
|
+
table_name: str,
|
|
46
|
+
columns: List[Dict[str, Any]],
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
body = {"table_name": table_name, "columns": columns}
|
|
49
|
+
return self._t.request("POST", "/v1/db/insert", json=body)
|
|
50
|
+
|
|
51
|
+
async def async_insert(
|
|
52
|
+
self,
|
|
53
|
+
table_name: str,
|
|
54
|
+
columns: List[Dict[str, Any]],
|
|
55
|
+
) -> Dict[str, Any]:
|
|
56
|
+
body = {"table_name": table_name, "columns": columns}
|
|
57
|
+
return await self._t.async_request("POST", "/v1/db/insert", json=body)
|
|
58
|
+
|
|
59
|
+
# -- query ----------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
def query(
|
|
62
|
+
self,
|
|
63
|
+
table_name: str,
|
|
64
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
65
|
+
columns: Optional[List[str]] = None,
|
|
66
|
+
limit: Optional[int] = None,
|
|
67
|
+
offset: Optional[int] = None,
|
|
68
|
+
) -> QueryResponse:
|
|
69
|
+
body: Dict[str, Any] = {"table_name": table_name}
|
|
70
|
+
if filters:
|
|
71
|
+
body["filters"] = filters
|
|
72
|
+
if columns:
|
|
73
|
+
body["columns"] = columns
|
|
74
|
+
if limit is not None:
|
|
75
|
+
body["limit"] = limit
|
|
76
|
+
if offset is not None:
|
|
77
|
+
body["offset"] = offset
|
|
78
|
+
data = self._t.request("POST", "/v1/db/query", json=body)
|
|
79
|
+
return QueryResponse.model_validate(data)
|
|
80
|
+
|
|
81
|
+
async def async_query(
|
|
82
|
+
self,
|
|
83
|
+
table_name: str,
|
|
84
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
85
|
+
columns: Optional[List[str]] = None,
|
|
86
|
+
limit: Optional[int] = None,
|
|
87
|
+
offset: Optional[int] = None,
|
|
88
|
+
) -> QueryResponse:
|
|
89
|
+
body: Dict[str, Any] = {"table_name": table_name}
|
|
90
|
+
if filters:
|
|
91
|
+
body["filters"] = filters
|
|
92
|
+
if columns:
|
|
93
|
+
body["columns"] = columns
|
|
94
|
+
if limit is not None:
|
|
95
|
+
body["limit"] = limit
|
|
96
|
+
if offset is not None:
|
|
97
|
+
body["offset"] = offset
|
|
98
|
+
data = await self._t.async_request("POST", "/v1/db/query", json=body)
|
|
99
|
+
return QueryResponse.model_validate(data)
|
|
100
|
+
|
|
101
|
+
# -- vector search --------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
def vector_search(
|
|
104
|
+
self,
|
|
105
|
+
table_name: str,
|
|
106
|
+
column: str,
|
|
107
|
+
vector: List[float],
|
|
108
|
+
top_k: int = 10,
|
|
109
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
110
|
+
) -> QueryResponse:
|
|
111
|
+
body: Dict[str, Any] = {
|
|
112
|
+
"table_name": table_name,
|
|
113
|
+
"column": column,
|
|
114
|
+
"vector": vector,
|
|
115
|
+
"top_k": top_k,
|
|
116
|
+
}
|
|
117
|
+
if filters:
|
|
118
|
+
body["filters"] = filters
|
|
119
|
+
data = self._t.request("POST", "/v1/db/vector-search", json=body)
|
|
120
|
+
return QueryResponse.model_validate(data)
|
|
121
|
+
|
|
122
|
+
async def async_vector_search(
|
|
123
|
+
self,
|
|
124
|
+
table_name: str,
|
|
125
|
+
column: str,
|
|
126
|
+
vector: List[float],
|
|
127
|
+
top_k: int = 10,
|
|
128
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
129
|
+
) -> QueryResponse:
|
|
130
|
+
body: Dict[str, Any] = {
|
|
131
|
+
"table_name": table_name,
|
|
132
|
+
"column": column,
|
|
133
|
+
"vector": vector,
|
|
134
|
+
"top_k": top_k,
|
|
135
|
+
}
|
|
136
|
+
if filters:
|
|
137
|
+
body["filters"] = filters
|
|
138
|
+
data = await self._t.async_request("POST", "/v1/db/vector-search", json=body)
|
|
139
|
+
return QueryResponse.model_validate(data)
|
|
140
|
+
|
|
141
|
+
# -- scroll ---------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def scroll(
|
|
144
|
+
self,
|
|
145
|
+
table_name: str,
|
|
146
|
+
batch_size: int = 100,
|
|
147
|
+
cursor: Optional[str] = None,
|
|
148
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
149
|
+
) -> ScrollQueryResponse:
|
|
150
|
+
body: Dict[str, Any] = {"table_name": table_name, "batch_size": batch_size}
|
|
151
|
+
if cursor:
|
|
152
|
+
body["cursor"] = cursor
|
|
153
|
+
if filters:
|
|
154
|
+
body["filters"] = filters
|
|
155
|
+
data = self._t.request("POST", "/v1/db/scroll", json=body)
|
|
156
|
+
return ScrollQueryResponse.model_validate(data)
|
|
157
|
+
|
|
158
|
+
async def async_scroll(
|
|
159
|
+
self,
|
|
160
|
+
table_name: str,
|
|
161
|
+
batch_size: int = 100,
|
|
162
|
+
cursor: Optional[str] = None,
|
|
163
|
+
filters: Optional[List[Dict[str, Any]]] = None,
|
|
164
|
+
) -> ScrollQueryResponse:
|
|
165
|
+
body: Dict[str, Any] = {"table_name": table_name, "batch_size": batch_size}
|
|
166
|
+
if cursor:
|
|
167
|
+
body["cursor"] = cursor
|
|
168
|
+
if filters:
|
|
169
|
+
body["filters"] = filters
|
|
170
|
+
data = await self._t.async_request("POST", "/v1/db/scroll", json=body)
|
|
171
|
+
return ScrollQueryResponse.model_validate(data)
|
|
172
|
+
|
|
173
|
+
# -- cluster --------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
def init_cluster(
|
|
176
|
+
self,
|
|
177
|
+
cluster_name: str,
|
|
178
|
+
node_count: int = 1,
|
|
179
|
+
replication_factor: int = 1,
|
|
180
|
+
config: Optional[Dict[str, Any]] = None,
|
|
181
|
+
) -> ClusterStatusResponse:
|
|
182
|
+
body = {
|
|
183
|
+
"cluster_name": cluster_name,
|
|
184
|
+
"node_count": node_count,
|
|
185
|
+
"replication_factor": replication_factor,
|
|
186
|
+
"config": config or {},
|
|
187
|
+
}
|
|
188
|
+
data = self._t.request("POST", "/v1/db/cluster/init", json=body)
|
|
189
|
+
return ClusterStatusResponse.model_validate(data)
|
|
190
|
+
|
|
191
|
+
async def async_init_cluster(
|
|
192
|
+
self,
|
|
193
|
+
cluster_name: str,
|
|
194
|
+
node_count: int = 1,
|
|
195
|
+
replication_factor: int = 1,
|
|
196
|
+
config: Optional[Dict[str, Any]] = None,
|
|
197
|
+
) -> ClusterStatusResponse:
|
|
198
|
+
body = {
|
|
199
|
+
"cluster_name": cluster_name,
|
|
200
|
+
"node_count": node_count,
|
|
201
|
+
"replication_factor": replication_factor,
|
|
202
|
+
"config": config or {},
|
|
203
|
+
}
|
|
204
|
+
data = await self._t.async_request("POST", "/v1/db/cluster/init", json=body)
|
|
205
|
+
return ClusterStatusResponse.model_validate(data)
|
|
206
|
+
|
|
207
|
+
def cluster_status(self, cluster_name: str) -> ClusterStatusResponse:
|
|
208
|
+
data = self._t.request("GET", f"/v1/db/cluster/{cluster_name}/status")
|
|
209
|
+
return ClusterStatusResponse.model_validate(data)
|
|
210
|
+
|
|
211
|
+
async def async_cluster_status(self, cluster_name: str) -> ClusterStatusResponse:
|
|
212
|
+
data = await self._t.async_request("GET", f"/v1/db/cluster/{cluster_name}/status")
|
|
213
|
+
return ClusterStatusResponse.model_validate(data)
|