provably-sdk 0.1.0__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.
- provably/__init__.py +55 -0
- provably/_answer_model.py +78 -0
- provably/_api.py +407 -0
- provably/_auth_api.py +191 -0
- provably/_config.py +136 -0
- provably/_errors.py +162 -0
- provably/_http.py +247 -0
- provably/auth_service.py +149 -0
- provably/connection.py +43 -0
- provably/oauth_login.py +133 -0
- provably/py.typed +0 -0
- provably/service.py +674 -0
- provably_sdk-0.1.0.dist-info/METADATA +92 -0
- provably_sdk-0.1.0.dist-info/RECORD +16 -0
- provably_sdk-0.1.0.dist-info/WHEEL +4 -0
- provably_sdk-0.1.0.dist-info/licenses/LICENSE.md +101 -0
provably/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Python SDK for the Provably API."""
|
|
2
|
+
|
|
3
|
+
from provably._answer_model import AggregateAnswer, QueryAnswer, ResultsetAnswer, TabularData
|
|
4
|
+
from provably._auth_api import Organization, OrganizationType
|
|
5
|
+
from provably._config import (
|
|
6
|
+
MemoryTokenStore,
|
|
7
|
+
OAuthTokens,
|
|
8
|
+
ProvablyConfig,
|
|
9
|
+
ProvablyConfigError,
|
|
10
|
+
TokenStore,
|
|
11
|
+
configure,
|
|
12
|
+
)
|
|
13
|
+
from provably._errors import (
|
|
14
|
+
ProvablyAPIError,
|
|
15
|
+
ProvablyAuthError,
|
|
16
|
+
ProvablyConnectionError,
|
|
17
|
+
ProvablyDataError,
|
|
18
|
+
ProvablyError,
|
|
19
|
+
ProvablyNotFoundError,
|
|
20
|
+
ProvablyResourceAlreadyExistsError,
|
|
21
|
+
ProvablyUnauthorizedError,
|
|
22
|
+
)
|
|
23
|
+
from provably.auth_service import ProvablyAuthService, auth_service
|
|
24
|
+
from provably.connection import ConnectionInfo
|
|
25
|
+
from provably.oauth_login import browser_login
|
|
26
|
+
from provably.service import ProvablyService, service
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AggregateAnswer",
|
|
30
|
+
"ConnectionInfo",
|
|
31
|
+
"MemoryTokenStore",
|
|
32
|
+
"OAuthTokens",
|
|
33
|
+
"Organization",
|
|
34
|
+
"OrganizationType",
|
|
35
|
+
"ProvablyAPIError",
|
|
36
|
+
"ProvablyAuthError",
|
|
37
|
+
"ProvablyAuthService",
|
|
38
|
+
"ProvablyConfig",
|
|
39
|
+
"ProvablyConfigError",
|
|
40
|
+
"ProvablyConnectionError",
|
|
41
|
+
"ProvablyDataError",
|
|
42
|
+
"ProvablyError",
|
|
43
|
+
"ProvablyNotFoundError",
|
|
44
|
+
"ProvablyResourceAlreadyExistsError",
|
|
45
|
+
"ProvablyService",
|
|
46
|
+
"ProvablyUnauthorizedError",
|
|
47
|
+
"QueryAnswer",
|
|
48
|
+
"ResultsetAnswer",
|
|
49
|
+
"TabularData",
|
|
50
|
+
"TokenStore",
|
|
51
|
+
"auth_service",
|
|
52
|
+
"browser_login",
|
|
53
|
+
"configure",
|
|
54
|
+
"service",
|
|
55
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Data validation and serialization contracts for the Rust API layers."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
import msgspec
|
|
6
|
+
import structlog
|
|
7
|
+
from pydantic import BaseModel, Field, model_validator
|
|
8
|
+
|
|
9
|
+
_log = structlog.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
__all__ = ["QueryAnswer", "TabularData", "AggregateAnswer", "ResultsetAnswer"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _safe_deserialize(cell: Any) -> Any:
|
|
15
|
+
"""Helper to cleanly parse stringified JSON containers using high-speed C decoding."""
|
|
16
|
+
if isinstance(cell, str) and cell.strip().startswith(("{", "[")):
|
|
17
|
+
try:
|
|
18
|
+
return msgspec.json.decode(cell)
|
|
19
|
+
except Exception as e:
|
|
20
|
+
_log.debug("safe_deserialize_fallback", error=str(e))
|
|
21
|
+
return cell
|
|
22
|
+
return cell
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TabularData(BaseModel):
|
|
26
|
+
columns: list[dict[str, Any]] = Field(default_factory=list)
|
|
27
|
+
rows: list[list[Any]] = Field(default_factory=list)
|
|
28
|
+
|
|
29
|
+
def extract_value(self) -> Any:
|
|
30
|
+
"""Unpacks tabular results down to the core target payload."""
|
|
31
|
+
if not self.rows:
|
|
32
|
+
return {"columns": self.columns, "rows": self.rows}
|
|
33
|
+
|
|
34
|
+
row0 = self.rows[0]
|
|
35
|
+
# Extracted column names are normalized to lowercase
|
|
36
|
+
col_names = [str(c.get("name") or "").lower() for c in self.columns]
|
|
37
|
+
|
|
38
|
+
# Target 1: Extract intercept record payloads cleanly if present
|
|
39
|
+
if "raw_response" in col_names:
|
|
40
|
+
return _safe_deserialize(row0[col_names.index("raw_response")])
|
|
41
|
+
|
|
42
|
+
# Target 2: Single cell scalar
|
|
43
|
+
if len(col_names) == 1 and len(row0) == 1:
|
|
44
|
+
return _safe_deserialize(row0[0])
|
|
45
|
+
|
|
46
|
+
return {"columns": self.columns, "rows": self.rows}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AggregateAnswer(BaseModel):
|
|
50
|
+
type: Literal["aggregate"]
|
|
51
|
+
value: str
|
|
52
|
+
|
|
53
|
+
def extract_value(self) -> Any:
|
|
54
|
+
return _safe_deserialize(self.value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ResultsetAnswer(BaseModel):
|
|
58
|
+
type: Literal["resultset"]
|
|
59
|
+
value: TabularData
|
|
60
|
+
|
|
61
|
+
def extract_value(self) -> Any:
|
|
62
|
+
return self.value.extract_value()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class QueryAnswer(BaseModel):
|
|
66
|
+
"""Wrapper mapping the structure of QueryAnswer enum."""
|
|
67
|
+
|
|
68
|
+
root: AggregateAnswer | ResultsetAnswer = Field(..., discriminator="type")
|
|
69
|
+
|
|
70
|
+
@model_validator(mode="before")
|
|
71
|
+
@classmethod
|
|
72
|
+
def wrap_root(cls, data: Any) -> Any:
|
|
73
|
+
if isinstance(data, dict) and "root" not in data:
|
|
74
|
+
return {"root": data}
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
def flatten(self) -> Any:
|
|
78
|
+
return self.root.extract_value()
|
provably/_api.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Provably API — named methods for every endpoint.
|
|
2
|
+
|
|
3
|
+
:class:`ProvablyAPI` covers five resource groups:
|
|
4
|
+
- **Middlewares** — create the Provably middleware for an org
|
|
5
|
+
- **Databases / Schemas / Tables / Columns** — onboard and inspect the connected database
|
|
6
|
+
- **Collections** — manage query collections
|
|
7
|
+
- **Integrations** — register external integrations
|
|
8
|
+
- **Queries & Proofs** — run queries, generate proofs, poll status
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from provably._config import ProvablyConfig, ProvablyConfigError, current
|
|
16
|
+
from provably._http import get_http
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProvablyAPI:
|
|
20
|
+
"""Provably API endpoints."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: ProvablyConfig | None = None) -> None:
|
|
23
|
+
c = config or current().config()
|
|
24
|
+
self.org_id = c.org_id
|
|
25
|
+
self.app = c.app_url
|
|
26
|
+
|
|
27
|
+
def _generic_path(self) -> str:
|
|
28
|
+
return "/api/v1"
|
|
29
|
+
|
|
30
|
+
def _org_path(self) -> str:
|
|
31
|
+
if self.org_id is None:
|
|
32
|
+
raise ProvablyConfigError("No organisation configured; pass org_id to provably.configure()")
|
|
33
|
+
return f"/api/v1/organizations/{self.org_id}"
|
|
34
|
+
|
|
35
|
+
# ------------------------------------------------------------------
|
|
36
|
+
# Feedback
|
|
37
|
+
# ------------------------------------------------------------------
|
|
38
|
+
async def create_feedback(self, body: dict[str, Any], files: dict[str, Any] | None = None) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Send a feedback.
|
|
41
|
+
"""
|
|
42
|
+
path = "/api/v1/feedback"
|
|
43
|
+
|
|
44
|
+
await get_http().post_multipart(path, body, files=files)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
# Organizations
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
async def list_organizations(self) -> list[dict[str, Any]]:
|
|
52
|
+
"""
|
|
53
|
+
List all organizations accessible to the authenticated user (via API key).
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
list[dict[str, Any]]: List of organization objects (each contains at least ``id`` and ``name``).
|
|
57
|
+
"""
|
|
58
|
+
path = "/api/v1/organizations"
|
|
59
|
+
result: list[dict[str, Any]] = await get_http().get(path)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
# Sandboxes
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
async def create_sandbox(self, org_id: uuid.UUID, *, token: str | None = None) -> dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Create a hosted sandbox database for the given organisation.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
org_id: The ID of the organisation that owns the sandbox.
|
|
72
|
+
token: Optional JWT token for authentication (used during init).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict[str, Any]: Sandbox record with ``status`` and ``connection_uri``.
|
|
76
|
+
"""
|
|
77
|
+
path = "/api/v1/sandboxes"
|
|
78
|
+
result: dict[str, Any] = await get_http().post(path, {"org_id": str(org_id)}, token=token)
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
async def get_sandbox(self, *, token: str | None = None) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Retrieve the current sandbox for the authenticated user.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
token: Optional JWT token for authentication (used during init).
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
dict[str, Any]: Sandbox record with ``status`` and ``connection_uri``.
|
|
90
|
+
"""
|
|
91
|
+
path = "/api/v1/sandboxes"
|
|
92
|
+
result: dict[str, Any] = await get_http().get(path, token=token)
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
async def delete_sandbox(self, *, token: str | None = None) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Delete the sandbox for the authenticated user.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
token: Optional JWT token for authentication (used during init).
|
|
101
|
+
"""
|
|
102
|
+
path = "/api/v1/sandboxes"
|
|
103
|
+
await get_http().delete(path, token=token)
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
# Middlewares
|
|
107
|
+
# ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
async def create_middleware(self) -> dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Create the Provably middleware for the configured org.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
115
|
+
"""
|
|
116
|
+
path = f"{self._org_path()}/middlewares/provably"
|
|
117
|
+
|
|
118
|
+
result: dict[str, Any] = await get_http().post(path)
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
async def list_middlewares(self) -> list[dict[str, Any]]:
|
|
122
|
+
"""
|
|
123
|
+
List all middlewares.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Any: The raw JSON response from the API.
|
|
127
|
+
"""
|
|
128
|
+
path = f"{self._org_path()}/middlewares"
|
|
129
|
+
|
|
130
|
+
result: list[dict[str, Any]] = await get_http().get(path)
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Databases
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
async def create_database(self, middleware_id: uuid.UUID, body: dict[str, Any]) -> dict[str, Any]:
|
|
138
|
+
"""
|
|
139
|
+
Onboard a database to a middleware.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
middleware_id: The ID of the middleware to attach the database to.
|
|
143
|
+
body: The database connection payload.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
httpx.Response: The raw HTTP response from the API.
|
|
147
|
+
"""
|
|
148
|
+
path = f"{self._org_path()}/middlewares/{middleware_id}/databases"
|
|
149
|
+
|
|
150
|
+
result: dict[str, Any] = await get_http().post(path, body)
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
async def list_databases(self, middleware_id: uuid.UUID) -> list[dict[str, Any]]:
|
|
154
|
+
"""
|
|
155
|
+
List all databases attached to a middleware.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
middleware_id: The ID of the middleware to query.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Any: The raw JSON response from the API.
|
|
162
|
+
"""
|
|
163
|
+
path = f"{self._org_path()}/middlewares/{middleware_id}/databases"
|
|
164
|
+
|
|
165
|
+
result: list[dict[str, Any]] = await get_http().get(path)
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
# ------------------------------------------------------------------
|
|
169
|
+
# Schemas / Tables / Columns
|
|
170
|
+
# ------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
async def list_columns_from_database(
|
|
173
|
+
self,
|
|
174
|
+
middleware_id: uuid.UUID,
|
|
175
|
+
database_id: uuid.UUID,
|
|
176
|
+
schema_id: uuid.UUID,
|
|
177
|
+
table_id: uuid.UUID,
|
|
178
|
+
) -> list[dict[str, Any]]:
|
|
179
|
+
"""
|
|
180
|
+
List all columns in a table.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
middleware_id: The ID of the middleware owning the database.
|
|
184
|
+
database_id: The ID of the database containing the schema.
|
|
185
|
+
schema_id: The ID of the schema containing the table.
|
|
186
|
+
table_id: The ID of the table to inspect.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
list[dict[str, Any]]: The raw JSON response from the API.
|
|
190
|
+
"""
|
|
191
|
+
path = (
|
|
192
|
+
f"{self._org_path()}/middlewares/{middleware_id}"
|
|
193
|
+
f"/databases/{database_id}/schemas/{schema_id}/tables/{table_id}/columns"
|
|
194
|
+
)
|
|
195
|
+
result: list[dict[str, Any]] = await get_http().get(path)
|
|
196
|
+
return result
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
# Data
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
async def get_data(self) -> dict[str, Any]:
|
|
203
|
+
"""
|
|
204
|
+
Retrieve data for the configured org.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
208
|
+
"""
|
|
209
|
+
path = f"{self._org_path()}/data"
|
|
210
|
+
result: dict[str, Any] = await get_http().get(path)
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
# Collections
|
|
215
|
+
# ------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
async def list_collections(self) -> list[dict[str, Any]]:
|
|
218
|
+
"""
|
|
219
|
+
List all collections for the configured org.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
list[dict[str, Any]]: The raw JSON response from the API.
|
|
223
|
+
"""
|
|
224
|
+
path = f"{self._org_path()}/collections"
|
|
225
|
+
result: list[dict[str, Any]] = await get_http().get(path)
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
async def create_collection(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
229
|
+
"""
|
|
230
|
+
Create a new collection for the configured org.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
body: The collection creation payload.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
httpx.Response: The raw HTTP response from the API.
|
|
237
|
+
"""
|
|
238
|
+
path = f"{self._org_path()}/collections"
|
|
239
|
+
|
|
240
|
+
result: dict[str, Any] = await get_http().post(path, body)
|
|
241
|
+
return result
|
|
242
|
+
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
# Integrations
|
|
245
|
+
# ------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
async def ensure_integration(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
248
|
+
"""
|
|
249
|
+
Idempotent get-or-create an integration for the configured org.
|
|
250
|
+
|
|
251
|
+
Reuses an existing enabled integration with the same name linked to the
|
|
252
|
+
exact requested collections, returning its full key instead of minting a
|
|
253
|
+
duplicate.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
body: The integration registration payload.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
260
|
+
"""
|
|
261
|
+
path = f"{self._org_path()}/integrations/ensure"
|
|
262
|
+
|
|
263
|
+
result: dict[str, Any] = await get_http().post(path, body)
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
async def list_integrations(self, query: str | None = None) -> list[dict[str, Any]]:
|
|
267
|
+
"""
|
|
268
|
+
List all integrations for the configured org.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
query: Optional search string to filter integrations by name.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
list[dict[str, Any]]: The raw JSON response from the API.
|
|
275
|
+
"""
|
|
276
|
+
path = f"{self._org_path()}/integrations"
|
|
277
|
+
params = {"query": query} if query is not None else None
|
|
278
|
+
result: list[dict[str, Any]] = await get_http().get(path, params=params)
|
|
279
|
+
return result
|
|
280
|
+
|
|
281
|
+
# ------------------------------------------------------------------
|
|
282
|
+
# Preprocess
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
async def start_preprocess(self, middleware_id: uuid.UUID, table_id: uuid.UUID) -> dict[str, Any]:
|
|
286
|
+
"""
|
|
287
|
+
Start a preprocessing job for a table.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
middleware_id: The ID of the middleware owning the table.
|
|
291
|
+
table_id: The ID of the table to preprocess.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
295
|
+
"""
|
|
296
|
+
path = f"{self._org_path()}/middlewares/{middleware_id}/tables/{table_id}/preprocess"
|
|
297
|
+
|
|
298
|
+
result: dict[str, Any] = await get_http().post(path, {"force": True})
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
async def get_preprocess_status(self, middleware_id: uuid.UUID, table_id: uuid.UUID) -> dict[str, Any]:
|
|
302
|
+
"""
|
|
303
|
+
Get the preprocessing status for a table.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
middleware_id: The ID of the middleware owning the table.
|
|
307
|
+
table_id: The ID of the table to check.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
311
|
+
"""
|
|
312
|
+
path = f"{self._org_path()}/middlewares/{middleware_id}/tables/{table_id}/preprocess"
|
|
313
|
+
|
|
314
|
+
result: dict[str, Any] = await get_http().get(path)
|
|
315
|
+
return result
|
|
316
|
+
|
|
317
|
+
# ------------------------------------------------------------------
|
|
318
|
+
# Queries / Proofs
|
|
319
|
+
# ------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
async def run_query(self, middleware_id: uuid.UUID, collection_id: uuid.UUID, sql: str) -> dict[str, Any]:
|
|
322
|
+
"""
|
|
323
|
+
Run a SQL query through a middleware and request a proof.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
middleware_id: The ID of the middleware to execute the query against.
|
|
327
|
+
collection_id: The ID of the collection to associate the query with.
|
|
328
|
+
sql: The SQL query string to execute.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
332
|
+
"""
|
|
333
|
+
path = f"{self._org_path()}/middlewares/{middleware_id}/query"
|
|
334
|
+
|
|
335
|
+
result: dict[str, Any] = await get_http().post(
|
|
336
|
+
path, {"query": sql, "require_proof": True, "collection_id": str(collection_id)}
|
|
337
|
+
)
|
|
338
|
+
return result
|
|
339
|
+
|
|
340
|
+
async def get_query(self, query_id: uuid.UUID, *, api_key: str | None = None) -> dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Retrieve a query record by ID.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
query_id: The ID of the query to retrieve.
|
|
346
|
+
api_key: Optional API key override for this request.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
dict[str, Any]: The raw JSON response from the API.
|
|
350
|
+
"""
|
|
351
|
+
path = f"{self._org_path()}/queries/{query_id}"
|
|
352
|
+
|
|
353
|
+
result: dict[str, Any] = await get_http().get(path, api_key=api_key)
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
async def get_query_proof(self, proof_id: uuid.UUID, *, api_key: str | None = None) -> bytes:
|
|
357
|
+
"""
|
|
358
|
+
Download the raw proof data by proof ID.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
proof_id: The ID of the proof to retrieve.
|
|
362
|
+
api_key: Optional API key override for this request.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
bytes: The raw proof content.
|
|
366
|
+
"""
|
|
367
|
+
path = f"{self._generic_path()}/proof_requests/{proof_id}/download"
|
|
368
|
+
|
|
369
|
+
return await get_http().get_raw(path, api_key=api_key)
|
|
370
|
+
|
|
371
|
+
async def verify_proof(self, query_id: uuid.UUID, *, api_key: str | None = None) -> dict[str, Any]:
|
|
372
|
+
"""
|
|
373
|
+
Request verification for an existing query proof.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
query_id: The unique identifier of the query whose proof needs
|
|
377
|
+
verification.
|
|
378
|
+
api_key: Optional API key override for this request.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
dict[str, Any]: A response confirming the verification
|
|
382
|
+
task has been successfully initiated.
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
ProvablyAPIError: If the query does not exist or verification
|
|
386
|
+
cannot be initiated.
|
|
387
|
+
"""
|
|
388
|
+
path = f"{self._org_path()}/queries/{query_id}/verify"
|
|
389
|
+
|
|
390
|
+
result: dict[str, Any] = await get_http().post(path, {}, api_key=api_key)
|
|
391
|
+
return result
|
|
392
|
+
|
|
393
|
+
# ------------------------------------------------------------------
|
|
394
|
+
# URL helpers
|
|
395
|
+
# ------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
def query_record_url(self, query_record_id: uuid.UUID) -> str:
|
|
398
|
+
"""Provably Data Admin URL for a query record."""
|
|
399
|
+
if not query_record_id:
|
|
400
|
+
raise ValueError("query_record_id is required")
|
|
401
|
+
return f"{self.app}/org/{self.org_id}/query-record/{query_record_id}"
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@functools.lru_cache(maxsize=1)
|
|
405
|
+
def get_api() -> ProvablyAPI:
|
|
406
|
+
"""Return the shared :class:`ProvablyAPI`, constructed on first call."""
|
|
407
|
+
return ProvablyAPI()
|