splox 0.2.3__tar.gz → 0.2.4__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.
- {splox-0.2.3 → splox-0.2.4}/PKG-INFO +1 -1
- {splox-0.2.3 → splox-0.2.4}/pyproject.toml +1 -1
- {splox-0.2.3 → splox-0.2.4}/src/splox/__init__.py +20 -19
- {splox-0.2.3 → splox-0.2.4}/src/splox/_client.py +2 -4
- {splox-0.2.3 → splox-0.2.4}/src/splox/_models.py +194 -200
- splox-0.2.4/src/splox/_resources.py +42 -0
- splox-0.2.4/src/splox/_workflows.py +222 -0
- splox-0.2.4/tests/integration_test.py +126 -0
- splox-0.2.4/tests/test_workflows.py +388 -0
- splox-0.2.3/src/splox/_resources.py +0 -491
- splox-0.2.3/tests/integration_test.py +0 -140
- {splox-0.2.3 → splox-0.2.4}/.github/workflows/publish.yml +0 -0
- {splox-0.2.3 → splox-0.2.4}/.github/workflows/test.yml +0 -0
- {splox-0.2.3 → splox-0.2.4}/.gitignore +0 -0
- {splox-0.2.3 → splox-0.2.4}/CHANGELOG.md +0 -0
- {splox-0.2.3 → splox-0.2.4}/LICENSE +0 -0
- {splox-0.2.3 → splox-0.2.4}/README.md +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/_ids.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/_interactions.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/_mcp.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/_runs.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/_transport.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/exceptions.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/src/splox/py.typed +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/__init__.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/integration_test_mcp.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/integration_test_v2.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_client.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_codemode_contract.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_exceptions.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_ids.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_models.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_runs.py +0 -0
- {splox-0.2.3 → splox-0.2.4}/tests/test_sse.py +0 -0
|
@@ -4,8 +4,11 @@ from splox._client import AsyncSploxClient, SploxClient
|
|
|
4
4
|
from splox._ids import decode_id, encode_id
|
|
5
5
|
from splox._models import (
|
|
6
6
|
ContentPart,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
GraphEdge,
|
|
8
|
+
GraphEdgeInput,
|
|
9
|
+
GraphNode,
|
|
10
|
+
GraphNodeInput,
|
|
11
|
+
GraphPosition,
|
|
9
12
|
Interaction,
|
|
10
13
|
InteractionPage,
|
|
11
14
|
InteractionResponse,
|
|
@@ -17,12 +20,10 @@ from splox._models import (
|
|
|
17
20
|
MCPServerToolsResponse,
|
|
18
21
|
Message,
|
|
19
22
|
MessagePage,
|
|
20
|
-
Node,
|
|
21
23
|
NodeExecution,
|
|
22
24
|
Output,
|
|
23
25
|
OutputPage,
|
|
24
26
|
PageInfo,
|
|
25
|
-
Pagination,
|
|
26
27
|
RunEvent,
|
|
27
28
|
RunEventPage,
|
|
28
29
|
RunFailure,
|
|
@@ -32,12 +33,11 @@ from splox._models import (
|
|
|
32
33
|
RunUsage,
|
|
33
34
|
SSEEvent,
|
|
34
35
|
Workflow,
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
WorkflowGraph,
|
|
37
|
+
WorkflowGraphUpdate,
|
|
38
|
+
WorkflowPage,
|
|
37
39
|
WorkflowRequest,
|
|
38
40
|
WorkflowRequestFile,
|
|
39
|
-
WorkflowVersion,
|
|
40
|
-
WorkflowVersionListResponse,
|
|
41
41
|
TERMINAL_RUN_STATUSES,
|
|
42
42
|
)
|
|
43
43
|
from splox._mcp import generate_connection_token, generate_connection_link
|
|
@@ -69,30 +69,31 @@ __all__ = [
|
|
|
69
69
|
"TERMINAL_RUN_STATUSES",
|
|
70
70
|
"encode_id",
|
|
71
71
|
"decode_id",
|
|
72
|
-
#
|
|
73
|
-
"
|
|
72
|
+
# v2 workflows (graph API)
|
|
73
|
+
"Workflow",
|
|
74
|
+
"WorkflowPage",
|
|
75
|
+
"WorkflowGraph",
|
|
76
|
+
"WorkflowGraphUpdate",
|
|
77
|
+
"GraphPosition",
|
|
78
|
+
"GraphNode",
|
|
79
|
+
"GraphEdge",
|
|
80
|
+
"GraphNodeInput",
|
|
81
|
+
"GraphEdgeInput",
|
|
82
|
+
# v1 resources (MCP, legacy stream events)
|
|
74
83
|
"MCPCatalogItem",
|
|
75
84
|
"MCPCatalogListResponse",
|
|
76
85
|
"MCPConnection",
|
|
77
86
|
"MCPConnectionListResponse",
|
|
78
87
|
"MCPServerToolOption",
|
|
79
88
|
"MCPServerToolsResponse",
|
|
80
|
-
"Node",
|
|
81
89
|
"NodeExecution",
|
|
82
|
-
"Pagination",
|
|
83
90
|
"SSEEvent",
|
|
84
|
-
"EntryNodesResponse",
|
|
85
|
-
"Workflow",
|
|
86
|
-
"WorkflowFull",
|
|
87
|
-
"WorkflowListResponse",
|
|
88
91
|
"WorkflowRequest",
|
|
89
92
|
"WorkflowRequestFile",
|
|
90
|
-
"WorkflowVersion",
|
|
91
|
-
"WorkflowVersionListResponse",
|
|
92
93
|
"generate_connection_token",
|
|
93
94
|
"generate_connection_link",
|
|
94
95
|
"notify",
|
|
95
96
|
"async_notify",
|
|
96
97
|
]
|
|
97
98
|
|
|
98
|
-
__version__ = "0.2.
|
|
99
|
+
__version__ = "0.2.4"
|
|
@@ -5,14 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
from typing import Optional, Any
|
|
7
7
|
|
|
8
|
-
from splox._resources import
|
|
9
|
-
AsyncWorkflows,
|
|
10
|
-
Workflows,
|
|
11
|
-
)
|
|
8
|
+
from splox._resources import async_notify, notify
|
|
12
9
|
from splox._interactions import AsyncInteractions, Interactions
|
|
13
10
|
from splox._mcp import AsyncMCP, MCP
|
|
14
11
|
from splox._runs import AsyncRuns, Runs
|
|
15
12
|
from splox._transport import DEFAULT_TIMEOUT, AsyncTransport, SyncTransport
|
|
13
|
+
from splox._workflows import AsyncWorkflows, Workflows
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
class SploxClient:
|
|
@@ -101,206 +101,6 @@ class NodeExecution:
|
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
@dataclass
|
|
105
|
-
class Pagination:
|
|
106
|
-
"""Cursor-based pagination info."""
|
|
107
|
-
|
|
108
|
-
limit: int
|
|
109
|
-
next_cursor: Optional[str] = None
|
|
110
|
-
has_more: bool = False
|
|
111
|
-
|
|
112
|
-
@classmethod
|
|
113
|
-
def from_dict(cls, data: Dict[str, Any]) -> Pagination:
|
|
114
|
-
return cls(
|
|
115
|
-
limit=data["limit"],
|
|
116
|
-
next_cursor=data.get("next_cursor"),
|
|
117
|
-
has_more=data.get("has_more", False),
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# --- Workflow / Version / Node / Edge models ---
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@dataclass
|
|
125
|
-
class WorkflowVersion:
|
|
126
|
-
"""Represents a specific version of a workflow."""
|
|
127
|
-
|
|
128
|
-
id: str
|
|
129
|
-
workflow_id: str
|
|
130
|
-
version_number: int
|
|
131
|
-
name: str
|
|
132
|
-
status: str
|
|
133
|
-
created_at: Optional[str] = None
|
|
134
|
-
updated_at: Optional[str] = None
|
|
135
|
-
description: Optional[str] = None
|
|
136
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def from_dict(cls, data: Dict[str, Any]) -> WorkflowVersion:
|
|
140
|
-
return cls(
|
|
141
|
-
id=data["id"],
|
|
142
|
-
workflow_id=data["workflow_id"],
|
|
143
|
-
version_number=data["version_number"],
|
|
144
|
-
name=data["name"],
|
|
145
|
-
status=data["status"],
|
|
146
|
-
created_at=data.get("created_at"),
|
|
147
|
-
updated_at=data.get("updated_at"),
|
|
148
|
-
description=data.get("description"),
|
|
149
|
-
metadata=data.get("metadata"),
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@dataclass
|
|
154
|
-
class Workflow:
|
|
155
|
-
"""Represents a workflow."""
|
|
156
|
-
|
|
157
|
-
id: str
|
|
158
|
-
user_id: str
|
|
159
|
-
created_at: Optional[str] = None
|
|
160
|
-
updated_at: Optional[str] = None
|
|
161
|
-
latest_version: Optional[WorkflowVersion] = None
|
|
162
|
-
is_public: Optional[bool] = None
|
|
163
|
-
|
|
164
|
-
@classmethod
|
|
165
|
-
def from_dict(cls, data: Dict[str, Any]) -> Workflow:
|
|
166
|
-
lv = None
|
|
167
|
-
if data.get("latest_version"):
|
|
168
|
-
lv = WorkflowVersion.from_dict(data["latest_version"])
|
|
169
|
-
return cls(
|
|
170
|
-
id=data["id"],
|
|
171
|
-
user_id=data["user_id"],
|
|
172
|
-
created_at=data.get("created_at"),
|
|
173
|
-
updated_at=data.get("updated_at"),
|
|
174
|
-
latest_version=lv,
|
|
175
|
-
is_public=data.get("is_public"),
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
@dataclass
|
|
180
|
-
class Node:
|
|
181
|
-
"""Represents a node in a workflow."""
|
|
182
|
-
|
|
183
|
-
id: str
|
|
184
|
-
workflow_version_id: str
|
|
185
|
-
node_type: str
|
|
186
|
-
label: str
|
|
187
|
-
pos_x: Optional[float] = None
|
|
188
|
-
pos_y: Optional[float] = None
|
|
189
|
-
parent_id: Optional[str] = None
|
|
190
|
-
extent: Optional[str] = None
|
|
191
|
-
data: Optional[Dict[str, Any]] = None
|
|
192
|
-
created_at: Optional[str] = None
|
|
193
|
-
updated_at: Optional[str] = None
|
|
194
|
-
|
|
195
|
-
@classmethod
|
|
196
|
-
def from_dict(cls, data: Dict[str, Any]) -> Node:
|
|
197
|
-
return cls(
|
|
198
|
-
id=data["id"],
|
|
199
|
-
workflow_version_id=data["workflow_version_id"],
|
|
200
|
-
node_type=data["node_type"],
|
|
201
|
-
label=data["label"],
|
|
202
|
-
pos_x=data.get("pos_x"),
|
|
203
|
-
pos_y=data.get("pos_y"),
|
|
204
|
-
parent_id=data.get("parent_id"),
|
|
205
|
-
extent=data.get("extent"),
|
|
206
|
-
data=data.get("data"),
|
|
207
|
-
created_at=data.get("created_at"),
|
|
208
|
-
updated_at=data.get("updated_at"),
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
@dataclass
|
|
213
|
-
class Edge:
|
|
214
|
-
"""Represents an edge (connection) between nodes."""
|
|
215
|
-
|
|
216
|
-
id: str
|
|
217
|
-
workflow_version_id: str
|
|
218
|
-
source: str
|
|
219
|
-
target: str
|
|
220
|
-
edge_type: str
|
|
221
|
-
source_handle: Optional[str] = None
|
|
222
|
-
data: Optional[Dict[str, Any]] = None
|
|
223
|
-
created_at: Optional[str] = None
|
|
224
|
-
updated_at: Optional[str] = None
|
|
225
|
-
|
|
226
|
-
@classmethod
|
|
227
|
-
def from_dict(cls, data: Dict[str, Any]) -> Edge:
|
|
228
|
-
return cls(
|
|
229
|
-
id=data["id"],
|
|
230
|
-
workflow_version_id=data["workflow_version_id"],
|
|
231
|
-
source=data["source"],
|
|
232
|
-
target=data["target"],
|
|
233
|
-
edge_type=data["edge_type"],
|
|
234
|
-
source_handle=data.get("source_handle"),
|
|
235
|
-
data=data.get("data"),
|
|
236
|
-
created_at=data.get("created_at"),
|
|
237
|
-
updated_at=data.get("updated_at"),
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@dataclass
|
|
242
|
-
class WorkflowFull:
|
|
243
|
-
"""Full workflow with version, nodes, and edges."""
|
|
244
|
-
|
|
245
|
-
workflow: Workflow
|
|
246
|
-
workflow_version: WorkflowVersion
|
|
247
|
-
nodes: List[Node] = field(default_factory=list)
|
|
248
|
-
edges: List[Edge] = field(default_factory=list)
|
|
249
|
-
|
|
250
|
-
@classmethod
|
|
251
|
-
def from_dict(cls, data: Dict[str, Any]) -> WorkflowFull:
|
|
252
|
-
return cls(
|
|
253
|
-
workflow=Workflow.from_dict(data["workflow"]),
|
|
254
|
-
workflow_version=WorkflowVersion.from_dict(data["workflow_version"]),
|
|
255
|
-
nodes=[Node.from_dict(n) for n in (data.get("nodes") or [])],
|
|
256
|
-
edges=[Edge.from_dict(e) for e in (data.get("edges") or [])],
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
# --- Response wrappers ---
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
@dataclass
|
|
264
|
-
class WorkflowListResponse:
|
|
265
|
-
"""Response from listing workflows."""
|
|
266
|
-
|
|
267
|
-
workflows: List[Workflow]
|
|
268
|
-
pagination: Pagination
|
|
269
|
-
|
|
270
|
-
@classmethod
|
|
271
|
-
def from_dict(cls, data: Dict[str, Any]) -> WorkflowListResponse:
|
|
272
|
-
return cls(
|
|
273
|
-
workflows=[Workflow.from_dict(w) for w in (data.get("workflows") or [])],
|
|
274
|
-
pagination=Pagination.from_dict(data["pagination"]),
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
@dataclass
|
|
279
|
-
class EntryNodesResponse:
|
|
280
|
-
"""Response from getting entry nodes."""
|
|
281
|
-
|
|
282
|
-
nodes: List[Node]
|
|
283
|
-
|
|
284
|
-
@classmethod
|
|
285
|
-
def from_dict(cls, data: Dict[str, Any]) -> EntryNodesResponse:
|
|
286
|
-
return cls(
|
|
287
|
-
nodes=[Node.from_dict(n) for n in (data.get("nodes") or [])],
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
@dataclass
|
|
292
|
-
class WorkflowVersionListResponse:
|
|
293
|
-
"""Response from listing workflow versions."""
|
|
294
|
-
|
|
295
|
-
versions: List[WorkflowVersion]
|
|
296
|
-
|
|
297
|
-
@classmethod
|
|
298
|
-
def from_dict(cls, data: Dict[str, Any]) -> WorkflowVersionListResponse:
|
|
299
|
-
return cls(
|
|
300
|
-
versions=[WorkflowVersion.from_dict(v) for v in (data.get("versions") or [])],
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
104
|
@dataclass
|
|
305
105
|
class SSEEvent:
|
|
306
106
|
"""A single Server-Sent Event from a listen stream.
|
|
@@ -981,3 +781,197 @@ class InteractionPage:
|
|
|
981
781
|
data=[Interaction.from_dict(i) for i in (raw.get("data") or [])],
|
|
982
782
|
page=PageInfo.from_dict(raw.get("page") or {}),
|
|
983
783
|
)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# ---------------------------------------------------------------------------
|
|
787
|
+
# v2 workflows (graph API)
|
|
788
|
+
# ---------------------------------------------------------------------------
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
@dataclass
|
|
792
|
+
class Workflow:
|
|
793
|
+
"""A v2 workflow (management surface; graphs live on versions)."""
|
|
794
|
+
|
|
795
|
+
id: str
|
|
796
|
+
name: str
|
|
797
|
+
description: str = ""
|
|
798
|
+
created_at: str = ""
|
|
799
|
+
updated_at: str = ""
|
|
800
|
+
|
|
801
|
+
@classmethod
|
|
802
|
+
def from_dict(cls, data: Dict[str, Any]) -> Workflow:
|
|
803
|
+
return cls(
|
|
804
|
+
id=data["id"],
|
|
805
|
+
name=data["name"],
|
|
806
|
+
description=data.get("description") or "",
|
|
807
|
+
created_at=data.get("created_at", ""),
|
|
808
|
+
updated_at=data.get("updated_at", ""),
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
@dataclass
|
|
813
|
+
class WorkflowPage:
|
|
814
|
+
"""One page of workflows (newest first)."""
|
|
815
|
+
|
|
816
|
+
data: List[Workflow]
|
|
817
|
+
page: PageInfo
|
|
818
|
+
|
|
819
|
+
@classmethod
|
|
820
|
+
def from_dict(cls, raw: Dict[str, Any]) -> WorkflowPage:
|
|
821
|
+
return cls(
|
|
822
|
+
data=[Workflow.from_dict(w) for w in (raw.get("data") or [])],
|
|
823
|
+
page=PageInfo.from_dict(raw.get("page") or {}),
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
@dataclass
|
|
828
|
+
class GraphPosition:
|
|
829
|
+
"""Canvas position of a graph node."""
|
|
830
|
+
|
|
831
|
+
x: float = 0.0
|
|
832
|
+
y: float = 0.0
|
|
833
|
+
|
|
834
|
+
@classmethod
|
|
835
|
+
def from_dict(cls, data: Dict[str, Any]) -> GraphPosition:
|
|
836
|
+
return cls(x=data.get("x", 0.0), y=data.get("y", 0.0))
|
|
837
|
+
|
|
838
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
839
|
+
return {"x": self.x, "y": self.y}
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
@dataclass
|
|
843
|
+
class GraphNode:
|
|
844
|
+
"""A node of a workflow version's graph.
|
|
845
|
+
|
|
846
|
+
``id`` is the server-assigned node UUID (graph-local, not an API resource
|
|
847
|
+
id); ``data`` is the raw node configuration document exactly as stored.
|
|
848
|
+
"""
|
|
849
|
+
|
|
850
|
+
id: str
|
|
851
|
+
type: str
|
|
852
|
+
label: str = ""
|
|
853
|
+
position: GraphPosition = field(default_factory=GraphPosition)
|
|
854
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
855
|
+
|
|
856
|
+
@classmethod
|
|
857
|
+
def from_dict(cls, data: Dict[str, Any]) -> GraphNode:
|
|
858
|
+
return cls(
|
|
859
|
+
id=data["id"],
|
|
860
|
+
type=data["type"],
|
|
861
|
+
label=data.get("label", ""),
|
|
862
|
+
position=GraphPosition.from_dict(data.get("position") or {}),
|
|
863
|
+
data=data.get("data") or {},
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
@dataclass
|
|
868
|
+
class GraphEdge:
|
|
869
|
+
"""An edge of a workflow version's graph.
|
|
870
|
+
|
|
871
|
+
``edge_type``: ``parallel`` edges drive execution flow; ``tool`` edges
|
|
872
|
+
attach tool / sub-agent nodes to an agent; ``error`` edges route
|
|
873
|
+
failures.
|
|
874
|
+
"""
|
|
875
|
+
|
|
876
|
+
id: str
|
|
877
|
+
source: str
|
|
878
|
+
target: str
|
|
879
|
+
edge_type: str
|
|
880
|
+
source_handle: Optional[str] = None
|
|
881
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
882
|
+
|
|
883
|
+
@classmethod
|
|
884
|
+
def from_dict(cls, data: Dict[str, Any]) -> GraphEdge:
|
|
885
|
+
return cls(
|
|
886
|
+
id=data["id"],
|
|
887
|
+
source=data["source"],
|
|
888
|
+
target=data["target"],
|
|
889
|
+
edge_type=data["edge_type"],
|
|
890
|
+
source_handle=data.get("source_handle"),
|
|
891
|
+
data=data.get("data") or {},
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
@dataclass
|
|
896
|
+
class WorkflowGraph:
|
|
897
|
+
"""The graph of a workflow's presented (active) version."""
|
|
898
|
+
|
|
899
|
+
workflow_version: str
|
|
900
|
+
nodes: List[GraphNode] = field(default_factory=list)
|
|
901
|
+
edges: List[GraphEdge] = field(default_factory=list)
|
|
902
|
+
|
|
903
|
+
@classmethod
|
|
904
|
+
def from_dict(cls, data: Dict[str, Any]) -> WorkflowGraph:
|
|
905
|
+
return cls(
|
|
906
|
+
workflow_version=data["workflow_version"],
|
|
907
|
+
nodes=[GraphNode.from_dict(n) for n in (data.get("nodes") or [])],
|
|
908
|
+
edges=[GraphEdge.from_dict(e) for e in (data.get("edges") or [])],
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
@dataclass
|
|
913
|
+
class GraphNodeInput:
|
|
914
|
+
"""Node input for ``workflows.set_graph``.
|
|
915
|
+
|
|
916
|
+
``id`` is a client-chosen key, unique within the request; edges reference
|
|
917
|
+
nodes by these keys. ``data`` omitted means the server defaults for the
|
|
918
|
+
node type.
|
|
919
|
+
"""
|
|
920
|
+
|
|
921
|
+
type: str
|
|
922
|
+
id: Optional[str] = None
|
|
923
|
+
label: Optional[str] = None
|
|
924
|
+
position: Optional[GraphPosition] = None
|
|
925
|
+
data: Optional[Dict[str, Any]] = None
|
|
926
|
+
|
|
927
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
928
|
+
d: Dict[str, Any] = {"type": self.type}
|
|
929
|
+
if self.id is not None:
|
|
930
|
+
d["id"] = self.id
|
|
931
|
+
if self.label is not None:
|
|
932
|
+
d["label"] = self.label
|
|
933
|
+
if self.position is not None:
|
|
934
|
+
d["position"] = self.position.to_dict()
|
|
935
|
+
if self.data is not None:
|
|
936
|
+
d["data"] = self.data
|
|
937
|
+
return d
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
@dataclass
|
|
941
|
+
class GraphEdgeInput:
|
|
942
|
+
"""Edge input for ``workflows.set_graph``.
|
|
943
|
+
|
|
944
|
+
``source``/``target`` are client node keys (``nodes[].id``). ``edge_type``
|
|
945
|
+
defaults to ``tool`` when the target is a tool node, else ``parallel``.
|
|
946
|
+
"""
|
|
947
|
+
|
|
948
|
+
source: str
|
|
949
|
+
target: str
|
|
950
|
+
edge_type: Optional[str] = None
|
|
951
|
+
source_handle: Optional[str] = None
|
|
952
|
+
data: Optional[Dict[str, Any]] = None
|
|
953
|
+
|
|
954
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
955
|
+
d: Dict[str, Any] = {"source": self.source, "target": self.target}
|
|
956
|
+
if self.edge_type is not None:
|
|
957
|
+
d["edge_type"] = self.edge_type
|
|
958
|
+
if self.source_handle is not None:
|
|
959
|
+
d["source_handle"] = self.source_handle
|
|
960
|
+
if self.data is not None:
|
|
961
|
+
d["data"] = self.data
|
|
962
|
+
return d
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
@dataclass
|
|
966
|
+
class WorkflowGraphUpdate:
|
|
967
|
+
"""Result of ``workflows.set_graph``: the new version and node id map."""
|
|
968
|
+
|
|
969
|
+
workflow_version: str
|
|
970
|
+
node_ids: Dict[str, str] = field(default_factory=dict)
|
|
971
|
+
|
|
972
|
+
@classmethod
|
|
973
|
+
def from_dict(cls, data: Dict[str, Any]) -> WorkflowGraphUpdate:
|
|
974
|
+
return cls(
|
|
975
|
+
workflow_version=data["workflow_version"],
|
|
976
|
+
node_ids=data.get("node_ids") or {},
|
|
977
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Webhook notification helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def notify(webhook_url: str, data: Any) -> None:
|
|
9
|
+
"""POST data to a webhook URL as JSON.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
webhook_url: The URL to POST to.
|
|
13
|
+
data: Any JSON-serialisable payload.
|
|
14
|
+
"""
|
|
15
|
+
import urllib.request, json as _json
|
|
16
|
+
body = _json.dumps(data).encode()
|
|
17
|
+
req = urllib.request.Request(
|
|
18
|
+
webhook_url,
|
|
19
|
+
data=body,
|
|
20
|
+
headers={"Content-Type": "application/json"},
|
|
21
|
+
method="POST",
|
|
22
|
+
)
|
|
23
|
+
with urllib.request.urlopen(req, timeout=10):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def async_notify(webhook_url: str, data: Any) -> None:
|
|
28
|
+
"""Async POST data to a webhook URL as JSON.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
webhook_url: The URL to POST to.
|
|
32
|
+
data: Any JSON-serialisable payload.
|
|
33
|
+
"""
|
|
34
|
+
import json as _json
|
|
35
|
+
try:
|
|
36
|
+
import aiohttp
|
|
37
|
+
async with aiohttp.ClientSession() as session:
|
|
38
|
+
await session.post(webhook_url, json=data, timeout=aiohttp.ClientTimeout(total=10))
|
|
39
|
+
except ImportError:
|
|
40
|
+
import asyncio
|
|
41
|
+
loop = asyncio.get_event_loop()
|
|
42
|
+
await loop.run_in_executor(None, notify, webhook_url, data)
|