kailash 0.8.0__py3-none-any.whl → 0.8.1__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.
- kailash/__init__.py +1 -1
- kailash/middleware/communication/realtime.py +50 -29
- kailash/nodes/code/python.py +5 -0
- kailash/workflow/visualization.py +36 -18
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/METADATA +2 -1
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/RECORD +10 -10
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/WHEEL +0 -0
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.8.0.dist-info → kailash-0.8.1.dist-info}/top_level.txt +0 -0
kailash/__init__.py
CHANGED
@@ -177,87 +177,108 @@ class ConnectionManager:
|
|
177
177
|
"active_users": len(self.user_connections),
|
178
178
|
"total_messages_sent": total_messages,
|
179
179
|
}
|
180
|
-
|
181
|
-
def filter_events(
|
180
|
+
|
181
|
+
def filter_events(
|
182
|
+
self, events: List[BaseEvent], event_filter: EventFilter = None
|
183
|
+
) -> List[BaseEvent]:
|
182
184
|
"""Filter events based on event filter criteria."""
|
183
185
|
if not event_filter:
|
184
186
|
return events
|
185
|
-
|
187
|
+
|
186
188
|
filtered = []
|
187
189
|
for event in events:
|
188
190
|
# Apply session filter
|
189
|
-
if event_filter.session_id and hasattr(event,
|
191
|
+
if event_filter.session_id and hasattr(event, "session_id"):
|
190
192
|
if event.session_id != event_filter.session_id:
|
191
193
|
continue
|
192
|
-
|
194
|
+
|
193
195
|
# Apply user filter
|
194
|
-
if event_filter.user_id and hasattr(event,
|
196
|
+
if event_filter.user_id and hasattr(event, "user_id"):
|
195
197
|
if event.user_id != event_filter.user_id:
|
196
198
|
continue
|
197
|
-
|
199
|
+
|
198
200
|
# Apply event type filter
|
199
|
-
if
|
201
|
+
if (
|
202
|
+
event_filter.event_types
|
203
|
+
and event.event_type not in event_filter.event_types
|
204
|
+
):
|
200
205
|
continue
|
201
|
-
|
206
|
+
|
202
207
|
filtered.append(event)
|
203
|
-
|
208
|
+
|
204
209
|
return filtered
|
205
|
-
|
210
|
+
|
206
211
|
def set_event_filter(self, connection_id: str, event_filter: EventFilter):
|
207
212
|
"""Set event filter for a specific connection."""
|
208
213
|
if connection_id in self.connections:
|
209
214
|
self.connections[connection_id]["event_filter"] = event_filter
|
210
|
-
|
215
|
+
|
211
216
|
def get_event_filter(self, connection_id: str) -> Optional[EventFilter]:
|
212
217
|
"""Get event filter for a specific connection."""
|
213
218
|
if connection_id in self.connections:
|
214
219
|
return self.connections[connection_id].get("event_filter")
|
215
220
|
return None
|
216
|
-
|
221
|
+
|
217
222
|
# Alias methods for compatibility
|
218
|
-
def event_filter(
|
223
|
+
def event_filter(
|
224
|
+
self, events: List[BaseEvent], filter_criteria: EventFilter = None
|
225
|
+
) -> List[BaseEvent]:
|
219
226
|
"""Alias for filter_events method."""
|
220
227
|
return self.filter_events(events, filter_criteria)
|
221
|
-
|
228
|
+
|
222
229
|
async def on_event(self, event: BaseEvent):
|
223
230
|
"""Handle incoming event - route to appropriate connections."""
|
224
231
|
await self.handle_event(event)
|
225
|
-
|
232
|
+
|
226
233
|
async def handle_event(self, event: BaseEvent):
|
227
234
|
"""Handle and route event to matching connections."""
|
228
235
|
await self.process_event(event)
|
229
|
-
|
236
|
+
|
230
237
|
async def process_event(self, event: BaseEvent):
|
231
238
|
"""Process event and broadcast to matching connections."""
|
232
239
|
message = {
|
233
240
|
"type": "event",
|
234
|
-
"event_type":
|
241
|
+
"event_type": (
|
242
|
+
event.event_type.value
|
243
|
+
if hasattr(event.event_type, "value")
|
244
|
+
else str(event.event_type)
|
245
|
+
),
|
235
246
|
"data": event.data,
|
236
|
-
"timestamp":
|
237
|
-
|
238
|
-
|
247
|
+
"timestamp": (
|
248
|
+
event.timestamp.isoformat()
|
249
|
+
if hasattr(event, "timestamp")
|
250
|
+
else datetime.now(timezone.utc).isoformat()
|
251
|
+
),
|
252
|
+
"session_id": getattr(event, "session_id", None),
|
253
|
+
"user_id": getattr(event, "user_id", None),
|
239
254
|
}
|
240
|
-
|
255
|
+
|
241
256
|
# Broadcast to all matching connections
|
242
257
|
for connection_id, connection in self.connections.items():
|
243
258
|
event_filter = connection.get("event_filter")
|
244
|
-
|
259
|
+
|
245
260
|
# Check if this connection should receive this event
|
246
261
|
should_send = True
|
247
262
|
if event_filter:
|
248
263
|
# Apply session filter
|
249
|
-
if
|
264
|
+
if (
|
265
|
+
event_filter.session_id
|
266
|
+
and connection["session_id"] != event_filter.session_id
|
267
|
+
):
|
250
268
|
should_send = False
|
251
|
-
|
269
|
+
|
252
270
|
# Apply user filter
|
253
|
-
if
|
271
|
+
if (
|
272
|
+
event_filter.user_id
|
273
|
+
and connection["user_id"] != event_filter.user_id
|
274
|
+
):
|
254
275
|
should_send = False
|
255
|
-
|
276
|
+
|
256
277
|
# Apply event type filter
|
257
|
-
if hasattr(event_filter,
|
278
|
+
if hasattr(event_filter, "event_types") and event_filter.event_types:
|
258
279
|
if event.event_type not in event_filter.event_types:
|
259
280
|
should_send = False
|
260
|
-
|
281
|
+
|
261
282
|
if should_send:
|
262
283
|
await self.send_to_connection(connection_id, message)
|
263
284
|
|
kailash/nodes/code/python.py
CHANGED
@@ -50,6 +50,7 @@ import ast
|
|
50
50
|
import importlib.util
|
51
51
|
import inspect
|
52
52
|
import logging
|
53
|
+
import os
|
53
54
|
import resource
|
54
55
|
import traceback
|
55
56
|
from collections.abc import Callable
|
@@ -465,6 +466,10 @@ class CodeExecutor:
|
|
465
466
|
# Normal operation - eagerly load all modules
|
466
467
|
for module_name in self.allowed_modules:
|
467
468
|
try:
|
469
|
+
# Skip scipy in CI due to version conflicts
|
470
|
+
if module_name == "scipy" and os.environ.get("CI"):
|
471
|
+
logger.warning("Skipping scipy import in CI environment")
|
472
|
+
continue
|
468
473
|
module = importlib.import_module(module_name)
|
469
474
|
namespace[module_name] = module
|
470
475
|
except ImportError:
|
@@ -70,7 +70,7 @@ class WorkflowVisualizer:
|
|
70
70
|
"""Get colors for all nodes in workflow."""
|
71
71
|
colors = []
|
72
72
|
for node_id in self.workflow.graph.nodes():
|
73
|
-
node_instance =
|
73
|
+
node_instance = self.workflow.nodes.get(node_id)
|
74
74
|
if node_instance:
|
75
75
|
node_type = node_instance.node_type
|
76
76
|
colors.append(self._get_node_color(node_type))
|
@@ -122,12 +122,14 @@ class WorkflowVisualizer:
|
|
122
122
|
|
123
123
|
return edge_labels
|
124
124
|
|
125
|
-
def _calculate_layout(
|
125
|
+
def _calculate_layout(
|
126
|
+
self, workflow: "Workflow" = None
|
127
|
+
) -> dict[str, tuple[float, float]]:
|
126
128
|
"""Calculate node positions for visualization."""
|
127
129
|
target_workflow = workflow or self.workflow
|
128
130
|
if not target_workflow:
|
129
131
|
return {}
|
130
|
-
|
132
|
+
|
131
133
|
# Try to use stored positions first
|
132
134
|
pos = {}
|
133
135
|
for node_id, node_instance in target_workflow.nodes.items():
|
@@ -154,8 +156,10 @@ class WorkflowVisualizer:
|
|
154
156
|
pos = nx.spring_layout(target_workflow.graph)
|
155
157
|
|
156
158
|
return pos
|
157
|
-
|
158
|
-
def _get_layout_positions(
|
159
|
+
|
160
|
+
def _get_layout_positions(
|
161
|
+
self, workflow: Workflow
|
162
|
+
) -> dict[str, tuple[float, float]]:
|
159
163
|
"""Get layout positions for workflow nodes."""
|
160
164
|
# Temporarily store workflow and calculate layout
|
161
165
|
original_workflow = self.workflow
|
@@ -164,7 +168,7 @@ class WorkflowVisualizer:
|
|
164
168
|
return self._calculate_layout()
|
165
169
|
finally:
|
166
170
|
self.workflow = original_workflow
|
167
|
-
|
171
|
+
|
168
172
|
def _get_node_colors(self, workflow: Workflow) -> list[str]:
|
169
173
|
"""Get node colors for workflow."""
|
170
174
|
colors = []
|
@@ -183,17 +187,19 @@ class WorkflowVisualizer:
|
|
183
187
|
color_key = "ai"
|
184
188
|
else:
|
185
189
|
color_key = "default"
|
186
|
-
colors.append(
|
190
|
+
colors.append(
|
191
|
+
self.node_colors.get(color_key, self.node_colors["default"])
|
192
|
+
)
|
187
193
|
else:
|
188
194
|
colors.append(self.node_colors["default"])
|
189
195
|
return colors
|
190
196
|
|
191
|
-
def _create_layers(self, workflow:
|
197
|
+
def _create_layers(self, workflow: "Workflow" = None) -> dict[int, list]:
|
192
198
|
"""Create layers of nodes for hierarchical layout."""
|
193
199
|
target_workflow = workflow or self.workflow
|
194
200
|
if not target_workflow:
|
195
201
|
return {}
|
196
|
-
|
202
|
+
|
197
203
|
layers = {}
|
198
204
|
remaining = set(target_workflow.graph.nodes())
|
199
205
|
layer = 0
|
@@ -251,18 +257,22 @@ class WorkflowVisualizer:
|
|
251
257
|
target_workflow = workflow or self.workflow
|
252
258
|
if not target_workflow:
|
253
259
|
raise ValueError("No workflow provided to draw")
|
254
|
-
|
260
|
+
|
255
261
|
# Use default position if not provided
|
256
262
|
if pos is None:
|
257
263
|
pos = self._get_layout_positions(target_workflow)
|
258
|
-
|
264
|
+
|
259
265
|
# Use default colors if not provided
|
260
266
|
if node_colors is None:
|
261
267
|
node_colors = self._get_node_colors(target_workflow)
|
262
|
-
|
268
|
+
|
263
269
|
# Draw nodes
|
264
270
|
nx.draw_networkx_nodes(
|
265
|
-
target_workflow.graph,
|
271
|
+
target_workflow.graph,
|
272
|
+
pos,
|
273
|
+
node_color=node_colors,
|
274
|
+
node_size=3000,
|
275
|
+
alpha=0.9,
|
266
276
|
)
|
267
277
|
|
268
278
|
# Draw edges
|
@@ -284,7 +294,7 @@ class WorkflowVisualizer:
|
|
284
294
|
self.workflow = target_workflow
|
285
295
|
labels = self._get_node_labels()
|
286
296
|
self.workflow = old_workflow
|
287
|
-
|
297
|
+
|
288
298
|
nx.draw_networkx_labels(
|
289
299
|
target_workflow.graph, pos, labels, font_size=10, font_weight="bold"
|
290
300
|
)
|
@@ -296,7 +306,7 @@ class WorkflowVisualizer:
|
|
296
306
|
self.workflow = target_workflow
|
297
307
|
edge_labels = self._get_edge_labels()
|
298
308
|
self.workflow = old_workflow
|
299
|
-
|
309
|
+
|
300
310
|
nx.draw_networkx_edge_labels(
|
301
311
|
target_workflow.graph, pos, edge_labels, font_size=8
|
302
312
|
)
|
@@ -325,8 +335,10 @@ class WorkflowVisualizer:
|
|
325
335
|
try:
|
326
336
|
# Check if workflow is available
|
327
337
|
if not self.workflow:
|
328
|
-
raise ValueError(
|
329
|
-
|
338
|
+
raise ValueError(
|
339
|
+
"No workflow to visualize. Set workflow property or create visualizer with workflow."
|
340
|
+
)
|
341
|
+
|
330
342
|
plt.figure(figsize=figsize)
|
331
343
|
|
332
344
|
# Calculate node positions
|
@@ -342,7 +354,13 @@ class WorkflowVisualizer:
|
|
342
354
|
|
343
355
|
# Draw the graph components
|
344
356
|
if pos:
|
345
|
-
self._draw_graph(
|
357
|
+
self._draw_graph(
|
358
|
+
workflow=self.workflow,
|
359
|
+
pos=pos,
|
360
|
+
node_colors=node_colors,
|
361
|
+
show_labels=show_labels,
|
362
|
+
show_connections=show_connections,
|
363
|
+
)
|
346
364
|
|
347
365
|
# Set title
|
348
366
|
if title is None:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kailash
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.1
|
4
4
|
Summary: Python SDK for the Kailash container-node architecture
|
5
5
|
Home-page: https://github.com/integrum/kailash-python-sdk
|
6
6
|
Author: Integrum
|
@@ -58,6 +58,7 @@ Requires-Dist: python-jose>=3.5.0
|
|
58
58
|
Requires-Dist: pytest-xdist>=3.6.0
|
59
59
|
Requires-Dist: pytest-timeout>=2.3.0
|
60
60
|
Requires-Dist: pytest-split>=0.9.0
|
61
|
+
Requires-Dist: pytest-forked>=1.6.0
|
61
62
|
Requires-Dist: asyncpg>=0.30.0
|
62
63
|
Requires-Dist: aiomysql>=0.2.0
|
63
64
|
Requires-Dist: twilio>=9.6.3
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kailash/__init__.py,sha256=
|
1
|
+
kailash/__init__.py,sha256=_Mwxl0OoLmLW4_EJ4sxICIUWNEA2gG4iFinzIxnweFk,2878
|
2
2
|
kailash/__main__.py,sha256=vr7TVE5o16V6LsTmRFKG6RDKUXHpIWYdZ6Dok2HkHnI,198
|
3
3
|
kailash/access_control.py,sha256=MjKtkoQ2sg1Mgfe7ovGxVwhAbpJKvaepPWr8dxOueMA,26058
|
4
4
|
kailash/access_control_abac.py,sha256=FPfa_8PuDP3AxTjdWfiH3ntwWO8NodA0py9W8SE5dno,30263
|
@@ -85,7 +85,7 @@ kailash/middleware/communication/__init__.py,sha256=keQ2db4WI2-oZ_nJ5sLE1Tum_RkU
|
|
85
85
|
kailash/middleware/communication/ai_chat.py,sha256=2XmnJB2Nz2xm2darsF2DYnSLGLAYdyYooHok5GrHKb8,35994
|
86
86
|
kailash/middleware/communication/api_gateway.py,sha256=hjs3Ued5YhFR1LcGJEB2d-F96imIuS7W7bxctQG3blA,32272
|
87
87
|
kailash/middleware/communication/events.py,sha256=MEjgcibNyjA4tSFK8CeXDn1oCE75My7K_saxdCBz2HY,15367
|
88
|
-
kailash/middleware/communication/realtime.py,sha256=
|
88
|
+
kailash/middleware/communication/realtime.py,sha256=lg2SCASOfEk00aoXq6lCYScpb7YZYCpmLKLxiE3RHAA,28827
|
89
89
|
kailash/middleware/core/__init__.py,sha256=4yQkOWC4b88zSogs1YVqtKG1PugbncqNCwNNWxTdIZA,545
|
90
90
|
kailash/middleware/core/agent_ui.py,sha256=Ki5QpsYNM3o3h9KGor4YEXs9Xg8ehXe3w_M8SJnYhxI,36358
|
91
91
|
kailash/middleware/core/schema.py,sha256=uVF-5ZJlLYHOQdsKrG46FnTO1bq_QtDjhUSkIIDL9dY,23584
|
@@ -166,7 +166,7 @@ kailash/nodes/cache/cache_invalidation.py,sha256=IUvxrRj3K5EF29Z2EaKl7t6Uze_cssn
|
|
166
166
|
kailash/nodes/cache/redis_pool_manager.py,sha256=GR82GCWxo_gAzRE-091OB6AhKre8CTwM3OoePLb2gvE,21574
|
167
167
|
kailash/nodes/code/__init__.py,sha256=yhEwuMjUEPFfe6hMGMd4E4gZdLUuf2JEQ7knYapiM4o,1283
|
168
168
|
kailash/nodes/code/async_python.py,sha256=Ai-iMpmz-sAori73JBk0wZtqmwtmF2GNPDxqB04I2Ck,37058
|
169
|
-
kailash/nodes/code/python.py,sha256=
|
169
|
+
kailash/nodes/code/python.py,sha256=AfKNL4B-OTiFQoNuCzZ2mLiJV61TaHLXhHmXudl20eg,60592
|
170
170
|
kailash/nodes/compliance/__init__.py,sha256=6a_FL4ofc8MAVuZ-ARW5uYenZLS4mBFVM9AI2QsnoF8,214
|
171
171
|
kailash/nodes/compliance/data_retention.py,sha256=90bH_eGwlcDzUdklAJeXQM-RcuLUGQFQ5fgHOK8a4qk,69443
|
172
172
|
kailash/nodes/compliance/gdpr.py,sha256=ZMoHZjAo4QtGwtFCzGMrAUBFV3TbZOnJ5DZGZS87Bas,70548
|
@@ -337,10 +337,10 @@ kailash/workflow/safety.py,sha256=pS5GKu7UdkzFZcb16Dn-0jBxjULDU-59_M0CbUVMVyw,11
|
|
337
337
|
kailash/workflow/state.py,sha256=UTZxs5-Ona6uvBhx1__i6-RX8gB4qazkBIWE7uyRmWQ,7600
|
338
338
|
kailash/workflow/templates.py,sha256=98EN5H4fO9b4xeczk20Hu5L8hNcAuRQNGayT6vnZYCw,48540
|
339
339
|
kailash/workflow/validation.py,sha256=rWZNJYA_XAfk_og6Cxz8p1gZ3j5CylPvMTDXg2SfjgM,38574
|
340
|
-
kailash/workflow/visualization.py,sha256=
|
341
|
-
kailash-0.8.
|
342
|
-
kailash-0.8.
|
343
|
-
kailash-0.8.
|
344
|
-
kailash-0.8.
|
345
|
-
kailash-0.8.
|
346
|
-
kailash-0.8.
|
340
|
+
kailash/workflow/visualization.py,sha256=nHBW-Ai8QBMZtn2Nf3EE1_aiMGi9S6Ui_BfpA5KbJPU,23187
|
341
|
+
kailash-0.8.1.dist-info/licenses/LICENSE,sha256=Axe6g7bTrJkToK9h9j2SpRUKKNaDZDCo2lQ2zPxCE6s,1065
|
342
|
+
kailash-0.8.1.dist-info/METADATA,sha256=KBxycJ3rYfE1ygfMR8LKrzA0AoKo1C37cU3fM66UXRw,26477
|
343
|
+
kailash-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
344
|
+
kailash-0.8.1.dist-info/entry_points.txt,sha256=M_q3b8PG5W4XbhSgESzIJjh3_4OBKtZFYFsOdkr2vO4,45
|
345
|
+
kailash-0.8.1.dist-info/top_level.txt,sha256=z7GzH2mxl66498pVf5HKwo5wwfPtt9Aq95uZUpH6JV0,8
|
346
|
+
kailash-0.8.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|