memorysync 1.3.0__tar.gz → 1.5.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.
- {memorysync-1.3.0 → memorysync-1.5.0}/PKG-INFO +2 -2
- {memorysync-1.3.0 → memorysync-1.5.0}/pyproject.toml +44 -44
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/__init__.py +18 -9
- memorysync-1.5.0/src/memorysync/_version.py +1 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/connections.py +81 -18
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/control_plane.py +336 -143
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/control_plane_types.py +122 -57
- memorysync-1.3.0/src/memorysync/_version.py +0 -1
- {memorysync-1.3.0 → memorysync-1.5.0}/.gitignore +0 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/LICENSE +0 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/README.md +0 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/client.py +0 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/errors.py +0 -0
- {memorysync-1.3.0 → memorysync-1.5.0}/src/memorysync/types.py +0 -0
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "memorysync"
|
|
7
|
-
version = "1.
|
|
8
|
-
description = "Official Python client for the MemorySync API."
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
license = { text = "MIT" }
|
|
11
|
-
requires-python = ">=3.9"
|
|
12
|
-
authors = [{ name = "MemorySync" }]
|
|
13
|
-
keywords = ["memorysync", "memory", "sdk", "client"]
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Development Status :: 5 - Production/Stable",
|
|
16
|
-
"Intended Audience :: Developers",
|
|
17
|
-
"License :: OSI Approved :: MIT License",
|
|
18
|
-
"Programming Language :: Python :: 3",
|
|
19
|
-
"Programming Language :: Python :: 3.9",
|
|
20
|
-
"Programming Language :: Python :: 3.10",
|
|
21
|
-
"Programming Language :: Python :: 3.11",
|
|
22
|
-
"Programming Language :: Python :: 3.12",
|
|
23
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
-
"Typing :: Typed",
|
|
25
|
-
]
|
|
26
|
-
dependencies = [
|
|
27
|
-
"httpx>=0.25,<1.0",
|
|
28
|
-
"typing-extensions>=4.5; python_version < '3.11'",
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
[project.urls]
|
|
32
|
-
Homepage = "https://memorysync.io"
|
|
33
|
-
Documentation = "https://docs.memorysync.io"
|
|
34
|
-
|
|
35
|
-
[tool.hatch.build.targets.wheel]
|
|
36
|
-
packages = ["src/memorysync"]
|
|
37
|
-
|
|
38
|
-
[tool.hatch.build.targets.sdist]
|
|
39
|
-
include = [
|
|
40
|
-
"src/memorysync",
|
|
41
|
-
"README.md",
|
|
42
|
-
"LICENSE",
|
|
43
|
-
"pyproject.toml",
|
|
44
|
-
]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "memorysync"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
description = "Official Python client for the MemorySync API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "MemorySync" }]
|
|
13
|
+
keywords = ["memorysync", "memory", "sdk", "client"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.25,<1.0",
|
|
28
|
+
"typing-extensions>=4.5; python_version < '3.11'",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://memorysync.io"
|
|
33
|
+
Documentation = "https://docs.memorysync.io"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/memorysync"]
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.sdist]
|
|
39
|
+
include = [
|
|
40
|
+
"src/memorysync",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"pyproject.toml",
|
|
44
|
+
]
|
|
@@ -37,14 +37,20 @@ from .client import AsyncMemorySyncClient, MemorySyncClient
|
|
|
37
37
|
from .control_plane import AsyncControlPlaneClient, ControlPlaneClient
|
|
38
38
|
from .control_plane_types import (
|
|
39
39
|
ApiKeyTestResponse,
|
|
40
|
-
AuditEvent,
|
|
41
|
-
AuditEventsResponse,
|
|
42
40
|
BulkRevokeRequest,
|
|
43
41
|
BulkRevokeResponse,
|
|
44
42
|
BulkRevokeResult,
|
|
43
|
+
CreatedWebhook,
|
|
44
|
+
CreateExportRequest,
|
|
45
|
+
CreateExportResponse,
|
|
45
46
|
CreateOrganizationRequest,
|
|
47
|
+
CreateProjectRequest,
|
|
46
48
|
CreateWebhookRequest,
|
|
47
49
|
CurrentPlanResponse,
|
|
50
|
+
ExportDownloadUrl,
|
|
51
|
+
ExportFilters,
|
|
52
|
+
ExportJob,
|
|
53
|
+
ExportJobListResponse,
|
|
48
54
|
Integration,
|
|
49
55
|
JsonObject,
|
|
50
56
|
LoginRequest,
|
|
@@ -52,17 +58,18 @@ from .control_plane_types import (
|
|
|
52
58
|
LoginSession,
|
|
53
59
|
LoginTokens,
|
|
54
60
|
OrganizationMembership,
|
|
55
|
-
OrganizationSettings,
|
|
56
61
|
Project,
|
|
62
|
+
RenameProjectRequest,
|
|
57
63
|
ReplayWebhookDeliveriesRequest,
|
|
58
64
|
Session,
|
|
59
65
|
SessionsResponse,
|
|
60
|
-
TeamMember,
|
|
61
66
|
TestWebhookRequest,
|
|
62
67
|
UpdateWebhookRequest,
|
|
63
68
|
Webhook,
|
|
64
69
|
WebhookDeliveriesResponse,
|
|
65
70
|
WebhookDelivery,
|
|
71
|
+
WebhookEventTypesResponse,
|
|
72
|
+
WebhookHealth,
|
|
66
73
|
WebhookReplayResponse,
|
|
67
74
|
WebhooksResponse,
|
|
68
75
|
WebhookTestResponse,
|
|
@@ -108,12 +115,14 @@ __all__ += [
|
|
|
108
115
|
"ControlPlaneClient", "AsyncControlPlaneClient", "JsonObject",
|
|
109
116
|
"BulkRevokeRequest", "BulkRevokeResult", "BulkRevokeResponse",
|
|
110
117
|
"ApiKeyTestResponse", "LoginRequest", "LoginTokens", "LoginSession",
|
|
111
|
-
"LoginResponse", "CurrentPlanResponse", "
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
118
|
+
"LoginResponse", "CurrentPlanResponse", "Session", "SessionsResponse",
|
|
119
|
+
"Integration", "CreateOrganizationRequest", "OrganizationMembership",
|
|
120
|
+
"Project", "CreateProjectRequest",
|
|
121
|
+
"RenameProjectRequest", "CreateWebhookRequest", "CreatedWebhook",
|
|
115
122
|
"UpdateWebhookRequest", "TestWebhookRequest",
|
|
116
123
|
"ReplayWebhookDeliveriesRequest", "Webhook", "WebhooksResponse",
|
|
117
124
|
"WebhookTestResponse", "WebhookReplayResponse", "WebhookDelivery",
|
|
118
|
-
"WebhookDeliveriesResponse",
|
|
125
|
+
"WebhookDeliveriesResponse", "WebhookEventTypesResponse", "WebhookHealth",
|
|
126
|
+
"ExportFilters", "CreateExportRequest", "ExportJob",
|
|
127
|
+
"ExportJobListResponse", "CreateExportResponse", "ExportDownloadUrl",
|
|
119
128
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.5.0"
|
|
@@ -38,7 +38,7 @@ shapes (memories, history, feedback, ontology).
|
|
|
38
38
|
|
|
39
39
|
from __future__ import annotations
|
|
40
40
|
|
|
41
|
-
from typing import Any, Callable, Dict, Optional, Sequence
|
|
41
|
+
from typing import Any, Callable, Dict, Mapping, Optional, Sequence
|
|
42
42
|
from urllib.parse import quote
|
|
43
43
|
|
|
44
44
|
_V2 = "/api/v2/integrations"
|
|
@@ -58,6 +58,20 @@ def _seg(value: Any) -> str:
|
|
|
58
58
|
return quote(str(value), safe="")
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _as_item(value: Any, key: str) -> Dict[str, Any]:
|
|
62
|
+
"""Normalize one selection entry into the object the API expects.
|
|
63
|
+
|
|
64
|
+
Two of the approval endpoints take a list of objects rather than a list of
|
|
65
|
+
ids — Slack channels carry an optional name and type, S3 prefixes carry an
|
|
66
|
+
optional bucket and label. Callers almost always have just the id, so both
|
|
67
|
+
forms are accepted here and widened to the object form, rather than making
|
|
68
|
+
every caller write ``{"id": channel_id}`` and making the simple case ugly.
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(value, Mapping):
|
|
71
|
+
return dict(value)
|
|
72
|
+
return {key: value}
|
|
73
|
+
|
|
74
|
+
|
|
61
75
|
class _Namespace:
|
|
62
76
|
"""Holds the bound request function. Subclasses add methods."""
|
|
63
77
|
|
|
@@ -97,11 +111,22 @@ class SlackNamespace(_Namespace):
|
|
|
97
111
|
"GET", f"{_V2}/connections/{_seg(connection_id)}/slack/channels"
|
|
98
112
|
)
|
|
99
113
|
|
|
100
|
-
def add_channels(self, connection_id: str,
|
|
101
|
-
"""Select channels for syncing.
|
|
114
|
+
def add_channels(self, connection_id: str, channels: Sequence[Any]) -> Any:
|
|
115
|
+
"""Select channels for syncing.
|
|
116
|
+
|
|
117
|
+
Accepts bare channel ids, which is the common case::
|
|
118
|
+
|
|
119
|
+
client.connections.slack.add_channels("c1", ["C0123", "C0456"])
|
|
120
|
+
|
|
121
|
+
or dicts when you want to carry the name and type across so the server
|
|
122
|
+
does not have to look them up again::
|
|
123
|
+
|
|
124
|
+
add_channels("c1", [{"id": "C0123", "name": "support",
|
|
125
|
+
"is_private": False}])
|
|
126
|
+
"""
|
|
102
127
|
return self._request(
|
|
103
128
|
"POST", f"{_V2}/connections/{_seg(connection_id)}/slack/channels",
|
|
104
|
-
json={"
|
|
129
|
+
json={"channels": [_as_item(c, "id") for c in channels]},
|
|
105
130
|
)
|
|
106
131
|
|
|
107
132
|
def remove_channel(self, connection_id: str, channel_id: str) -> Any:
|
|
@@ -195,18 +220,47 @@ class S3Namespace(_Namespace):
|
|
|
195
220
|
"GET", f"{_V2}/connections/{_seg(connection_id)}/s3/prefixes"
|
|
196
221
|
)
|
|
197
222
|
|
|
198
|
-
def add_prefixes(self, connection_id: str, prefixes: Sequence[
|
|
199
|
-
"""Select prefixes for syncing.
|
|
223
|
+
def add_prefixes(self, connection_id: str, prefixes: Sequence[Any]) -> Any:
|
|
224
|
+
"""Select prefixes for syncing.
|
|
225
|
+
|
|
226
|
+
Accepts bare prefixes, or dicts carrying ``bucket`` and ``label``::
|
|
227
|
+
|
|
228
|
+
add_prefixes("c1", ["handbook/", "policies/"])
|
|
229
|
+
add_prefixes("c1", [{"prefix": "handbook/", "label": "Handbook"}])
|
|
230
|
+
|
|
231
|
+
An empty string means the bucket root. The bucket defaults to the one the
|
|
232
|
+
connection's credentials were validated against, and the API rejects any
|
|
233
|
+
other bucket rather than indexing one nobody proved access to.
|
|
234
|
+
"""
|
|
200
235
|
return self._request(
|
|
201
236
|
"POST", f"{_V2}/connections/{_seg(connection_id)}/s3/prefixes",
|
|
202
|
-
json={"prefixes":
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
def
|
|
206
|
-
|
|
237
|
+
json={"prefixes": [_as_item(p, "prefix") for p in prefixes]},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def remove_prefix(
|
|
241
|
+
self,
|
|
242
|
+
connection_id: str,
|
|
243
|
+
prefix: str = "",
|
|
244
|
+
*,
|
|
245
|
+
bucket: Optional[str] = None,
|
|
246
|
+
purge: bool = False,
|
|
247
|
+
) -> Any:
|
|
248
|
+
"""Revoke one prefix approval, optionally purging what it produced.
|
|
249
|
+
|
|
250
|
+
The prefix travels as a query parameter, not in the body and not as a path
|
|
251
|
+
segment: prefixes contain slashes, which a path segment cannot carry
|
|
252
|
+
unambiguously, and this endpoint reads no body at all.
|
|
253
|
+
|
|
254
|
+
Pass ``purge=True`` to also delete the memories already derived from the
|
|
255
|
+
prefix. The default leaves them in place, so revoking an approval does not
|
|
256
|
+
silently destroy knowledge.
|
|
257
|
+
"""
|
|
258
|
+
params: Dict[str, Any] = {"prefix": prefix, "purge": purge}
|
|
259
|
+
if bucket is not None:
|
|
260
|
+
params["bucket"] = bucket
|
|
207
261
|
return self._request(
|
|
208
262
|
"DELETE", f"{_V2}/connections/{_seg(connection_id)}/s3/prefixes",
|
|
209
|
-
|
|
263
|
+
params=params,
|
|
210
264
|
)
|
|
211
265
|
|
|
212
266
|
def exclusion_policy(self, connection_id: str) -> Any:
|
|
@@ -286,10 +340,15 @@ class GranolaNamespace(_Namespace):
|
|
|
286
340
|
)
|
|
287
341
|
|
|
288
342
|
def relink_identity(self, connection_id: str, **body: Any) -> Any:
|
|
289
|
-
"""
|
|
343
|
+
"""Re-run identity matching for this connection.
|
|
344
|
+
|
|
345
|
+
Takes no arguments: the route reads no body and re-matches the whole
|
|
346
|
+
roster. ``**body`` is kept only so a forward-compatible field can be
|
|
347
|
+
passed once the route grows one.
|
|
348
|
+
"""
|
|
290
349
|
return self._request(
|
|
291
350
|
"POST", f"{_V2}/connections/{_seg(connection_id)}/granola/identities/relink",
|
|
292
|
-
json=body,
|
|
351
|
+
json=body or None,
|
|
293
352
|
)
|
|
294
353
|
|
|
295
354
|
def settings(self, connection_id: str) -> Any:
|
|
@@ -321,7 +380,7 @@ class ConnectionOAuthNamespace(_Namespace):
|
|
|
321
380
|
platform back. Poll :meth:`status` to find out how it went — the callback
|
|
322
381
|
does not come to your backend.
|
|
323
382
|
"""
|
|
324
|
-
payload = {"
|
|
383
|
+
payload = {"provider_id": provider}
|
|
325
384
|
payload.update(body)
|
|
326
385
|
return self._request("POST", f"{_V2}/oauth/initiate", json=payload)
|
|
327
386
|
|
|
@@ -361,14 +420,18 @@ class ConnectionsNamespace(_Namespace):
|
|
|
361
420
|
return self._request("GET", f"{_V2}/connections/{_seg(connection_id)}")
|
|
362
421
|
|
|
363
422
|
def create_with_api_key(self, provider: str, api_key: str, **body: Any) -> Any:
|
|
364
|
-
"""Connect a provider that authenticates with an API key or bot token.
|
|
365
|
-
|
|
423
|
+
"""Connect a provider that authenticates with an API key or bot token.
|
|
424
|
+
|
|
425
|
+
The wire field is ``provider_id``; the argument is named ``provider``
|
|
426
|
+
because that is what the rest of this namespace calls it.
|
|
427
|
+
"""
|
|
428
|
+
payload: Dict[str, Any] = {"provider_id": provider, "api_key": api_key}
|
|
366
429
|
payload.update(body)
|
|
367
430
|
return self._request("POST", f"{_V2}/connections/api-key", json=payload)
|
|
368
431
|
|
|
369
432
|
def create_with_credentials(self, provider: str, credentials: Dict[str, Any], **body: Any) -> Any:
|
|
370
433
|
"""Connect a provider that needs a credential bundle, such as S3 keys."""
|
|
371
|
-
payload: Dict[str, Any] = {"
|
|
434
|
+
payload: Dict[str, Any] = {"provider_id": provider, "credentials": credentials}
|
|
372
435
|
payload.update(body)
|
|
373
436
|
return self._request("POST", f"{_V2}/connections/credentials", json=payload)
|
|
374
437
|
|
|
@@ -6,16 +6,17 @@ import re
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from email.utils import parsedate_to_datetime
|
|
8
8
|
from typing import Any, Dict, List, Mapping, Optional, Sequence, TypeVar, cast
|
|
9
|
-
from urllib.parse import urlparse
|
|
9
|
+
from urllib.parse import quote, urlparse
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
13
|
from ._version import __version__
|
|
14
14
|
from .control_plane_types import (
|
|
15
|
-
ApiKeyTestResponse,
|
|
16
|
-
CurrentPlanResponse,
|
|
17
|
-
|
|
18
|
-
Webhook, WebhookDeliveriesResponse,
|
|
15
|
+
ApiKeyTestResponse, BulkRevokeResponse, CreatedWebhook, CreateExportResponse,
|
|
16
|
+
CurrentPlanResponse, ExportDownloadUrl, ExportJob, ExportJobListResponse,
|
|
17
|
+
Integration, LoginResponse, OrganizationMembership, Project, Session,
|
|
18
|
+
SessionsResponse, Webhook, WebhookDeliveriesResponse, WebhookDelivery,
|
|
19
|
+
WebhookEventTypesResponse, WebhookHealth, WebhookReplayResponse,
|
|
19
20
|
WebhooksResponse, WebhookTestResponse,
|
|
20
21
|
)
|
|
21
22
|
from .errors import (
|
|
@@ -200,6 +201,93 @@ def _validate_webhook(name: str, url: str, events: Sequence[str]) -> None:
|
|
|
200
201
|
_string_sequence(events, "events")
|
|
201
202
|
|
|
202
203
|
|
|
204
|
+
def _project_name(value: str) -> str:
|
|
205
|
+
_required_text(value, "name")
|
|
206
|
+
if len(value) > 200:
|
|
207
|
+
raise ValidationError("name may contain at most 200 characters")
|
|
208
|
+
return value
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _webhook_retry_config(value: Optional[Mapping[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
212
|
+
if value is None:
|
|
213
|
+
return None
|
|
214
|
+
if not isinstance(value, Mapping):
|
|
215
|
+
raise ValidationError("retry_config must be a mapping")
|
|
216
|
+
allowed = {
|
|
217
|
+
"enabled", "max_retries", "initial_delay_seconds", "max_delay_seconds",
|
|
218
|
+
"backoff_multiplier", "retry_status_codes",
|
|
219
|
+
}
|
|
220
|
+
unknown = set(value) - allowed
|
|
221
|
+
if unknown:
|
|
222
|
+
raise ValidationError(f"retry_config contains unsupported fields: {sorted(unknown)}")
|
|
223
|
+
result = dict(value)
|
|
224
|
+
if "enabled" in result and not isinstance(result["enabled"], bool):
|
|
225
|
+
raise ValidationError("retry_config.enabled must be a boolean")
|
|
226
|
+
for key, minimum, maximum in (
|
|
227
|
+
("max_retries", 1, 10), ("initial_delay_seconds", 1, 60),
|
|
228
|
+
("max_delay_seconds", 60, 86400),
|
|
229
|
+
):
|
|
230
|
+
if key in result:
|
|
231
|
+
_bounded(result[key], f"retry_config.{key}", minimum, maximum)
|
|
232
|
+
if "backoff_multiplier" in result:
|
|
233
|
+
multiplier = result["backoff_multiplier"]
|
|
234
|
+
if (not isinstance(multiplier, (int, float)) or isinstance(multiplier, bool)
|
|
235
|
+
or not 1.0 <= multiplier <= 5.0):
|
|
236
|
+
raise ValidationError("retry_config.backoff_multiplier must be between 1 and 5")
|
|
237
|
+
if "retry_status_codes" in result:
|
|
238
|
+
_string_sequence(result["retry_status_codes"], "retry_config.retry_status_codes")
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _webhook_signature_config(value: Optional[Mapping[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
243
|
+
if value is None:
|
|
244
|
+
return None
|
|
245
|
+
if not isinstance(value, Mapping):
|
|
246
|
+
raise ValidationError("signature_config must be a mapping")
|
|
247
|
+
allowed = {"algorithm", "header_name", "timestamp_header", "tolerance_seconds"}
|
|
248
|
+
unknown = set(value) - allowed
|
|
249
|
+
if unknown:
|
|
250
|
+
raise ValidationError(f"signature_config contains unsupported fields: {sorted(unknown)}")
|
|
251
|
+
result = dict(value)
|
|
252
|
+
if "algorithm" in result and result["algorithm"] not in ("hmac-sha256", "hmac-sha512"):
|
|
253
|
+
raise ValidationError("signature_config.algorithm must be 'hmac-sha256' or 'hmac-sha512'")
|
|
254
|
+
for key in ("header_name", "timestamp_header"):
|
|
255
|
+
if key in result:
|
|
256
|
+
_required_text(result[key], f"signature_config.{key}")
|
|
257
|
+
if len(result[key]) > 64:
|
|
258
|
+
raise ValidationError(f"signature_config.{key} may contain at most 64 characters")
|
|
259
|
+
if "tolerance_seconds" in result:
|
|
260
|
+
_bounded(result["tolerance_seconds"], "signature_config.tolerance_seconds", 60, 3600)
|
|
261
|
+
return result
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _path_segment(value: str, name: str) -> str:
|
|
265
|
+
"""Validate and percent-encode an opaque identifier as one URL segment."""
|
|
266
|
+
return quote(_required_text(value, name), safe="")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _export_job_id(value: str) -> str:
|
|
270
|
+
_required_text(value, "job_id")
|
|
271
|
+
if not 8 <= len(value) <= 64:
|
|
272
|
+
raise ValidationError("job_id must contain between 8 and 64 characters")
|
|
273
|
+
return quote(value, safe="")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _export_filters(value: Optional[Mapping[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
277
|
+
if value is None:
|
|
278
|
+
return None
|
|
279
|
+
if not isinstance(value, Mapping):
|
|
280
|
+
raise ValidationError("filters must be a mapping")
|
|
281
|
+
allowed = {
|
|
282
|
+
"q", "user_id", "is_summary", "tier", "source", "time_range",
|
|
283
|
+
"date_from", "date_to", "include_soft_deleted",
|
|
284
|
+
}
|
|
285
|
+
unknown = set(value) - allowed
|
|
286
|
+
if unknown:
|
|
287
|
+
raise ValidationError(f"filters contains unsupported fields: {sorted(unknown)}")
|
|
288
|
+
return dict(value)
|
|
289
|
+
|
|
290
|
+
|
|
203
291
|
class ControlPlaneClient(_ControlPlaneBase):
|
|
204
292
|
"""Synchronous bearer client for dashboard/control-plane routes."""
|
|
205
293
|
|
|
@@ -262,59 +350,12 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
262
350
|
def get_current_plan(self) -> CurrentPlanResponse:
|
|
263
351
|
return cast(CurrentPlanResponse, self._request("GET", "/org/billing/current-plan"))
|
|
264
352
|
|
|
265
|
-
def list_team_members(self) -> List[TeamMember]:
|
|
266
|
-
return cast(List[TeamMember], self._request("GET", "/admin/team/members"))
|
|
267
|
-
|
|
268
|
-
def suspend_team_member(self, member_id: int) -> TeamMember:
|
|
269
|
-
path = f"/admin/team/members/{_positive_id(member_id, 'member_id')}"
|
|
270
|
-
return cast(TeamMember, self._request("PATCH", path, json={"status": "suspended"}))
|
|
271
|
-
|
|
272
|
-
def remove_team_member(self, member_id: int) -> None:
|
|
273
|
-
path = f"/admin/team/members/{_positive_id(member_id, 'member_id')}"
|
|
274
|
-
self._request("DELETE", path)
|
|
275
|
-
|
|
276
353
|
def list_sessions(self) -> SessionsResponse:
|
|
277
354
|
return cast(SessionsResponse, self._request("GET", "/auth/sessions"))
|
|
278
355
|
|
|
279
356
|
def revoke_session(self, session_id: int) -> None:
|
|
280
357
|
self._request("POST", f"/auth/sessions/{_positive_id(session_id, 'session_id')}/revoke")
|
|
281
358
|
|
|
282
|
-
|
|
283
|
-
def list_audit_events(
|
|
284
|
-
self, *, limit: Optional[int] = None, cursor: Optional[int] = None,
|
|
285
|
-
skip: Optional[int] = None, sort_direction: Optional[str] = None,
|
|
286
|
-
tenant_id: Optional[str] = None, actor: Optional[str] = None,
|
|
287
|
-
actor_email: Optional[str] = None, ip: Optional[str] = None,
|
|
288
|
-
action: Optional[str] = None, resource_type: Optional[str] = None,
|
|
289
|
-
resource_id: Optional[str] = None, severity: Optional[str] = None,
|
|
290
|
-
category: Optional[str] = None, start: Optional[str] = None,
|
|
291
|
-
end: Optional[str] = None, success: Optional[bool] = None,
|
|
292
|
-
source: Optional[str] = None, ingest_method: Optional[str] = None,
|
|
293
|
-
search: Optional[str] = None, include_stats: Optional[bool] = None,
|
|
294
|
-
) -> AuditEventsResponse:
|
|
295
|
-
if limit is not None:
|
|
296
|
-
_bounded(limit, "limit", 1, 200)
|
|
297
|
-
if cursor is not None and cursor < 0:
|
|
298
|
-
raise ValidationError("cursor must be non-negative")
|
|
299
|
-
if skip is not None and skip < 0:
|
|
300
|
-
raise ValidationError("skip must be non-negative")
|
|
301
|
-
if sort_direction not in (None, "asc", "desc"):
|
|
302
|
-
raise ValidationError("sort_direction must be 'asc' or 'desc'")
|
|
303
|
-
params = _body(limit=limit, cursor=cursor, skip=skip, sort=sort_direction,
|
|
304
|
-
tenant_id=tenant_id, actor=actor, actor_email=actor_email,
|
|
305
|
-
ip=ip, action=action, resource_type=resource_type,
|
|
306
|
-
resource_id=resource_id, severity=severity, category=category,
|
|
307
|
-
start=start, end=end, success=success, source=source,
|
|
308
|
-
ingest_method=ingest_method, search=search,
|
|
309
|
-
include_stats=include_stats)
|
|
310
|
-
raw = self._request("GET", "/admin/audit-logs", params=params)
|
|
311
|
-
return cast(AuditEventsResponse, {
|
|
312
|
-
"events": raw.get("logs", []),
|
|
313
|
-
"next_cursor": raw.get("next_cursor"),
|
|
314
|
-
"stats": raw.get("stats"),
|
|
315
|
-
"sort": raw.get("sort", sort_direction or "desc"),
|
|
316
|
-
})
|
|
317
|
-
|
|
318
359
|
def list_integrations(self, *, category: Optional[str] = None) -> List[Integration]:
|
|
319
360
|
if category is not None:
|
|
320
361
|
_required_text(category, "category")
|
|
@@ -329,41 +370,63 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
329
370
|
def list_organizations(self) -> List[OrganizationMembership]:
|
|
330
371
|
return cast(List[OrganizationMembership], self._request("GET", "/organizations"))
|
|
331
372
|
|
|
332
|
-
def list_organization_members(self) -> List[TeamMember]:
|
|
333
|
-
return self.list_team_members()
|
|
334
|
-
|
|
335
|
-
def get_organization_settings(self, *, tenant_id: Optional[str] = None) -> OrganizationSettings:
|
|
336
|
-
if tenant_id is not None:
|
|
337
|
-
_required_text(tenant_id, "tenant_id")
|
|
338
|
-
return cast(OrganizationSettings, self._request("GET", "/admin/tenant-settings", params=_body(tenant_id=tenant_id)))
|
|
339
|
-
|
|
340
373
|
def list_projects(self) -> List[Project]:
|
|
341
374
|
return cast(List[Project], self._request("GET", "/org/projects"))
|
|
342
375
|
|
|
376
|
+
def create_project(self, name: str) -> Project:
|
|
377
|
+
return cast(Project, self._request("POST", "/org/projects", json={"name": _project_name(name)}))
|
|
378
|
+
|
|
379
|
+
def rename_project(self, project_id: str, name: str) -> Project:
|
|
380
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}"
|
|
381
|
+
return cast(Project, self._request("PATCH", path, json={"name": _project_name(name)}))
|
|
382
|
+
|
|
383
|
+
def archive_project(self, project_id: str) -> Project:
|
|
384
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}/archive"
|
|
385
|
+
return cast(Project, self._request("POST", path))
|
|
386
|
+
|
|
387
|
+
def unarchive_project(self, project_id: str) -> Project:
|
|
388
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}/unarchive"
|
|
389
|
+
return cast(Project, self._request("POST", path))
|
|
390
|
+
|
|
391
|
+
def delete_project(self, project_id: str) -> None:
|
|
392
|
+
self._request("DELETE", f"/org/projects/{_path_segment(project_id, 'project_id')}")
|
|
393
|
+
|
|
343
394
|
def create_webhook(
|
|
344
395
|
self, name: str, url: str, events: Sequence[str], *,
|
|
345
|
-
description: Optional[str] = None, retry_config: Optional[
|
|
346
|
-
signature_config: Optional[
|
|
396
|
+
description: Optional[str] = None, retry_config: Optional[Mapping[str, Any]] = None,
|
|
397
|
+
signature_config: Optional[Mapping[str, Any]] = None,
|
|
347
398
|
project_id: Optional[str] = None,
|
|
348
|
-
) ->
|
|
399
|
+
) -> CreatedWebhook:
|
|
349
400
|
_validate_webhook(name, url, events)
|
|
350
401
|
if description is not None and len(description) > 500:
|
|
351
402
|
raise ValidationError("description may contain at most 500 characters")
|
|
352
|
-
data = _body(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
403
|
+
data = _body(
|
|
404
|
+
name=name, url=url, events=list(events), description=description,
|
|
405
|
+
retry_config=_webhook_retry_config(retry_config),
|
|
406
|
+
signature_config=_webhook_signature_config(signature_config),
|
|
407
|
+
project_id=project_id,
|
|
408
|
+
)
|
|
409
|
+
return cast(CreatedWebhook, self._request("POST", "/org/webhooks", json=data, project_id=_project_override(project_id)))
|
|
356
410
|
|
|
357
411
|
def list_webhooks(self, *, project_id: Optional[str] = None) -> WebhooksResponse:
|
|
358
412
|
return cast(WebhooksResponse, self._request("GET", "/org/webhooks", project_id=_project_override(project_id)))
|
|
359
413
|
|
|
414
|
+
def get_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
415
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
416
|
+
return cast(Webhook, self._request("GET", path, project_id=_project_override(project_id)))
|
|
417
|
+
|
|
418
|
+
def get_webhook_event_types(self, *, project_id: Optional[str] = None) -> WebhookEventTypesResponse:
|
|
419
|
+
return cast(WebhookEventTypesResponse, self._request("GET", "/org/webhooks/event-types", project_id=_project_override(project_id)))
|
|
420
|
+
|
|
421
|
+
def get_webhook_health(self, *, project_id: Optional[str] = None) -> WebhookHealth:
|
|
422
|
+
return cast(WebhookHealth, self._request("GET", "/org/webhooks/health", project_id=_project_override(project_id)))
|
|
360
423
|
|
|
361
424
|
def update_webhook(
|
|
362
425
|
self, endpoint_id: int, *, name: Optional[str] = None,
|
|
363
426
|
url: Optional[str] = None, description: Optional[str] = None,
|
|
364
427
|
events: Optional[Sequence[str]] = None,
|
|
365
|
-
retry_config: Optional[
|
|
366
|
-
signature_config: Optional[
|
|
428
|
+
retry_config: Optional[Mapping[str, Any]] = None,
|
|
429
|
+
signature_config: Optional[Mapping[str, Any]] = None,
|
|
367
430
|
project_id: Optional[str] = None,
|
|
368
431
|
) -> Webhook:
|
|
369
432
|
if name is not None:
|
|
@@ -376,9 +439,12 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
376
439
|
raise ValidationError("description may contain at most 500 characters")
|
|
377
440
|
if events is not None:
|
|
378
441
|
_string_sequence(events, "events")
|
|
379
|
-
data = _body(
|
|
380
|
-
|
|
381
|
-
|
|
442
|
+
data = _body(
|
|
443
|
+
name=name, url=url, description=description,
|
|
444
|
+
events=list(events) if events is not None else None,
|
|
445
|
+
retry_config=_webhook_retry_config(retry_config),
|
|
446
|
+
signature_config=_webhook_signature_config(signature_config),
|
|
447
|
+
)
|
|
382
448
|
if not data:
|
|
383
449
|
raise ValidationError("update_webhook requires at least one editable field")
|
|
384
450
|
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
@@ -388,6 +454,18 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
388
454
|
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
389
455
|
self._request("DELETE", path, project_id=_project_override(project_id))
|
|
390
456
|
|
|
457
|
+
def pause_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
458
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/pause"
|
|
459
|
+
return cast(Webhook, self._request("POST", path, project_id=_project_override(project_id)))
|
|
460
|
+
|
|
461
|
+
def resume_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
462
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/resume"
|
|
463
|
+
return cast(Webhook, self._request("POST", path, project_id=_project_override(project_id)))
|
|
464
|
+
|
|
465
|
+
def rotate_webhook_secret(self, endpoint_id: int, *, project_id: Optional[str] = None) -> CreatedWebhook:
|
|
466
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/rotate-secret"
|
|
467
|
+
return cast(CreatedWebhook, self._request("POST", path, project_id=_project_override(project_id)))
|
|
468
|
+
|
|
391
469
|
def test_webhook(
|
|
392
470
|
self, endpoint_id: int, *, event_type: str = "memory.created",
|
|
393
471
|
project_id: Optional[str] = None,
|
|
@@ -408,8 +486,14 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
408
486
|
data = {"since_minutes": since_minutes, "statuses": list(statuses), "limit": limit}
|
|
409
487
|
return cast(WebhookReplayResponse, self._request("POST", path, json=data, project_id=_project_override(project_id)))
|
|
410
488
|
|
|
489
|
+
def get_latest_webhook_delivery(
|
|
490
|
+
self, endpoint_id: int, *, project_id: Optional[str] = None,
|
|
491
|
+
) -> Optional[WebhookDelivery]:
|
|
492
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/deliveries/latest"
|
|
493
|
+
return cast(Optional[WebhookDelivery], self._request("GET", path, project_id=_project_override(project_id)))
|
|
494
|
+
|
|
411
495
|
def list_webhook_deliveries(
|
|
412
|
-
self, endpoint_id: int, *, page: int = 1, page_size: int =
|
|
496
|
+
self, endpoint_id: int, *, page: int = 1, page_size: int = 20,
|
|
413
497
|
status: Optional[str] = None, project_id: Optional[str] = None,
|
|
414
498
|
) -> WebhookDeliveriesResponse:
|
|
415
499
|
_positive_id(page, "page")
|
|
@@ -420,6 +504,60 @@ class ControlPlaneClient(_ControlPlaneBase):
|
|
|
420
504
|
params = _body(page=page, page_size=page_size, status_filter=status)
|
|
421
505
|
return cast(WebhookDeliveriesResponse, self._request("GET", path, params=params, project_id=_project_override(project_id)))
|
|
422
506
|
|
|
507
|
+
def list_recent_webhook_deliveries(
|
|
508
|
+
self, *, page: int = 1, page_size: int = 20,
|
|
509
|
+
endpoint_id: Optional[int] = None, status: Optional[str] = None,
|
|
510
|
+
project_id: Optional[str] = None,
|
|
511
|
+
) -> WebhookDeliveriesResponse:
|
|
512
|
+
_positive_id(page, "page")
|
|
513
|
+
_bounded(page_size, "page_size", 1, 100)
|
|
514
|
+
if endpoint_id is not None:
|
|
515
|
+
_positive_id(endpoint_id, "endpoint_id")
|
|
516
|
+
if status is not None:
|
|
517
|
+
_required_text(status, "status")
|
|
518
|
+
params = _body(page=page, page_size=page_size, endpoint_id=endpoint_id, status_filter=status)
|
|
519
|
+
return cast(WebhookDeliveriesResponse, self._request("GET", "/org/webhooks/deliveries/recent", params=params, project_id=_project_override(project_id)))
|
|
520
|
+
|
|
521
|
+
def get_webhook_delivery(self, delivery_id: int, *, project_id: Optional[str] = None) -> WebhookDelivery:
|
|
522
|
+
path = f"/org/webhooks/deliveries/{_positive_id(delivery_id, 'delivery_id')}"
|
|
523
|
+
return cast(WebhookDelivery, self._request("GET", path, project_id=_project_override(project_id)))
|
|
524
|
+
|
|
525
|
+
def retry_webhook_delivery(self, delivery_id: int, *, project_id: Optional[str] = None) -> WebhookDelivery:
|
|
526
|
+
path = f"/org/webhooks/deliveries/{_positive_id(delivery_id, 'delivery_id')}/retry"
|
|
527
|
+
return cast(WebhookDelivery, self._request("POST", path, project_id=_project_override(project_id)))
|
|
528
|
+
|
|
529
|
+
def create_export(
|
|
530
|
+
self, *, format: str = "csv", scope: str = "filtered",
|
|
531
|
+
filters: Optional[Mapping[str, Any]] = None,
|
|
532
|
+
project_id: Optional[str] = None,
|
|
533
|
+
) -> CreateExportResponse:
|
|
534
|
+
if format not in ("csv", "jsonl"):
|
|
535
|
+
raise ValidationError("format must be 'csv' or 'jsonl'")
|
|
536
|
+
if scope not in ("filtered", "all", "date_range"):
|
|
537
|
+
raise ValidationError("scope must be 'filtered', 'all', or 'date_range'")
|
|
538
|
+
data = _body(format=format, scope=scope, filters=_export_filters(filters))
|
|
539
|
+
return cast(CreateExportResponse, self._request("POST", "/exports", json=data, project_id=_project_override(project_id)))
|
|
540
|
+
|
|
541
|
+
def list_exports(self, *, limit: int = 20, project_id: Optional[str] = None) -> ExportJobListResponse:
|
|
542
|
+
_bounded(limit, "limit", 1, 100)
|
|
543
|
+
return cast(ExportJobListResponse, self._request("GET", "/exports", params={"limit": limit}, project_id=_project_override(project_id)))
|
|
544
|
+
|
|
545
|
+
def get_export(self, job_id: str, *, project_id: Optional[str] = None) -> ExportJob:
|
|
546
|
+
path = f"/exports/{_export_job_id(job_id)}"
|
|
547
|
+
return cast(ExportJob, self._request("GET", path, project_id=_project_override(project_id)))
|
|
548
|
+
|
|
549
|
+
def cancel_export(self, job_id: str, *, project_id: Optional[str] = None) -> ExportJob:
|
|
550
|
+
path = f"/exports/{_export_job_id(job_id)}/cancel"
|
|
551
|
+
return cast(ExportJob, self._request("POST", path, project_id=_project_override(project_id)))
|
|
552
|
+
|
|
553
|
+
def retry_export(self, job_id: str, *, project_id: Optional[str] = None) -> CreateExportResponse:
|
|
554
|
+
path = f"/exports/{_export_job_id(job_id)}/retry"
|
|
555
|
+
return cast(CreateExportResponse, self._request("POST", path, project_id=_project_override(project_id)))
|
|
556
|
+
|
|
557
|
+
def get_export_download_url(self, job_id: str, *, project_id: Optional[str] = None) -> ExportDownloadUrl:
|
|
558
|
+
path = f"/exports/{_export_job_id(job_id)}/download-url"
|
|
559
|
+
return cast(ExportDownloadUrl, self._request("GET", path, project_id=_project_override(project_id)))
|
|
560
|
+
|
|
423
561
|
|
|
424
562
|
class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
425
563
|
"""Async-native bearer client for dashboard/control-plane routes."""
|
|
@@ -482,58 +620,12 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
|
482
620
|
async def get_current_plan(self) -> CurrentPlanResponse:
|
|
483
621
|
return cast(CurrentPlanResponse, await self._request("GET", "/org/billing/current-plan"))
|
|
484
622
|
|
|
485
|
-
async def list_team_members(self) -> List[TeamMember]:
|
|
486
|
-
return cast(List[TeamMember], await self._request("GET", "/admin/team/members"))
|
|
487
|
-
|
|
488
|
-
async def suspend_team_member(self, member_id: int) -> TeamMember:
|
|
489
|
-
path = f"/admin/team/members/{_positive_id(member_id, 'member_id')}"
|
|
490
|
-
return cast(TeamMember, await self._request("PATCH", path, json={"status": "suspended"}))
|
|
491
|
-
|
|
492
|
-
async def remove_team_member(self, member_id: int) -> None:
|
|
493
|
-
await self._request("DELETE", f"/admin/team/members/{_positive_id(member_id, 'member_id')}")
|
|
494
|
-
|
|
495
623
|
async def list_sessions(self) -> SessionsResponse:
|
|
496
624
|
return cast(SessionsResponse, await self._request("GET", "/auth/sessions"))
|
|
497
625
|
|
|
498
626
|
async def revoke_session(self, session_id: int) -> None:
|
|
499
627
|
await self._request("POST", f"/auth/sessions/{_positive_id(session_id, 'session_id')}/revoke")
|
|
500
628
|
|
|
501
|
-
|
|
502
|
-
async def list_audit_events(
|
|
503
|
-
self, *, limit: Optional[int] = None, cursor: Optional[int] = None,
|
|
504
|
-
skip: Optional[int] = None, sort_direction: Optional[str] = None,
|
|
505
|
-
tenant_id: Optional[str] = None, actor: Optional[str] = None,
|
|
506
|
-
actor_email: Optional[str] = None, ip: Optional[str] = None,
|
|
507
|
-
action: Optional[str] = None, resource_type: Optional[str] = None,
|
|
508
|
-
resource_id: Optional[str] = None, severity: Optional[str] = None,
|
|
509
|
-
category: Optional[str] = None, start: Optional[str] = None,
|
|
510
|
-
end: Optional[str] = None, success: Optional[bool] = None,
|
|
511
|
-
source: Optional[str] = None, ingest_method: Optional[str] = None,
|
|
512
|
-
search: Optional[str] = None, include_stats: Optional[bool] = None,
|
|
513
|
-
) -> AuditEventsResponse:
|
|
514
|
-
if limit is not None:
|
|
515
|
-
_bounded(limit, "limit", 1, 200)
|
|
516
|
-
if cursor is not None and cursor < 0:
|
|
517
|
-
raise ValidationError("cursor must be non-negative")
|
|
518
|
-
if skip is not None and skip < 0:
|
|
519
|
-
raise ValidationError("skip must be non-negative")
|
|
520
|
-
if sort_direction not in (None, "asc", "desc"):
|
|
521
|
-
raise ValidationError("sort_direction must be 'asc' or 'desc'")
|
|
522
|
-
params = _body(limit=limit, cursor=cursor, skip=skip, sort=sort_direction,
|
|
523
|
-
tenant_id=tenant_id, actor=actor, actor_email=actor_email,
|
|
524
|
-
ip=ip, action=action, resource_type=resource_type,
|
|
525
|
-
resource_id=resource_id, severity=severity, category=category,
|
|
526
|
-
start=start, end=end, success=success, source=source,
|
|
527
|
-
ingest_method=ingest_method, search=search,
|
|
528
|
-
include_stats=include_stats)
|
|
529
|
-
raw = await self._request("GET", "/admin/audit-logs", params=params)
|
|
530
|
-
return cast(AuditEventsResponse, {
|
|
531
|
-
"events": raw.get("logs", []),
|
|
532
|
-
"next_cursor": raw.get("next_cursor"),
|
|
533
|
-
"stats": raw.get("stats"),
|
|
534
|
-
"sort": raw.get("sort", sort_direction or "desc"),
|
|
535
|
-
})
|
|
536
|
-
|
|
537
629
|
async def list_integrations(self, *, category: Optional[str] = None) -> List[Integration]:
|
|
538
630
|
if category is not None:
|
|
539
631
|
_required_text(category, "category")
|
|
@@ -550,43 +642,67 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
|
550
642
|
async def list_organizations(self) -> List[OrganizationMembership]:
|
|
551
643
|
return cast(List[OrganizationMembership], await self._request("GET", "/organizations"))
|
|
552
644
|
|
|
553
|
-
async def list_organization_members(self) -> List[TeamMember]:
|
|
554
|
-
return await self.list_team_members()
|
|
555
|
-
|
|
556
|
-
async def get_organization_settings(self, *, tenant_id: Optional[str] = None) -> OrganizationSettings:
|
|
557
|
-
if tenant_id is not None:
|
|
558
|
-
_required_text(tenant_id, "tenant_id")
|
|
559
|
-
raw = await self._request("GET", "/admin/tenant-settings", params=_body(tenant_id=tenant_id))
|
|
560
|
-
return cast(OrganizationSettings, raw)
|
|
561
|
-
|
|
562
645
|
async def list_projects(self) -> List[Project]:
|
|
563
646
|
return cast(List[Project], await self._request("GET", "/org/projects"))
|
|
564
647
|
|
|
648
|
+
async def create_project(self, name: str) -> Project:
|
|
649
|
+
raw = await self._request("POST", "/org/projects", json={"name": _project_name(name)})
|
|
650
|
+
return cast(Project, raw)
|
|
651
|
+
|
|
652
|
+
async def rename_project(self, project_id: str, name: str) -> Project:
|
|
653
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}"
|
|
654
|
+
return cast(Project, await self._request("PATCH", path, json={"name": _project_name(name)}))
|
|
655
|
+
|
|
656
|
+
async def archive_project(self, project_id: str) -> Project:
|
|
657
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}/archive"
|
|
658
|
+
return cast(Project, await self._request("POST", path))
|
|
659
|
+
|
|
660
|
+
async def unarchive_project(self, project_id: str) -> Project:
|
|
661
|
+
path = f"/org/projects/{_path_segment(project_id, 'project_id')}/unarchive"
|
|
662
|
+
return cast(Project, await self._request("POST", path))
|
|
663
|
+
|
|
664
|
+
async def delete_project(self, project_id: str) -> None:
|
|
665
|
+
await self._request("DELETE", f"/org/projects/{_path_segment(project_id, 'project_id')}")
|
|
666
|
+
|
|
565
667
|
async def create_webhook(
|
|
566
668
|
self, name: str, url: str, events: Sequence[str], *,
|
|
567
|
-
description: Optional[str] = None, retry_config: Optional[
|
|
568
|
-
signature_config: Optional[
|
|
669
|
+
description: Optional[str] = None, retry_config: Optional[Mapping[str, Any]] = None,
|
|
670
|
+
signature_config: Optional[Mapping[str, Any]] = None,
|
|
569
671
|
project_id: Optional[str] = None,
|
|
570
|
-
) ->
|
|
672
|
+
) -> CreatedWebhook:
|
|
571
673
|
_validate_webhook(name, url, events)
|
|
572
674
|
if description is not None and len(description) > 500:
|
|
573
675
|
raise ValidationError("description may contain at most 500 characters")
|
|
574
|
-
data = _body(
|
|
575
|
-
|
|
576
|
-
|
|
676
|
+
data = _body(
|
|
677
|
+
name=name, url=url, events=list(events), description=description,
|
|
678
|
+
retry_config=_webhook_retry_config(retry_config),
|
|
679
|
+
signature_config=_webhook_signature_config(signature_config),
|
|
680
|
+
project_id=project_id,
|
|
681
|
+
)
|
|
577
682
|
raw = await self._request("POST", "/org/webhooks", json=data, project_id=_project_override(project_id))
|
|
578
|
-
return cast(
|
|
683
|
+
return cast(CreatedWebhook, raw)
|
|
579
684
|
|
|
580
685
|
async def list_webhooks(self, *, project_id: Optional[str] = None) -> WebhooksResponse:
|
|
581
686
|
return cast(WebhooksResponse, await self._request("GET", "/org/webhooks", project_id=_project_override(project_id)))
|
|
582
687
|
|
|
688
|
+
async def get_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
689
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
690
|
+
return cast(Webhook, await self._request("GET", path, project_id=_project_override(project_id)))
|
|
691
|
+
|
|
692
|
+
async def get_webhook_event_types(self, *, project_id: Optional[str] = None) -> WebhookEventTypesResponse:
|
|
693
|
+
raw = await self._request("GET", "/org/webhooks/event-types", project_id=_project_override(project_id))
|
|
694
|
+
return cast(WebhookEventTypesResponse, raw)
|
|
695
|
+
|
|
696
|
+
async def get_webhook_health(self, *, project_id: Optional[str] = None) -> WebhookHealth:
|
|
697
|
+
raw = await self._request("GET", "/org/webhooks/health", project_id=_project_override(project_id))
|
|
698
|
+
return cast(WebhookHealth, raw)
|
|
583
699
|
|
|
584
700
|
async def update_webhook(
|
|
585
701
|
self, endpoint_id: int, *, name: Optional[str] = None,
|
|
586
702
|
url: Optional[str] = None, description: Optional[str] = None,
|
|
587
703
|
events: Optional[Sequence[str]] = None,
|
|
588
|
-
retry_config: Optional[
|
|
589
|
-
signature_config: Optional[
|
|
704
|
+
retry_config: Optional[Mapping[str, Any]] = None,
|
|
705
|
+
signature_config: Optional[Mapping[str, Any]] = None,
|
|
590
706
|
project_id: Optional[str] = None,
|
|
591
707
|
) -> Webhook:
|
|
592
708
|
if name is not None:
|
|
@@ -599,19 +715,33 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
|
599
715
|
raise ValidationError("description may contain at most 500 characters")
|
|
600
716
|
if events is not None:
|
|
601
717
|
_string_sequence(events, "events")
|
|
602
|
-
data = _body(
|
|
603
|
-
|
|
604
|
-
|
|
718
|
+
data = _body(
|
|
719
|
+
name=name, url=url, description=description,
|
|
720
|
+
events=list(events) if events is not None else None,
|
|
721
|
+
retry_config=_webhook_retry_config(retry_config),
|
|
722
|
+
signature_config=_webhook_signature_config(signature_config),
|
|
723
|
+
)
|
|
605
724
|
if not data:
|
|
606
725
|
raise ValidationError("update_webhook requires at least one editable field")
|
|
607
726
|
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
608
|
-
|
|
609
|
-
return cast(Webhook, raw)
|
|
727
|
+
return cast(Webhook, await self._request("PATCH", path, json=data, project_id=_project_override(project_id)))
|
|
610
728
|
|
|
611
729
|
async def delete_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> None:
|
|
612
730
|
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}"
|
|
613
731
|
await self._request("DELETE", path, project_id=_project_override(project_id))
|
|
614
732
|
|
|
733
|
+
async def pause_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
734
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/pause"
|
|
735
|
+
return cast(Webhook, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
736
|
+
|
|
737
|
+
async def resume_webhook(self, endpoint_id: int, *, project_id: Optional[str] = None) -> Webhook:
|
|
738
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/resume"
|
|
739
|
+
return cast(Webhook, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
740
|
+
|
|
741
|
+
async def rotate_webhook_secret(self, endpoint_id: int, *, project_id: Optional[str] = None) -> CreatedWebhook:
|
|
742
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/rotate-secret"
|
|
743
|
+
return cast(CreatedWebhook, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
744
|
+
|
|
615
745
|
async def test_webhook(
|
|
616
746
|
self, endpoint_id: int, *, event_type: str = "memory.created",
|
|
617
747
|
project_id: Optional[str] = None,
|
|
@@ -634,8 +764,15 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
|
634
764
|
raw = await self._request("POST", path, json=data, project_id=_project_override(project_id))
|
|
635
765
|
return cast(WebhookReplayResponse, raw)
|
|
636
766
|
|
|
767
|
+
async def get_latest_webhook_delivery(
|
|
768
|
+
self, endpoint_id: int, *, project_id: Optional[str] = None,
|
|
769
|
+
) -> Optional[WebhookDelivery]:
|
|
770
|
+
path = f"/org/webhooks/{_positive_id(endpoint_id, 'endpoint_id')}/deliveries/latest"
|
|
771
|
+
raw = await self._request("GET", path, project_id=_project_override(project_id))
|
|
772
|
+
return cast(Optional[WebhookDelivery], raw)
|
|
773
|
+
|
|
637
774
|
async def list_webhook_deliveries(
|
|
638
|
-
self, endpoint_id: int, *, page: int = 1, page_size: int =
|
|
775
|
+
self, endpoint_id: int, *, page: int = 1, page_size: int = 20,
|
|
639
776
|
status: Optional[str] = None, project_id: Optional[str] = None,
|
|
640
777
|
) -> WebhookDeliveriesResponse:
|
|
641
778
|
_positive_id(page, "page")
|
|
@@ -646,3 +783,59 @@ class AsyncControlPlaneClient(_ControlPlaneBase):
|
|
|
646
783
|
params = _body(page=page, page_size=page_size, status_filter=status)
|
|
647
784
|
raw = await self._request("GET", path, params=params, project_id=_project_override(project_id))
|
|
648
785
|
return cast(WebhookDeliveriesResponse, raw)
|
|
786
|
+
|
|
787
|
+
async def list_recent_webhook_deliveries(
|
|
788
|
+
self, *, page: int = 1, page_size: int = 20,
|
|
789
|
+
endpoint_id: Optional[int] = None, status: Optional[str] = None,
|
|
790
|
+
project_id: Optional[str] = None,
|
|
791
|
+
) -> WebhookDeliveriesResponse:
|
|
792
|
+
_positive_id(page, "page")
|
|
793
|
+
_bounded(page_size, "page_size", 1, 100)
|
|
794
|
+
if endpoint_id is not None:
|
|
795
|
+
_positive_id(endpoint_id, "endpoint_id")
|
|
796
|
+
if status is not None:
|
|
797
|
+
_required_text(status, "status")
|
|
798
|
+
params = _body(page=page, page_size=page_size, endpoint_id=endpoint_id, status_filter=status)
|
|
799
|
+
raw = await self._request("GET", "/org/webhooks/deliveries/recent", params=params, project_id=_project_override(project_id))
|
|
800
|
+
return cast(WebhookDeliveriesResponse, raw)
|
|
801
|
+
|
|
802
|
+
async def get_webhook_delivery(self, delivery_id: int, *, project_id: Optional[str] = None) -> WebhookDelivery:
|
|
803
|
+
path = f"/org/webhooks/deliveries/{_positive_id(delivery_id, 'delivery_id')}"
|
|
804
|
+
return cast(WebhookDelivery, await self._request("GET", path, project_id=_project_override(project_id)))
|
|
805
|
+
|
|
806
|
+
async def retry_webhook_delivery(self, delivery_id: int, *, project_id: Optional[str] = None) -> WebhookDelivery:
|
|
807
|
+
path = f"/org/webhooks/deliveries/{_positive_id(delivery_id, 'delivery_id')}/retry"
|
|
808
|
+
return cast(WebhookDelivery, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
809
|
+
|
|
810
|
+
async def create_export(
|
|
811
|
+
self, *, format: str = "csv", scope: str = "filtered",
|
|
812
|
+
filters: Optional[Mapping[str, Any]] = None,
|
|
813
|
+
project_id: Optional[str] = None,
|
|
814
|
+
) -> CreateExportResponse:
|
|
815
|
+
if format not in ("csv", "jsonl"):
|
|
816
|
+
raise ValidationError("format must be 'csv' or 'jsonl'")
|
|
817
|
+
if scope not in ("filtered", "all", "date_range"):
|
|
818
|
+
raise ValidationError("scope must be 'filtered', 'all', or 'date_range'")
|
|
819
|
+
data = _body(format=format, scope=scope, filters=_export_filters(filters))
|
|
820
|
+
raw = await self._request("POST", "/exports", json=data, project_id=_project_override(project_id))
|
|
821
|
+
return cast(CreateExportResponse, raw)
|
|
822
|
+
|
|
823
|
+
async def list_exports(self, *, limit: int = 20, project_id: Optional[str] = None) -> ExportJobListResponse:
|
|
824
|
+
_bounded(limit, "limit", 1, 100)
|
|
825
|
+
raw = await self._request("GET", "/exports", params={"limit": limit}, project_id=_project_override(project_id))
|
|
826
|
+
return cast(ExportJobListResponse, raw)
|
|
827
|
+
|
|
828
|
+
async def get_export(self, job_id: str, *, project_id: Optional[str] = None) -> ExportJob:
|
|
829
|
+
return cast(ExportJob, await self._request("GET", f"/exports/{_export_job_id(job_id)}", project_id=_project_override(project_id)))
|
|
830
|
+
|
|
831
|
+
async def cancel_export(self, job_id: str, *, project_id: Optional[str] = None) -> ExportJob:
|
|
832
|
+
path = f"/exports/{_export_job_id(job_id)}/cancel"
|
|
833
|
+
return cast(ExportJob, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
834
|
+
|
|
835
|
+
async def retry_export(self, job_id: str, *, project_id: Optional[str] = None) -> CreateExportResponse:
|
|
836
|
+
path = f"/exports/{_export_job_id(job_id)}/retry"
|
|
837
|
+
return cast(CreateExportResponse, await self._request("POST", path, project_id=_project_override(project_id)))
|
|
838
|
+
|
|
839
|
+
async def get_export_download_url(self, job_id: str, *, project_id: Optional[str] = None) -> ExportDownloadUrl:
|
|
840
|
+
path = f"/exports/{_export_job_id(job_id)}/download-url"
|
|
841
|
+
return cast(ExportDownloadUrl, await self._request("GET", path, project_id=_project_override(project_id)))
|
|
@@ -4,6 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any, Dict, List, Optional, TypedDict
|
|
6
6
|
|
|
7
|
+
try:
|
|
8
|
+
from typing import NotRequired
|
|
9
|
+
except ImportError: # Python 3.9 and 3.10
|
|
10
|
+
from typing_extensions import NotRequired
|
|
11
|
+
|
|
7
12
|
JsonObject = Dict[str, Any]
|
|
8
13
|
|
|
9
14
|
|
|
@@ -62,21 +67,6 @@ class CurrentPlanResponse(TypedDict, total=False):
|
|
|
62
67
|
cancel_at_period_end: bool
|
|
63
68
|
|
|
64
69
|
|
|
65
|
-
class TeamMember(TypedDict, total=False):
|
|
66
|
-
id: str
|
|
67
|
-
name: str
|
|
68
|
-
email: str
|
|
69
|
-
role: str
|
|
70
|
-
status: str
|
|
71
|
-
last_active: Optional[str]
|
|
72
|
-
mfa_enabled: bool
|
|
73
|
-
auth_method: str
|
|
74
|
-
login_provider: str
|
|
75
|
-
devices: int
|
|
76
|
-
sessions: int
|
|
77
|
-
is_scim_managed: bool
|
|
78
|
-
|
|
79
|
-
|
|
80
70
|
class Session(TypedDict, total=False):
|
|
81
71
|
id: int
|
|
82
72
|
is_current: bool
|
|
@@ -93,26 +83,7 @@ class Session(TypedDict, total=False):
|
|
|
93
83
|
|
|
94
84
|
class SessionsResponse(TypedDict):
|
|
95
85
|
sessions: List[Session]
|
|
96
|
-
current_session_id: int
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class AuditEvent(TypedDict, total=False):
|
|
100
|
-
id: int
|
|
101
|
-
event_id: str
|
|
102
|
-
timestamp: str
|
|
103
|
-
category: str
|
|
104
|
-
actor: JsonObject
|
|
105
|
-
action: str
|
|
106
|
-
resource: JsonObject
|
|
107
|
-
severity: str
|
|
108
|
-
success: bool
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class AuditEventsResponse(TypedDict, total=False):
|
|
112
|
-
events: List[AuditEvent]
|
|
113
|
-
next_cursor: Optional[int]
|
|
114
|
-
stats: Optional[JsonObject]
|
|
115
|
-
sort: str
|
|
86
|
+
current_session_id: Optional[int]
|
|
116
87
|
|
|
117
88
|
|
|
118
89
|
class Integration(TypedDict, total=False):
|
|
@@ -133,26 +104,6 @@ class OrganizationMembership(TypedDict):
|
|
|
133
104
|
role: str
|
|
134
105
|
|
|
135
106
|
|
|
136
|
-
class OrganizationSettings(TypedDict, total=False):
|
|
137
|
-
tenant_id: str
|
|
138
|
-
tenant_name: str
|
|
139
|
-
tenant_slug: str
|
|
140
|
-
description: Optional[str]
|
|
141
|
-
plan: str
|
|
142
|
-
status: str
|
|
143
|
-
created_at: str
|
|
144
|
-
contact_email: str
|
|
145
|
-
custom_domain: Optional[str]
|
|
146
|
-
custom_domain_verified: bool
|
|
147
|
-
logo_url: Optional[str]
|
|
148
|
-
total_memories: int
|
|
149
|
-
active_users: int
|
|
150
|
-
organization_name: str
|
|
151
|
-
scheduled_deletion_at: Optional[str]
|
|
152
|
-
mfa_enrolled: bool
|
|
153
|
-
password_enabled: bool
|
|
154
|
-
|
|
155
|
-
|
|
156
107
|
class Project(TypedDict, total=False):
|
|
157
108
|
id: str
|
|
158
109
|
name: str
|
|
@@ -173,10 +124,33 @@ class Webhook(TypedDict, total=False):
|
|
|
173
124
|
events: List[str]
|
|
174
125
|
enabled: bool
|
|
175
126
|
signature_algorithm: str
|
|
176
|
-
|
|
177
|
-
|
|
127
|
+
signature_header: str
|
|
128
|
+
timestamp_header: str
|
|
129
|
+
signature_tolerance: int
|
|
130
|
+
retry_enabled: bool
|
|
131
|
+
max_retries: int
|
|
132
|
+
initial_delay_seconds: int
|
|
133
|
+
max_delay_seconds: int
|
|
134
|
+
backoff_multiplier: float
|
|
135
|
+
retry_status_codes: List[str]
|
|
136
|
+
total_deliveries: int
|
|
137
|
+
successful_deliveries: int
|
|
138
|
+
failed_deliveries: int
|
|
139
|
+
consecutive_failures: int
|
|
178
140
|
success_rate: float
|
|
141
|
+
last_triggered_at: Optional[str]
|
|
142
|
+
last_success_at: Optional[str]
|
|
143
|
+
last_failure_at: Optional[str]
|
|
144
|
+
last_error: Optional[str]
|
|
145
|
+
last_status_code: Optional[int]
|
|
146
|
+
created_at: str
|
|
179
147
|
updated_at: str
|
|
148
|
+
created_by_name: Optional[str]
|
|
149
|
+
project_id: Optional[str]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CreatedWebhook(Webhook):
|
|
153
|
+
secret: str
|
|
180
154
|
|
|
181
155
|
|
|
182
156
|
class WebhooksResponse(TypedDict, total=False):
|
|
@@ -212,11 +186,16 @@ class WebhookDelivery(TypedDict, total=False):
|
|
|
212
186
|
event_type: str
|
|
213
187
|
event_id: str
|
|
214
188
|
status: str
|
|
189
|
+
payload: JsonObject
|
|
215
190
|
payload_hash: str
|
|
191
|
+
signature: Optional[str]
|
|
216
192
|
status_code: Optional[int]
|
|
193
|
+
response_body: Optional[str]
|
|
194
|
+
error_message: Optional[str]
|
|
217
195
|
latency_ms: Optional[int]
|
|
218
196
|
attempt_number: int
|
|
219
197
|
max_attempts: int
|
|
198
|
+
next_retry_at: Optional[str]
|
|
220
199
|
created_at: str
|
|
221
200
|
completed_at: Optional[str]
|
|
222
201
|
|
|
@@ -232,6 +211,10 @@ class CreateWebhookRequest(TypedDict):
|
|
|
232
211
|
name: str
|
|
233
212
|
url: str
|
|
234
213
|
events: List[str]
|
|
214
|
+
description: NotRequired[Optional[str]]
|
|
215
|
+
retry_config: NotRequired[JsonObject]
|
|
216
|
+
signature_config: NotRequired[JsonObject]
|
|
217
|
+
project_id: NotRequired[str]
|
|
235
218
|
|
|
236
219
|
|
|
237
220
|
class UpdateWebhookRequest(TypedDict, total=False):
|
|
@@ -265,3 +248,85 @@ class ReplayWebhookDeliveriesRequest(TypedDict):
|
|
|
265
248
|
since_minutes: int
|
|
266
249
|
statuses: List[str]
|
|
267
250
|
limit: int
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class CreateProjectRequest(TypedDict):
|
|
254
|
+
name: str
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class RenameProjectRequest(TypedDict):
|
|
258
|
+
name: str
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class WebhookEventTypesResponse(TypedDict):
|
|
262
|
+
event_types: List[str]
|
|
263
|
+
categories: Dict[str, List[str]]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class WebhookHealth(TypedDict):
|
|
267
|
+
active_webhooks: int
|
|
268
|
+
total_webhooks: int
|
|
269
|
+
total_deliveries_all_time: int
|
|
270
|
+
total_deliveries_24h: int
|
|
271
|
+
success_rate_7d: float
|
|
272
|
+
failing_endpoints: int
|
|
273
|
+
pending_retries: int
|
|
274
|
+
dead_letter_count: int
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class ExportFilters(TypedDict, total=False):
|
|
278
|
+
q: str
|
|
279
|
+
user_id: str
|
|
280
|
+
is_summary: bool
|
|
281
|
+
tier: str
|
|
282
|
+
source: str
|
|
283
|
+
time_range: str
|
|
284
|
+
date_from: str
|
|
285
|
+
date_to: str
|
|
286
|
+
include_soft_deleted: bool
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class CreateExportRequest(TypedDict, total=False):
|
|
290
|
+
format: str
|
|
291
|
+
scope: str
|
|
292
|
+
filters: ExportFilters
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class ExportJob(TypedDict, total=False):
|
|
296
|
+
id: str
|
|
297
|
+
project_id: Optional[str]
|
|
298
|
+
environment: str
|
|
299
|
+
format: str
|
|
300
|
+
scope: str
|
|
301
|
+
filters: JsonObject
|
|
302
|
+
status: str
|
|
303
|
+
error_message: Optional[str]
|
|
304
|
+
retry_count: int
|
|
305
|
+
total_rows: Optional[int]
|
|
306
|
+
processed_rows: int
|
|
307
|
+
progress_percentage: float
|
|
308
|
+
file_size_bytes: Optional[int]
|
|
309
|
+
sha256_hash: Optional[str]
|
|
310
|
+
download_count: int
|
|
311
|
+
created_at: Optional[str]
|
|
312
|
+
started_at: Optional[str]
|
|
313
|
+
completed_at: Optional[str]
|
|
314
|
+
expires_at: Optional[str]
|
|
315
|
+
download_url: Optional[str]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class ExportJobListResponse(TypedDict):
|
|
319
|
+
jobs: List[ExportJob]
|
|
320
|
+
total: int
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class CreateExportResponse(TypedDict):
|
|
324
|
+
job: ExportJob
|
|
325
|
+
estimated_total: Optional[int]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class ExportDownloadUrl(TypedDict):
|
|
329
|
+
download_url: str
|
|
330
|
+
expires_at: Optional[str]
|
|
331
|
+
file_size_bytes: Optional[int]
|
|
332
|
+
filename: str
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|