synapsedx-sdk 0.3.0.dev20261012__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.
- synapsedx_sdk/__init__.py +277 -0
- synapsedx_sdk/example.py +12 -0
- synapsedx_sdk/test_client.py +118 -0
- synapsedx_sdk-0.3.0.dev20261012.dist-info/METADATA +47 -0
- synapsedx_sdk-0.3.0.dev20261012.dist-info/RECORD +7 -0
- synapsedx_sdk-0.3.0.dev20261012.dist-info/WHEEL +5 -0
- synapsedx_sdk-0.3.0.dev20261012.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Copyright 2026 SynapseDX Contributors
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
"""synapsedx Python SDK — gateway + IAM + workflow client."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import quote, urlencode
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SynapseDXError(Exception):
|
|
15
|
+
"""Mọi lỗi SDK đều là SynapseDXError (network hoặc HTTP)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["SynapseDXClient", "SynapseDXError"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class SynapseDXClient:
|
|
23
|
+
base_url: str
|
|
24
|
+
token: str | None = None
|
|
25
|
+
api_key: str | None = None
|
|
26
|
+
timeout: int = 15
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
self.base_url = self.base_url.rstrip("/")
|
|
30
|
+
|
|
31
|
+
def _headers(self) -> dict[str, str]:
|
|
32
|
+
h = {"Content-Type": "application/json"}
|
|
33
|
+
if self.token:
|
|
34
|
+
h["Authorization"] = f"Bearer {self.token}"
|
|
35
|
+
if self.api_key:
|
|
36
|
+
h["X-API-Key"] = self.api_key
|
|
37
|
+
return h
|
|
38
|
+
|
|
39
|
+
def set_token(self, token: str) -> None:
|
|
40
|
+
"""Set/replace the Bearer JWT (mirrors JS setToken / Go SetToken)."""
|
|
41
|
+
self.token = token
|
|
42
|
+
|
|
43
|
+
def set_api_key(self, api_key: str) -> None:
|
|
44
|
+
"""Set/replace the X-API-Key (mirrors JS setApiKey / Go SetAPIKey)."""
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
|
|
47
|
+
def _req(self, method: str, path: str, body: Any | None = None) -> Any:
|
|
48
|
+
try:
|
|
49
|
+
r = requests.request(
|
|
50
|
+
method,
|
|
51
|
+
self.base_url + path,
|
|
52
|
+
json=body,
|
|
53
|
+
headers=self._headers(),
|
|
54
|
+
timeout=self.timeout,
|
|
55
|
+
)
|
|
56
|
+
except Exception as e: # network, DNS, timeout...
|
|
57
|
+
raise SynapseDXError(f"synapsedx network {path}: {e}") from e
|
|
58
|
+
try:
|
|
59
|
+
r.raise_for_status()
|
|
60
|
+
except Exception as e:
|
|
61
|
+
raise SynapseDXError(
|
|
62
|
+
f"synapsedx {r.status_code} {path}: {r.text[:500]}"
|
|
63
|
+
) from e
|
|
64
|
+
return r.json() if r.text else None
|
|
65
|
+
|
|
66
|
+
# ---- auth ----
|
|
67
|
+
def login(self, email: str, org_slug: str = "acme") -> dict:
|
|
68
|
+
return self._req(
|
|
69
|
+
"POST", "/v1/auth/login", {"email": email, "orgSlug": org_slug}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def refresh(self, refresh_token: str) -> dict:
|
|
73
|
+
return self._req("POST", "/v1/auth/refresh", {"refreshToken": refresh_token})
|
|
74
|
+
|
|
75
|
+
def logout(self, refresh_token: str) -> dict:
|
|
76
|
+
return self._req("POST", "/v1/auth/logout", {"refreshToken": refresh_token})
|
|
77
|
+
|
|
78
|
+
def logout_all(self, refresh_token: str) -> dict:
|
|
79
|
+
return self._req("POST", "/v1/auth/logout-all", {"refreshToken": refresh_token})
|
|
80
|
+
|
|
81
|
+
def verify(self) -> dict:
|
|
82
|
+
return self._req("GET", "/v1/auth/verify")
|
|
83
|
+
|
|
84
|
+
def me(self) -> dict:
|
|
85
|
+
return self._req("GET", "/v1/auth/me")
|
|
86
|
+
|
|
87
|
+
# ---- orgs ----
|
|
88
|
+
def list_orgs(self) -> list:
|
|
89
|
+
return self._req("GET", "/v1/orgs")
|
|
90
|
+
|
|
91
|
+
def create_org(self, slug: str, display_name: str) -> dict:
|
|
92
|
+
return self._req(
|
|
93
|
+
"POST", "/v1/orgs", {"slug": slug, "displayName": display_name}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def create_account(self, org_id: str, slug: str, display_name: str) -> dict:
|
|
97
|
+
return self._req(
|
|
98
|
+
"POST",
|
|
99
|
+
f"/v1/orgs/{quote(org_id, safe='')}/accounts",
|
|
100
|
+
{"slug": slug, "displayName": display_name},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def list_accounts(self, org_id: str) -> list:
|
|
104
|
+
return self._req("GET", f"/v1/orgs/{quote(org_id, safe='')}/accounts")
|
|
105
|
+
|
|
106
|
+
def create_user(
|
|
107
|
+
self, org_id: str, email: str, display_name: str | None = None
|
|
108
|
+
) -> dict:
|
|
109
|
+
return self._req(
|
|
110
|
+
"POST",
|
|
111
|
+
f"/v1/orgs/{quote(org_id, safe='')}/users",
|
|
112
|
+
{"email": email, "displayName": display_name},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def list_users(self, org_id: str) -> list:
|
|
116
|
+
return self._req("GET", f"/v1/orgs/{quote(org_id, safe='')}/users")
|
|
117
|
+
|
|
118
|
+
def user_policies(self, user_id: str) -> list:
|
|
119
|
+
"""Effective policy documents of a user (debug helper for authorize)."""
|
|
120
|
+
return self._req("GET", f"/v1/users/{quote(user_id, safe='')}/policies")
|
|
121
|
+
|
|
122
|
+
# ---- iam ----
|
|
123
|
+
def authorize(
|
|
124
|
+
self, user_id: str, action: str, resource: str, context: dict | None = None
|
|
125
|
+
) -> dict:
|
|
126
|
+
return self._req(
|
|
127
|
+
"POST",
|
|
128
|
+
"/v1/authorize",
|
|
129
|
+
{
|
|
130
|
+
"userId": user_id,
|
|
131
|
+
"action": action,
|
|
132
|
+
"resource": resource,
|
|
133
|
+
"context": context,
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def attach_policy(self, role_id: str, policy_id: str) -> dict:
|
|
138
|
+
return self._req(
|
|
139
|
+
"POST",
|
|
140
|
+
f"/v1/roles/{quote(role_id, safe='')}/attach",
|
|
141
|
+
{"policyId": policy_id},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def assign_role(self, user_id: str, role_id: str) -> dict:
|
|
145
|
+
return self._req(
|
|
146
|
+
"POST", f"/v1/users/{quote(user_id, safe='')}/assign", {"roleId": role_id}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def simulate(
|
|
150
|
+
self, documents: list, action: str, resource: str, context: dict | None = None
|
|
151
|
+
) -> dict:
|
|
152
|
+
return self._req(
|
|
153
|
+
"POST",
|
|
154
|
+
"/v1/authorize/dry-run",
|
|
155
|
+
{
|
|
156
|
+
"documents": documents,
|
|
157
|
+
"action": action,
|
|
158
|
+
"resource": resource,
|
|
159
|
+
"context": context,
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def list_audit(self, limit: int = 50, actor: str | None = None) -> list:
|
|
164
|
+
params: dict[str, Any] = {"limit": limit}
|
|
165
|
+
if actor:
|
|
166
|
+
params["actor"] = actor
|
|
167
|
+
return self._req("GET", f"/v1/audit?{urlencode(params)}")
|
|
168
|
+
|
|
169
|
+
def create_api_key(
|
|
170
|
+
self, account_id: str, name: str = "default", scopes: list | None = None
|
|
171
|
+
) -> dict:
|
|
172
|
+
return self._req(
|
|
173
|
+
"POST",
|
|
174
|
+
f"/v1/accounts/{quote(account_id, safe='')}/keys",
|
|
175
|
+
{"name": name, "scopes": scopes or ["*"]},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def revoke_api_key(self, key_id: str) -> dict:
|
|
179
|
+
return self._req("DELETE", f"/v1/keys/{quote(key_id, safe='')}")
|
|
180
|
+
|
|
181
|
+
def verify_api_key(self, api_key: str) -> dict:
|
|
182
|
+
"""Check a raw API key via the internal verify endpoint."""
|
|
183
|
+
return self._req("POST", "/v1/keys/verify", {"apiKey": api_key})
|
|
184
|
+
|
|
185
|
+
def create_service_account(
|
|
186
|
+
self, account_id: str, slug: str, description: str = ""
|
|
187
|
+
) -> dict:
|
|
188
|
+
return self._req(
|
|
189
|
+
"POST",
|
|
190
|
+
f"/v1/accounts/{quote(account_id, safe='')}/service-accounts",
|
|
191
|
+
{"slug": slug, "description": description},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def list_service_accounts(self, account_id: str) -> list:
|
|
195
|
+
return self._req(
|
|
196
|
+
"GET", f"/v1/accounts/{quote(account_id, safe='')}/service-accounts"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def list_roles(self, account_id: str) -> list:
|
|
200
|
+
return self._req("GET", f"/v1/accounts/{quote(account_id, safe='')}/roles")
|
|
201
|
+
|
|
202
|
+
def create_role(self, account_id: str, slug: str, description: str = "") -> dict:
|
|
203
|
+
return self._req(
|
|
204
|
+
"POST",
|
|
205
|
+
f"/v1/accounts/{quote(account_id, safe='')}/roles",
|
|
206
|
+
{"slug": slug, "description": description},
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def delete_role(self, role_id: str) -> dict:
|
|
210
|
+
return self._req("DELETE", f"/v1/roles/{quote(role_id, safe='')}")
|
|
211
|
+
|
|
212
|
+
def list_policies(self, account_id: str) -> list:
|
|
213
|
+
return self._req("GET", f"/v1/accounts/{quote(account_id, safe='')}/policies")
|
|
214
|
+
|
|
215
|
+
def create_policy(
|
|
216
|
+
self, account_id: str, slug: str, document: dict, description: str = ""
|
|
217
|
+
) -> dict:
|
|
218
|
+
return self._req(
|
|
219
|
+
"POST",
|
|
220
|
+
f"/v1/accounts/{quote(account_id, safe='')}/policies",
|
|
221
|
+
{"slug": slug, "document": document, "description": description},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def delete_policy(self, policy_id: str) -> dict:
|
|
225
|
+
return self._req("DELETE", f"/v1/policies/{quote(policy_id, safe='')}")
|
|
226
|
+
|
|
227
|
+
def service_account_token(self, sa_id: str, scopes: list | None = None) -> dict:
|
|
228
|
+
return self._req(
|
|
229
|
+
"POST",
|
|
230
|
+
f"/v1/service-accounts/{quote(sa_id, safe='')}/token",
|
|
231
|
+
{"scopes": scopes or ["*"]},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# ---- workflows ----
|
|
235
|
+
def list_templates(self) -> Any:
|
|
236
|
+
return self._req("GET", "/v1/templates")
|
|
237
|
+
|
|
238
|
+
def get_template(self, name: str) -> Any:
|
|
239
|
+
"""Fetch a single built-in template definition by name."""
|
|
240
|
+
return self._req("GET", f"/v1/templates/{quote(name, safe='')}")
|
|
241
|
+
|
|
242
|
+
def get_workflow(self, wf_id: str) -> Any:
|
|
243
|
+
return self._req("GET", f"/v1/workflows/{quote(wf_id, safe='')}")
|
|
244
|
+
|
|
245
|
+
def activate_workflow(self, wf_id: str) -> Any:
|
|
246
|
+
return self._req("POST", f"/v1/workflows/{quote(wf_id, safe='')}/activate")
|
|
247
|
+
|
|
248
|
+
def deactivate_workflow(self, wf_id: str) -> Any:
|
|
249
|
+
return self._req("POST", f"/v1/workflows/{quote(wf_id, safe='')}/deactivate")
|
|
250
|
+
|
|
251
|
+
def execute_workflow(self, wf_id: str, payload: dict | None = None) -> Any:
|
|
252
|
+
"""Trigger a manual execution (proxied to the engine)."""
|
|
253
|
+
return self._req(
|
|
254
|
+
"POST", f"/v1/workflows/{quote(wf_id, safe='')}/execute", payload or {}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def list_workflows(self) -> Any:
|
|
258
|
+
return self._req("GET", "/v1/workflows")
|
|
259
|
+
|
|
260
|
+
def create_workflow_from_template(
|
|
261
|
+
self, template: str, name: str | None = None
|
|
262
|
+
) -> Any:
|
|
263
|
+
return self._req("POST", "/v1/workflows", {"template": template, "name": name})
|
|
264
|
+
|
|
265
|
+
def create_workflow(self, name: str, definition: dict) -> Any:
|
|
266
|
+
"""Create a workflow from a raw n8n definition."""
|
|
267
|
+
return self._req(
|
|
268
|
+
"POST", "/v1/workflows", {"name": name, "definition": definition}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def list_executions(self, query: str = "") -> Any:
|
|
272
|
+
"""Proxy the engine execution list (e.g. query='limit=20')."""
|
|
273
|
+
q = query if not query or query.startswith("?") else f"?{query}"
|
|
274
|
+
return self._req("GET", f"/v1/executions{q}")
|
|
275
|
+
|
|
276
|
+
def get_execution(self, exec_id: str) -> Any:
|
|
277
|
+
return self._req("GET", f"/v1/executions/{quote(exec_id, safe='')}")
|
synapsedx_sdk/example.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Smoke example for synapsedx-sdk."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from synapsedx_sdk import SynapseDXClient
|
|
6
|
+
|
|
7
|
+
base = os.environ.get("SYNAPSEDX_BASE_URL", "http://localhost:8080")
|
|
8
|
+
c = SynapseDXClient(base)
|
|
9
|
+
login = c.login("demo@acme.local")
|
|
10
|
+
print("login:", login["user"]["email"])
|
|
11
|
+
c.token = login["accessToken"]
|
|
12
|
+
print(c.list_orgs())
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Offline SDK tests — stdlib echo server, no platform needed."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
6
|
+
|
|
7
|
+
from synapsedx_sdk import SynapseDXClient, SynapseDXError
|
|
8
|
+
|
|
9
|
+
_seen: dict = {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Echo(BaseHTTPRequestHandler):
|
|
13
|
+
def _send(self):
|
|
14
|
+
_seen["auth"] = self.headers.get("Authorization")
|
|
15
|
+
_seen["key"] = self.headers.get("X-API-Key")
|
|
16
|
+
_seen["path"] = self.path
|
|
17
|
+
_seen["method"] = self.command
|
|
18
|
+
# List endpoints return arrays like the real API.
|
|
19
|
+
is_list = self.command == "GET" and (
|
|
20
|
+
self.path == "/v1/orgs"
|
|
21
|
+
or self.path.startswith("/v1/executions")
|
|
22
|
+
or self.path in ("/v1/templates", "/v1/workflows", "/v1/audit")
|
|
23
|
+
or self.path.endswith(
|
|
24
|
+
("/accounts", "/users", "/roles", "/policies", "/service-accounts")
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
payload = b"[]" if is_list else json.dumps({"ok": True}).encode()
|
|
28
|
+
self.send_response(200)
|
|
29
|
+
self.send_header("Content-Type", "application/json")
|
|
30
|
+
self.send_header("Content-Length", str(len(payload)))
|
|
31
|
+
self.end_headers()
|
|
32
|
+
self.wfile.write(payload)
|
|
33
|
+
|
|
34
|
+
do_GET = _send
|
|
35
|
+
do_POST = _send
|
|
36
|
+
do_DELETE = _send
|
|
37
|
+
|
|
38
|
+
def log_message(self, *a): # silent
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _server():
|
|
43
|
+
srv = HTTPServer(("127.0.0.1", 0), Echo)
|
|
44
|
+
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
|
45
|
+
return srv
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_trim_and_headers():
|
|
49
|
+
srv = _server()
|
|
50
|
+
try:
|
|
51
|
+
base = f"http://127.0.0.1:{srv.server_port}"
|
|
52
|
+
c = SynapseDXClient(base + "/")
|
|
53
|
+
assert c.base_url == base
|
|
54
|
+
c.token = "t1"
|
|
55
|
+
c.list_orgs()
|
|
56
|
+
assert _seen["auth"] == "Bearer t1", _seen
|
|
57
|
+
assert _seen["path"] == "/v1/orgs"
|
|
58
|
+
c2 = SynapseDXClient(base, api_key="k1")
|
|
59
|
+
c2.list_templates()
|
|
60
|
+
assert _seen["key"] == "k1", _seen
|
|
61
|
+
finally:
|
|
62
|
+
srv.shutdown()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_network_error_wrapped():
|
|
66
|
+
c = SynapseDXClient("http://127.0.0.1:1", timeout=2)
|
|
67
|
+
try:
|
|
68
|
+
c.list_orgs()
|
|
69
|
+
except SynapseDXError as e:
|
|
70
|
+
assert "synapsedx" in str(e)
|
|
71
|
+
return
|
|
72
|
+
raise AssertionError("expected SynapseDXError")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_import():
|
|
76
|
+
from synapsedx_sdk import SynapseDXClient
|
|
77
|
+
|
|
78
|
+
c = SynapseDXClient("http://localhost:8080")
|
|
79
|
+
assert c.base_url == "http://localhost:8080"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_full_coverage_paths():
|
|
83
|
+
srv = _server()
|
|
84
|
+
try:
|
|
85
|
+
base = f"http://127.0.0.1:{srv.server_port}"
|
|
86
|
+
c = SynapseDXClient(base)
|
|
87
|
+
c.login("a@b.c")
|
|
88
|
+
assert _seen["path"] == "/v1/auth/login"
|
|
89
|
+
c.refresh("rt")
|
|
90
|
+
assert _seen["path"] == "/v1/auth/refresh"
|
|
91
|
+
c.list_accounts("org1")
|
|
92
|
+
assert _seen["path"] == "/v1/orgs/org1/accounts"
|
|
93
|
+
c.user_policies("u1")
|
|
94
|
+
assert _seen["path"] == "/v1/users/u1/policies"
|
|
95
|
+
c.get_template("webhook-to-log")
|
|
96
|
+
assert _seen["path"] == "/v1/templates/webhook-to-log"
|
|
97
|
+
c.get_workflow("a/b")
|
|
98
|
+
assert _seen["path"] == "/v1/workflows/a%2Fb"
|
|
99
|
+
c.deactivate_workflow("w1")
|
|
100
|
+
assert _seen["path"] == "/v1/workflows/w1/deactivate"
|
|
101
|
+
c.execute_workflow("w1", {})
|
|
102
|
+
assert _seen["path"] == "/v1/workflows/w1/execute"
|
|
103
|
+
c.list_executions("limit=5")
|
|
104
|
+
assert _seen["path"] == "/v1/executions?limit=5"
|
|
105
|
+
c.get_execution("e1")
|
|
106
|
+
assert _seen["path"] == "/v1/executions/e1"
|
|
107
|
+
c.verify_api_key("k")
|
|
108
|
+
assert _seen["path"] == "/v1/keys/verify"
|
|
109
|
+
c.list_audit(actor="u@x")
|
|
110
|
+
assert "actor=u%40x" in _seen["path"], _seen
|
|
111
|
+
c.set_token("t")
|
|
112
|
+
c.set_api_key("k")
|
|
113
|
+
assert c.token == "t" and c.api_key == "k"
|
|
114
|
+
doc = {"Statement": [{"Effect": "Allow", "Action": "*", "Resource": "*"}]}
|
|
115
|
+
c.create_workflow("n", doc)
|
|
116
|
+
assert _seen["path"] == "/v1/workflows"
|
|
117
|
+
finally:
|
|
118
|
+
srv.shutdown()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: synapsedx-sdk
|
|
3
|
+
Version: 0.3.0.dev20261012
|
|
4
|
+
Summary: synapsedx SDK for Python (Apache-2.0)
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.28
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
Requires-Dist: ruff; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# synapsedx SDK for Python
|
|
14
|
+
Apache-2.0. See root LICENSE. Part of [SynapseDX](../../README.md) Tier-1 PaaS.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install synapsedx-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from synapsedx_sdk import SynapseDXClient
|
|
22
|
+
|
|
23
|
+
c = SynapseDXClient("http://localhost:8080")
|
|
24
|
+
login = c.login("demo@acme.local")
|
|
25
|
+
c.set_token(login["accessToken"])
|
|
26
|
+
|
|
27
|
+
c.create_org("acme-2", "ACME 2")
|
|
28
|
+
c.authorize(login["user"]["id"], "workflow:Execute", "*")
|
|
29
|
+
c.create_workflow_from_template("webhook-to-log", "demo")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Auth: `SynapseDXClient(base_url, token=..., api_key=...)`, or `set_token(jwt)` /
|
|
33
|
+
`set_api_key(key)` after login. `timeout` configurable (default 15s).
|
|
34
|
+
See `synapsedx_sdk/example.py` and [docs/api.md](../../docs/api.md).
|
|
35
|
+
|
|
36
|
+
## Full method coverage (mirrors `docs/api.md`)
|
|
37
|
+
|
|
38
|
+
- Auth: `login`, `refresh`, `logout`, `logout_all`, `verify`, `me`
|
|
39
|
+
- Orgs/accounts/users: `list_orgs`, `create_org`, `list_accounts`, `create_account`,
|
|
40
|
+
`list_users`, `create_user`, `user_policies`
|
|
41
|
+
- IAM: `create_role`, `list_roles`, `delete_role`, `create_policy`, `list_policies`,
|
|
42
|
+
`delete_policy`, `attach_policy`, `assign_role`, `authorize`, `simulate`,
|
|
43
|
+
`list_audit`, `create_api_key`, `revoke_api_key`, `verify_api_key`,
|
|
44
|
+
`create_service_account`, `list_service_accounts`, `service_account_token`
|
|
45
|
+
- Workflows: `list_workflows`, `get_workflow`, `create_workflow_from_template`,
|
|
46
|
+
`create_workflow`, `activate_workflow`, `deactivate_workflow`, `execute_workflow`,
|
|
47
|
+
`list_templates`, `get_template`, `list_executions`, `get_execution`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
synapsedx_sdk/__init__.py,sha256=lZG9AAZA1to5e0LNxFT-7okKBc6t9o84U-OF-lb_ss4,9783
|
|
2
|
+
synapsedx_sdk/example.py,sha256=NRC5baEUwQfnfEqa4IINUp0lXtvAPFNQike6r8X9j8E,316
|
|
3
|
+
synapsedx_sdk/test_client.py,sha256=YuCi8FJfa3qg_NvRuUC71scZk8GDbu25Nt9wr68D0vU,3855
|
|
4
|
+
synapsedx_sdk-0.3.0.dev20261012.dist-info/METADATA,sha256=prssFAEonMEb24WEWEFPP_LgqzfThYcDYmNuwpbY4yc,1806
|
|
5
|
+
synapsedx_sdk-0.3.0.dev20261012.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
synapsedx_sdk-0.3.0.dev20261012.dist-info/top_level.txt,sha256=Z-344B3E557x3g82iRDzss7X6LX6vGohdsO1S1bo3Iw,14
|
|
7
|
+
synapsedx_sdk-0.3.0.dev20261012.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
synapsedx_sdk
|