getstack 0.5.1__tar.gz → 0.6.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.
- {getstack-0.5.1 → getstack-0.6.0}/PKG-INFO +1 -1
- {getstack-0.5.1 → getstack-0.6.0}/pyproject.toml +1 -1
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/__init__.py +53 -4
- getstack-0.6.0/src/getstack/_async/__init__.py +14 -0
- getstack-0.6.0/src/getstack/_async/agents.py +53 -0
- getstack-0.6.0/src/getstack/_async/audit.py +46 -0
- getstack-0.6.0/src/getstack/_async/credentials.py +43 -0
- getstack-0.6.0/src/getstack/_async/dropoffs.py +49 -0
- getstack-0.6.0/src/getstack/_async/identity.py +92 -0
- getstack-0.6.0/src/getstack/_async/intents.py +128 -0
- getstack-0.6.0/src/getstack/_async/notifications.py +49 -0
- getstack-0.6.0/src/getstack/_async/passports.py +364 -0
- getstack-0.6.0/src/getstack/_async/proxy.py +73 -0
- getstack-0.6.0/src/getstack/_async/reviews.py +53 -0
- getstack-0.6.0/src/getstack/_async/scan.py +40 -0
- getstack-0.6.0/src/getstack/_async/security_events.py +26 -0
- getstack-0.6.0/src/getstack/_async/services.py +41 -0
- getstack-0.6.0/src/getstack/_async/skills.py +162 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/client.py +47 -13
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/passports.py +7 -3
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/proxy.py +7 -1
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/skills.py +21 -1
- {getstack-0.5.1 → getstack-0.6.0}/.gitignore +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/CLAUDE.md +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/LICENSE +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/README.md +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/agent_auth.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/agents.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/audit.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/auth.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/browser_bootstrap.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/credentials.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/dropoffs.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/errors.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/identity.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/intents.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/notifications.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/py.typed +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/reviews.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/scan.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/security_events.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/services.py +0 -0
- {getstack-0.5.1 → getstack-0.6.0}/src/getstack/types.py +0 -0
|
@@ -256,15 +256,31 @@ class Stack:
|
|
|
256
256
|
class AsyncStack:
|
|
257
257
|
"""Asynchronous STACK client.
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
transport layer for future async service implementations.
|
|
259
|
+
Sub-chunk 2.4 — full parity with Stack. All 14 typed services are
|
|
260
|
+
async-native, plus AsyncMission for the bounded-mission context
|
|
261
|
+
manager. Uses httpx.AsyncClient under the hood.
|
|
263
262
|
|
|
264
263
|
Args:
|
|
265
264
|
api_key: A STACK API key (``sk_live_...``).
|
|
266
265
|
base_url: Override the API base URL.
|
|
267
266
|
timeout: HTTP timeout in seconds.
|
|
267
|
+
|
|
268
|
+
Example::
|
|
269
|
+
|
|
270
|
+
from getstack import AsyncStack
|
|
271
|
+
|
|
272
|
+
async with AsyncStack(api_key="sk_live_...") as stack:
|
|
273
|
+
agents = await stack.agents.list()
|
|
274
|
+
async with stack.passports.mission(
|
|
275
|
+
agent_id=agents[0].id,
|
|
276
|
+
intent="Process invoices",
|
|
277
|
+
services=["slack", "stripe"],
|
|
278
|
+
) as mission:
|
|
279
|
+
resp = await mission.proxy(
|
|
280
|
+
"stripe", "https://api.stripe.com/v1/charges",
|
|
281
|
+
method="POST", body={"amount": 50},
|
|
282
|
+
_proxy_service=stack.proxy,
|
|
283
|
+
)
|
|
268
284
|
"""
|
|
269
285
|
|
|
270
286
|
def __init__(
|
|
@@ -293,6 +309,39 @@ class AsyncStack:
|
|
|
293
309
|
|
|
294
310
|
self._client = AsyncHttpClient(auth, base_url=base_url, timeout=timeout)
|
|
295
311
|
|
|
312
|
+
# Sub-chunk 2.4 — 14 typed async services attached eagerly,
|
|
313
|
+
# matching Stack's posture. Footprint is small; lazy init
|
|
314
|
+
# would only buy us memory we don't need to save.
|
|
315
|
+
from ._async.agents import AsyncAgentService
|
|
316
|
+
from ._async.passports import AsyncPassportService
|
|
317
|
+
from ._async.credentials import AsyncCredentialService
|
|
318
|
+
from ._async.services import AsyncServiceService
|
|
319
|
+
from ._async.dropoffs import AsyncDropoffService
|
|
320
|
+
from ._async.reviews import AsyncReviewService
|
|
321
|
+
from ._async.notifications import AsyncNotificationService
|
|
322
|
+
from ._async.audit import AsyncAuditService
|
|
323
|
+
from ._async.proxy import AsyncProxyService
|
|
324
|
+
from ._async.scan import AsyncScanService
|
|
325
|
+
from ._async.security_events import AsyncSecurityEventService
|
|
326
|
+
from ._async.skills import AsyncSkillService
|
|
327
|
+
from ._async.identity import AsyncIdentityService
|
|
328
|
+
from ._async.intents import AsyncIntentsService
|
|
329
|
+
|
|
330
|
+
self.agents = AsyncAgentService(self._client)
|
|
331
|
+
self.passports = AsyncPassportService(self._client)
|
|
332
|
+
self.credentials = AsyncCredentialService(self._client)
|
|
333
|
+
self.services = AsyncServiceService(self._client)
|
|
334
|
+
self.dropoffs = AsyncDropoffService(self._client)
|
|
335
|
+
self.reviews = AsyncReviewService(self._client)
|
|
336
|
+
self.notifications = AsyncNotificationService(self._client)
|
|
337
|
+
self.audit = AsyncAuditService(self._client)
|
|
338
|
+
self.proxy = AsyncProxyService(self._client)
|
|
339
|
+
self.scan = AsyncScanService(self._client)
|
|
340
|
+
self.security_events = AsyncSecurityEventService(self._client)
|
|
341
|
+
self.skills = AsyncSkillService(self._client)
|
|
342
|
+
self.identity = AsyncIdentityService(self._client)
|
|
343
|
+
self.intents = AsyncIntentsService(self._client)
|
|
344
|
+
|
|
296
345
|
@classmethod
|
|
297
346
|
def from_session(cls, session_token: str, **kwargs) -> AsyncStack:
|
|
298
347
|
"""Create an async client authenticated with a session JWT."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Async service modules (Sub-chunk 2.4 — full parity with sync).
|
|
2
|
+
|
|
3
|
+
Each AsyncXxxService mirrors the sync XxxService surface exactly:
|
|
4
|
+
same method names, same kwargs, same return types. The only
|
|
5
|
+
difference is `async def` and `await self._client.x` instead of
|
|
6
|
+
`self._client.x`. The Mission ergonomic gets an AsyncMission
|
|
7
|
+
context manager that uses asyncio.create_task + asyncio.sleep
|
|
8
|
+
instead of threading.Timer.
|
|
9
|
+
|
|
10
|
+
Import via AsyncStack:
|
|
11
|
+
from getstack import AsyncStack
|
|
12
|
+
async with AsyncStack(api_key="sk_live_...") as stack:
|
|
13
|
+
agents = await stack.agents.list()
|
|
14
|
+
"""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Async mirror of AgentService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import Agent
|
|
9
|
+
from ..agents import _to_agent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncAgentService:
|
|
13
|
+
def __init__(self, client: AsyncHttpClient):
|
|
14
|
+
self._client = client
|
|
15
|
+
|
|
16
|
+
async def register(
|
|
17
|
+
self,
|
|
18
|
+
name: str,
|
|
19
|
+
description: str | None = None,
|
|
20
|
+
accountability_mode: str = "enforced",
|
|
21
|
+
on_warning: str = "notify",
|
|
22
|
+
on_critical: str = "notify",
|
|
23
|
+
) -> Agent:
|
|
24
|
+
body: dict[str, Any] = {"name": name, "accountability_mode": accountability_mode}
|
|
25
|
+
if description:
|
|
26
|
+
body["description"] = description
|
|
27
|
+
if on_warning != "notify":
|
|
28
|
+
body["on_warning"] = on_warning
|
|
29
|
+
if on_critical != "notify":
|
|
30
|
+
body["on_critical"] = on_critical
|
|
31
|
+
return _to_agent(await self._client.post("/v1/agents/register", json=body))
|
|
32
|
+
|
|
33
|
+
async def get(self, agent_id: str) -> Agent:
|
|
34
|
+
return _to_agent(await self._client.get(f"/v1/agents/{agent_id}"))
|
|
35
|
+
|
|
36
|
+
async def list(self) -> list[Agent]:
|
|
37
|
+
data = await self._client.get("/v1/agents")
|
|
38
|
+
return [_to_agent(a) for a in data]
|
|
39
|
+
|
|
40
|
+
async def update(self, agent_id: str, **kwargs: Any) -> Agent:
|
|
41
|
+
return _to_agent(await self._client.patch(f"/v1/agents/{agent_id}", json=kwargs))
|
|
42
|
+
|
|
43
|
+
async def suspend(self, agent_id: str) -> Agent:
|
|
44
|
+
return await self.update(agent_id, status="suspended")
|
|
45
|
+
|
|
46
|
+
async def activate(self, agent_id: str) -> Agent:
|
|
47
|
+
return await self.update(agent_id, status="active")
|
|
48
|
+
|
|
49
|
+
async def delete(self, agent_id: str) -> dict[str, Any]:
|
|
50
|
+
return await self._client.delete(f"/v1/agents/{agent_id}")
|
|
51
|
+
|
|
52
|
+
async def unblock(self, agent_id: str) -> dict[str, Any]:
|
|
53
|
+
return await self._client.post(f"/v1/agents/{agent_id}/unblock")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Async mirror of AuditService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncAuditService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def list(
|
|
15
|
+
self,
|
|
16
|
+
limit: int = 20,
|
|
17
|
+
since: int | None = None,
|
|
18
|
+
agent_id: str | None = None,
|
|
19
|
+
passport_jti: str | None = None,
|
|
20
|
+
) -> dict[str, Any]:
|
|
21
|
+
params: dict[str, str] = {"limit": str(limit)}
|
|
22
|
+
if since is not None:
|
|
23
|
+
params["since"] = str(since)
|
|
24
|
+
if agent_id is not None:
|
|
25
|
+
params["agent_id"] = agent_id
|
|
26
|
+
if passport_jti is not None:
|
|
27
|
+
params["passport_jti"] = passport_jti
|
|
28
|
+
return await self._client.get("/v1/audit", params=params)
|
|
29
|
+
|
|
30
|
+
async def chain_head(self) -> dict[str, Any]:
|
|
31
|
+
return await self._client.get("/v1/audit/chain-head")
|
|
32
|
+
|
|
33
|
+
async def verify_chain(
|
|
34
|
+
self,
|
|
35
|
+
from_date: str | None = None,
|
|
36
|
+
to_date: str | None = None,
|
|
37
|
+
limit: int | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
params: dict[str, str] = {}
|
|
40
|
+
if from_date is not None:
|
|
41
|
+
params["from"] = from_date
|
|
42
|
+
if to_date is not None:
|
|
43
|
+
params["to"] = to_date
|
|
44
|
+
if limit is not None:
|
|
45
|
+
params["limit"] = str(limit)
|
|
46
|
+
return await self._client.get("/v1/audit/verify-chain", params=params)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Async mirror of CredentialService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import Credential
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncCredentialService:
|
|
12
|
+
def __init__(self, client: AsyncHttpClient):
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
async def get(self, provider: str, passport_token: str | None = None) -> Credential:
|
|
16
|
+
extra_headers: dict[str, str] = {}
|
|
17
|
+
if passport_token:
|
|
18
|
+
extra_headers["X-Passport-Token"] = passport_token
|
|
19
|
+
data = await self._client.get(
|
|
20
|
+
f"/v1/credentials/{quote(provider)}",
|
|
21
|
+
extra_headers=extra_headers if extra_headers else None,
|
|
22
|
+
)
|
|
23
|
+
return Credential(
|
|
24
|
+
provider=data["provider"],
|
|
25
|
+
credential=data.get("credential"),
|
|
26
|
+
credentials=data.get("credentials"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def get_by_connection(
|
|
30
|
+
self, connection_id: str, passport_token: str | None = None
|
|
31
|
+
) -> Credential:
|
|
32
|
+
extra_headers: dict[str, str] = {}
|
|
33
|
+
if passport_token:
|
|
34
|
+
extra_headers["X-Passport-Token"] = passport_token
|
|
35
|
+
data = await self._client.get(
|
|
36
|
+
f"/v1/credentials/by-connection/{connection_id}",
|
|
37
|
+
extra_headers=extra_headers if extra_headers else None,
|
|
38
|
+
)
|
|
39
|
+
return Credential(
|
|
40
|
+
provider=data["provider"],
|
|
41
|
+
credential=data.get("credential"),
|
|
42
|
+
credentials=data.get("credentials"),
|
|
43
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Async mirror of DropoffService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncDropoffService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def create(
|
|
15
|
+
self,
|
|
16
|
+
from_agent_id: str,
|
|
17
|
+
to_agent_id: str,
|
|
18
|
+
schema: dict[str, Any],
|
|
19
|
+
ttl_seconds: int | None = None,
|
|
20
|
+
on_expire: str | None = None,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
body: dict[str, Any] = {
|
|
23
|
+
"from_agent": from_agent_id,
|
|
24
|
+
"to_agent": to_agent_id,
|
|
25
|
+
"schema": schema,
|
|
26
|
+
}
|
|
27
|
+
if ttl_seconds:
|
|
28
|
+
body["ttl_seconds"] = ttl_seconds
|
|
29
|
+
if on_expire:
|
|
30
|
+
body["on_expire"] = on_expire
|
|
31
|
+
return await self._client.post("/v1/dropoffs", json=body)
|
|
32
|
+
|
|
33
|
+
async def deposit(self, dropoff_id: str, data: dict[str, Any], agent_id: str) -> dict[str, Any]:
|
|
34
|
+
return await self._client.post(
|
|
35
|
+
f"/v1/dropoffs/{dropoff_id}/deposit",
|
|
36
|
+
json={"agent_id": agent_id, "payload": data},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
async def collect(self, dropoff_id: str, agent_id: str) -> dict[str, Any]:
|
|
40
|
+
return await self._client.post(
|
|
41
|
+
f"/v1/dropoffs/{dropoff_id}/collect",
|
|
42
|
+
json={"agent_id": agent_id},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def get(self, dropoff_id: str) -> dict[str, Any]:
|
|
46
|
+
return await self._client.get(f"/v1/dropoffs/{dropoff_id}")
|
|
47
|
+
|
|
48
|
+
async def list(self) -> list[dict[str, Any]]:
|
|
49
|
+
return await self._client.get("/v1/dropoffs")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Async mirror of IdentityService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import IdentityProvider, IdentityClaim, IdentitySettings, VerificationSession
|
|
9
|
+
from ..identity import _to_provider, _to_claim
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncIdentityService:
|
|
13
|
+
def __init__(self, client: AsyncHttpClient):
|
|
14
|
+
self._client = client
|
|
15
|
+
|
|
16
|
+
async def get_settings(self) -> IdentitySettings:
|
|
17
|
+
data = await self._client.get("/v1/operators/me/identity-settings")
|
|
18
|
+
return IdentitySettings(
|
|
19
|
+
identity_claim_ttl=data["identity_claim_ttl"],
|
|
20
|
+
identity_claim_inheritance=data["identity_claim_inheritance"],
|
|
21
|
+
identity_auto_revoke=data["identity_auto_revoke"],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
async def update_settings(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
identity_claim_ttl: str | None = None,
|
|
28
|
+
identity_claim_inheritance: str | None = None,
|
|
29
|
+
identity_auto_revoke: bool | None = None,
|
|
30
|
+
) -> IdentitySettings:
|
|
31
|
+
body: dict[str, Any] = {}
|
|
32
|
+
if identity_claim_ttl is not None:
|
|
33
|
+
body["identity_claim_ttl"] = identity_claim_ttl
|
|
34
|
+
if identity_claim_inheritance is not None:
|
|
35
|
+
body["identity_claim_inheritance"] = identity_claim_inheritance
|
|
36
|
+
if identity_auto_revoke is not None:
|
|
37
|
+
body["identity_auto_revoke"] = identity_auto_revoke
|
|
38
|
+
data = await self._client.patch("/v1/operators/me/identity-settings", json=body)
|
|
39
|
+
return IdentitySettings(
|
|
40
|
+
identity_claim_ttl=data["identity_claim_ttl"],
|
|
41
|
+
identity_claim_inheritance=data["identity_claim_inheritance"],
|
|
42
|
+
identity_auto_revoke=data["identity_auto_revoke"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def list_providers(self) -> list[IdentityProvider]:
|
|
46
|
+
data = await self._client.get("/v1/identity/providers")
|
|
47
|
+
return [_to_provider(p) for p in data]
|
|
48
|
+
|
|
49
|
+
async def initiate_verification(
|
|
50
|
+
self,
|
|
51
|
+
provider_key: str,
|
|
52
|
+
*,
|
|
53
|
+
requested_claims: list[str] | None = None,
|
|
54
|
+
return_url: str | None = None,
|
|
55
|
+
) -> VerificationSession:
|
|
56
|
+
body: dict[str, Any] = {"provider_key": provider_key}
|
|
57
|
+
if requested_claims:
|
|
58
|
+
body["requested_claims"] = requested_claims
|
|
59
|
+
if return_url:
|
|
60
|
+
body["return_url"] = return_url
|
|
61
|
+
data = await self._client.post("/v1/identity/verify/initiate", json=body)
|
|
62
|
+
return VerificationSession(
|
|
63
|
+
session_ref=data["session_ref"],
|
|
64
|
+
authorization_url=data.get("authorization_url"),
|
|
65
|
+
status=data.get("status", "pending"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def complete_verification(self, provider_key: str, session_ref: str) -> dict[str, Any]:
|
|
69
|
+
return await self._client.post(
|
|
70
|
+
"/v1/identity/verify/complete",
|
|
71
|
+
json={"provider_key": provider_key, "session_ref": session_ref},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def grant_delegation(self, delegated_scopes: list[str]) -> IdentityClaim:
|
|
75
|
+
data = await self._client.post(
|
|
76
|
+
"/v1/identity/delegate",
|
|
77
|
+
json={"delegated_scopes": delegated_scopes},
|
|
78
|
+
)
|
|
79
|
+
return _to_claim(data)
|
|
80
|
+
|
|
81
|
+
async def list_claims(self) -> list[IdentityClaim]:
|
|
82
|
+
data = await self._client.get("/v1/identity/claims")
|
|
83
|
+
return [_to_claim(c) for c in data]
|
|
84
|
+
|
|
85
|
+
async def revoke_claim(self, claim_id: str) -> dict[str, Any]:
|
|
86
|
+
return await self._client.delete(f"/v1/identity/claims/{claim_id}")
|
|
87
|
+
|
|
88
|
+
async def get_service_requirements(self, service_id: str) -> dict[str, Any]:
|
|
89
|
+
return await self._client.get(f"/v1/services/{service_id}/requirements")
|
|
90
|
+
|
|
91
|
+
async def set_service_requirement(self, service_id: str, **kwargs: Any) -> dict[str, Any]:
|
|
92
|
+
return await self._client.post(f"/v1/services/{service_id}/requirements", json=kwargs)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Async mirror of IntentsService. asyncio.sleep for the poll loop."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..client import AsyncHttpClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncIntentsService:
|
|
12
|
+
def __init__(self, client: AsyncHttpClient):
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
async def simulate(
|
|
16
|
+
self,
|
|
17
|
+
intent: dict[str, Any],
|
|
18
|
+
passport_token: str,
|
|
19
|
+
*,
|
|
20
|
+
intent_claim_ref: str | None = None,
|
|
21
|
+
persist: bool | None = None,
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
body: dict[str, Any] = {"intent": intent}
|
|
24
|
+
if intent_claim_ref is not None:
|
|
25
|
+
body["intent_claim_ref"] = intent_claim_ref
|
|
26
|
+
if persist is not None:
|
|
27
|
+
body["persist"] = persist
|
|
28
|
+
return await self._client.request(
|
|
29
|
+
"POST",
|
|
30
|
+
"/v1/intents/simulate",
|
|
31
|
+
json=body,
|
|
32
|
+
extra_headers={"X-Passport-Token": passport_token},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def submit(
|
|
36
|
+
self,
|
|
37
|
+
intent: dict[str, Any],
|
|
38
|
+
passport_token: str,
|
|
39
|
+
*,
|
|
40
|
+
expires_in_seconds: int | None = None,
|
|
41
|
+
skip_simulation: bool = False,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
body: dict[str, Any] = {"intent": intent}
|
|
44
|
+
if expires_in_seconds is not None:
|
|
45
|
+
body["expires_in_seconds"] = expires_in_seconds
|
|
46
|
+
if skip_simulation:
|
|
47
|
+
body["skip_simulation"] = True
|
|
48
|
+
return await self._client.request(
|
|
49
|
+
"POST",
|
|
50
|
+
"/v1/intents/submit",
|
|
51
|
+
json=body,
|
|
52
|
+
extra_headers={"X-Passport-Token": passport_token},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async def submit_and_wait(
|
|
56
|
+
self,
|
|
57
|
+
intent: dict[str, Any],
|
|
58
|
+
passport_token: str,
|
|
59
|
+
*,
|
|
60
|
+
expires_in_seconds: int | None = None,
|
|
61
|
+
skip_simulation: bool = False,
|
|
62
|
+
timeout_seconds: float = 60.0,
|
|
63
|
+
poll_interval_seconds: float = 1.0,
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
"""Async submit + poll. Uses asyncio.sleep instead of time.sleep
|
|
66
|
+
so the event loop stays free during the wait."""
|
|
67
|
+
initial = await self.submit(
|
|
68
|
+
intent,
|
|
69
|
+
passport_token,
|
|
70
|
+
expires_in_seconds=expires_in_seconds,
|
|
71
|
+
skip_simulation=skip_simulation,
|
|
72
|
+
)
|
|
73
|
+
approval_id = initial["approval_id"]
|
|
74
|
+
loop = asyncio.get_event_loop()
|
|
75
|
+
deadline = loop.time() + timeout_seconds
|
|
76
|
+
last_row: dict[str, Any] | None = None
|
|
77
|
+
while loop.time() < deadline:
|
|
78
|
+
row = await self.get(approval_id)
|
|
79
|
+
last_row = row
|
|
80
|
+
if row.get("status") != "pending":
|
|
81
|
+
return {"initial": initial, "final": row, "timed_out": False}
|
|
82
|
+
await asyncio.sleep(poll_interval_seconds)
|
|
83
|
+
return {
|
|
84
|
+
"initial": initial,
|
|
85
|
+
"final": last_row or {"id": approval_id, "status": "pending"},
|
|
86
|
+
"timed_out": True,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async def get(self, approval_id: str) -> dict[str, Any]:
|
|
90
|
+
return await self._client.request("GET", f"/v1/intents/{approval_id}")
|
|
91
|
+
|
|
92
|
+
async def list_pending(
|
|
93
|
+
self, *, page: int | None = None, limit: int | None = None
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
params: dict[str, Any] = {}
|
|
96
|
+
if page is not None:
|
|
97
|
+
params["page"] = page
|
|
98
|
+
if limit is not None:
|
|
99
|
+
params["limit"] = limit
|
|
100
|
+
return await self._client.request("GET", "/v1/intents/pending", params=params or None)
|
|
101
|
+
|
|
102
|
+
async def approve(self, approval_id: str, *, notes: str | None = None) -> dict[str, Any]:
|
|
103
|
+
body: dict[str, Any] = {}
|
|
104
|
+
if notes is not None:
|
|
105
|
+
body["notes"] = notes
|
|
106
|
+
return await self._client.request(
|
|
107
|
+
"POST",
|
|
108
|
+
f"/v1/intents/{approval_id}/approve",
|
|
109
|
+
json=body,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def reject(
|
|
113
|
+
self,
|
|
114
|
+
approval_id: str,
|
|
115
|
+
*,
|
|
116
|
+
notes: str | None = None,
|
|
117
|
+
block_future: bool = False,
|
|
118
|
+
) -> dict[str, Any]:
|
|
119
|
+
body: dict[str, Any] = {}
|
|
120
|
+
if notes is not None:
|
|
121
|
+
body["notes"] = notes
|
|
122
|
+
if block_future:
|
|
123
|
+
body["block_future"] = True
|
|
124
|
+
return await self._client.request(
|
|
125
|
+
"POST",
|
|
126
|
+
f"/v1/intents/{approval_id}/reject",
|
|
127
|
+
json=body,
|
|
128
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Async mirror of NotificationService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import NotificationChannel
|
|
9
|
+
from ..notifications import _to_channel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncNotificationService:
|
|
13
|
+
def __init__(self, client: AsyncHttpClient):
|
|
14
|
+
self._client = client
|
|
15
|
+
|
|
16
|
+
async def list(self) -> list[NotificationChannel]:
|
|
17
|
+
data = await self._client.get("/v1/notifications/channels")
|
|
18
|
+
return [_to_channel(ch) for ch in data]
|
|
19
|
+
|
|
20
|
+
async def create(
|
|
21
|
+
self,
|
|
22
|
+
channel_type: str,
|
|
23
|
+
destination: str,
|
|
24
|
+
events: list[str] | None = None,
|
|
25
|
+
min_severity: str = "warning",
|
|
26
|
+
) -> NotificationChannel:
|
|
27
|
+
body: dict[str, Any] = {
|
|
28
|
+
"channel_type": channel_type,
|
|
29
|
+
"destination": destination,
|
|
30
|
+
"min_severity": min_severity,
|
|
31
|
+
}
|
|
32
|
+
if events:
|
|
33
|
+
body["events"] = events
|
|
34
|
+
return _to_channel(await self._client.post("/v1/notifications/channels", json=body))
|
|
35
|
+
|
|
36
|
+
async def send_verification_code(self, channel_id: str) -> dict[str, Any]:
|
|
37
|
+
return await self._client.post(f"/v1/notifications/channels/{channel_id}/send-code")
|
|
38
|
+
|
|
39
|
+
async def verify(self, channel_id: str, code: str) -> dict[str, Any]:
|
|
40
|
+
return await self._client.post(
|
|
41
|
+
f"/v1/notifications/channels/{channel_id}/verify",
|
|
42
|
+
json={"code": code},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def test(self, channel_id: str) -> dict[str, Any]:
|
|
46
|
+
return await self._client.post(f"/v1/notifications/channels/{channel_id}/test")
|
|
47
|
+
|
|
48
|
+
async def delete(self, channel_id: str) -> dict[str, Any]:
|
|
49
|
+
return await self._client.delete(f"/v1/notifications/channels/{channel_id}")
|