kailash 0.2.0__py3-none-any.whl → 0.2.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/mcp/server_new.py +6 -6
- kailash/nodes/data/__init__.py +1 -2
- kailash/nodes/data/sql.py +699 -256
- kailash/workflow/cycle_analyzer.py +346 -225
- kailash/workflow/cycle_builder.py +75 -69
- kailash/workflow/cycle_config.py +62 -46
- kailash/workflow/cycle_debugger.py +284 -184
- kailash/workflow/cycle_exceptions.py +111 -97
- kailash/workflow/cycle_profiler.py +272 -202
- kailash/workflow/migration.py +238 -197
- kailash/workflow/templates.py +124 -105
- kailash/workflow/validation.py +356 -298
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/METADATA +4 -1
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/RECORD +18 -18
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.0.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ full backward compatibility with existing workflow construction methods.
|
|
8
8
|
|
9
9
|
Examples:
|
10
10
|
Basic cycle creation:
|
11
|
-
|
11
|
+
|
12
12
|
>>> workflow = Workflow("optimization", "Optimization Loop")
|
13
13
|
>>> cycle = workflow.create_cycle("quality_improvement")
|
14
14
|
>>> cycle.connect("processor", "evaluator") \
|
@@ -16,9 +16,9 @@ Examples:
|
|
16
16
|
... .converge_when("quality > 0.9") \
|
17
17
|
... .timeout(300) \
|
18
18
|
... .build()
|
19
|
-
|
19
|
+
|
20
20
|
Advanced configuration:
|
21
|
-
|
21
|
+
|
22
22
|
>>> cycle = workflow.create_cycle("complex_optimization") \
|
23
23
|
... .connect("optimizer", "evaluator", {"result": "input_data"}) \
|
24
24
|
... .max_iterations(100) \
|
@@ -28,9 +28,9 @@ Examples:
|
|
28
28
|
... .when("needs_optimization == True") \
|
29
29
|
... .nested_in("outer_cycle") \
|
30
30
|
... .build()
|
31
|
-
|
31
|
+
|
32
32
|
Template integration:
|
33
|
-
|
33
|
+
|
34
34
|
>>> from kailash.workflow.cycle_config import CycleTemplates
|
35
35
|
>>> config = CycleTemplates.optimization_loop(max_iterations=50)
|
36
36
|
>>> cycle = CycleBuilder.from_config(workflow, config) \
|
@@ -40,17 +40,17 @@ Examples:
|
|
40
40
|
"""
|
41
41
|
|
42
42
|
import logging
|
43
|
-
from typing import Dict, Optional
|
43
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
44
44
|
|
45
45
|
from kailash.sdk_exceptions import WorkflowValidationError
|
46
46
|
from kailash.workflow.cycle_exceptions import (
|
47
47
|
CycleConfigurationError,
|
48
|
-
CycleConnectionError
|
48
|
+
CycleConnectionError,
|
49
49
|
)
|
50
50
|
|
51
51
|
if TYPE_CHECKING:
|
52
|
-
from .graph import Workflow
|
53
52
|
from .cycle_config import CycleConfig
|
53
|
+
from .graph import Workflow
|
54
54
|
|
55
55
|
logger = logging.getLogger(__name__)
|
56
56
|
|
@@ -59,13 +59,13 @@ class CycleBuilder:
|
|
59
59
|
"""
|
60
60
|
Fluent builder for creating cyclic workflow connections.
|
61
61
|
|
62
|
-
This class provides an intuitive, chainable API for configuring cyclic
|
62
|
+
This class provides an intuitive, chainable API for configuring cyclic
|
63
63
|
connections in workflows. It replaces the verbose parameter-heavy approach
|
64
64
|
with a more discoverable and type-safe builder pattern.
|
65
65
|
|
66
66
|
Examples:
|
67
67
|
Creating a basic cycle:
|
68
|
-
|
68
|
+
|
69
69
|
>>> workflow = Workflow("optimization", "Optimization Loop")
|
70
70
|
>>> cycle = workflow.create_cycle("quality_improvement")
|
71
71
|
>>> cycle.connect("processor", "evaluator") \
|
@@ -85,12 +85,12 @@ class CycleBuilder:
|
|
85
85
|
"""
|
86
86
|
self._workflow = workflow
|
87
87
|
self._cycle_id = cycle_id
|
88
|
-
|
88
|
+
|
89
89
|
# Connection parameters
|
90
90
|
self._source_node: Optional[str] = None
|
91
91
|
self._target_node: Optional[str] = None
|
92
92
|
self._mapping: Optional[Dict[str, str]] = None
|
93
|
-
|
93
|
+
|
94
94
|
# Cycle parameters
|
95
95
|
self._max_iterations: Optional[int] = None
|
96
96
|
self._convergence_check: Optional[str] = None
|
@@ -99,20 +99,24 @@ class CycleBuilder:
|
|
99
99
|
self._condition: Optional[str] = None
|
100
100
|
self._parent_cycle: Optional[str] = None
|
101
101
|
|
102
|
-
def connect(
|
103
|
-
|
102
|
+
def connect(
|
103
|
+
self,
|
104
|
+
source_node: str,
|
105
|
+
target_node: str,
|
106
|
+
mapping: Optional[Dict[str, str]] = None,
|
107
|
+
) -> "CycleBuilder":
|
104
108
|
"""
|
105
109
|
Configure the source and target nodes for the cycle connection.
|
106
110
|
|
107
|
-
Establishes which nodes will be connected in a cyclic pattern. The
|
108
|
-
mapping parameter defines how outputs from the source node map to
|
111
|
+
Establishes which nodes will be connected in a cyclic pattern. The
|
112
|
+
mapping parameter defines how outputs from the source node map to
|
109
113
|
inputs of the target node.
|
110
114
|
|
111
115
|
Args:
|
112
116
|
source_node: Node ID that produces output for the cycle.
|
113
117
|
target_node: Node ID that receives input from the cycle.
|
114
|
-
mapping: Output-to-input mapping. Keys are source output fields,
|
115
|
-
values are target input fields. If None, attempts automatic
|
118
|
+
mapping: Output-to-input mapping. Keys are source output fields,
|
119
|
+
values are target input fields. If None, attempts automatic
|
116
120
|
mapping based on parameter names.
|
117
121
|
|
118
122
|
Returns:
|
@@ -129,27 +133,27 @@ class CycleBuilder:
|
|
129
133
|
"""
|
130
134
|
# Validate nodes exist in workflow
|
131
135
|
available_nodes = list(self._workflow.nodes.keys())
|
132
|
-
|
136
|
+
|
133
137
|
if source_node not in self._workflow.nodes:
|
134
138
|
raise CycleConnectionError(
|
135
139
|
f"Source node '{source_node}' not found in workflow",
|
136
140
|
source_node=source_node,
|
137
141
|
available_nodes=available_nodes,
|
138
|
-
error_code="CYCLE_CONN_001"
|
142
|
+
error_code="CYCLE_CONN_001",
|
139
143
|
)
|
140
|
-
|
144
|
+
|
141
145
|
if target_node not in self._workflow.nodes:
|
142
146
|
raise CycleConnectionError(
|
143
147
|
f"Target node '{target_node}' not found in workflow",
|
144
148
|
target_node=target_node,
|
145
149
|
available_nodes=available_nodes,
|
146
|
-
error_code="CYCLE_CONN_002"
|
150
|
+
error_code="CYCLE_CONN_002",
|
147
151
|
)
|
148
152
|
|
149
153
|
self._source_node = source_node
|
150
154
|
self._target_node = target_node
|
151
155
|
self._mapping = mapping
|
152
|
-
|
156
|
+
|
153
157
|
return self
|
154
158
|
|
155
159
|
def max_iterations(self, iterations: int) -> "CycleBuilder":
|
@@ -160,7 +164,7 @@ class CycleBuilder:
|
|
160
164
|
This is a critical safety mechanism for production workflows.
|
161
165
|
|
162
166
|
Args:
|
163
|
-
iterations: Maximum number of iterations allowed. Must be positive.
|
167
|
+
iterations: Maximum number of iterations allowed. Must be positive.
|
164
168
|
Recommended range: 10-1000 depending on use case.
|
165
169
|
|
166
170
|
Returns:
|
@@ -182,10 +186,10 @@ class CycleBuilder:
|
|
182
186
|
suggestions=[
|
183
187
|
"Use 10-100 iterations for quick convergence",
|
184
188
|
"Use 100-1000 iterations for complex optimization",
|
185
|
-
"Consider adding convergence_check for early termination"
|
186
|
-
]
|
189
|
+
"Consider adding convergence_check for early termination",
|
190
|
+
],
|
187
191
|
)
|
188
|
-
|
192
|
+
|
189
193
|
self._max_iterations = iterations
|
190
194
|
return self
|
191
195
|
|
@@ -193,8 +197,8 @@ class CycleBuilder:
|
|
193
197
|
"""
|
194
198
|
Set the convergence condition to terminate the cycle early.
|
195
199
|
|
196
|
-
Defines an expression that, when true, will stop cycle execution
|
197
|
-
before reaching max_iterations. This enables efficient early
|
200
|
+
Defines an expression that, when true, will stop cycle execution
|
201
|
+
before reaching max_iterations. This enables efficient early
|
198
202
|
termination when the desired result is achieved.
|
199
203
|
|
200
204
|
Args:
|
@@ -218,16 +222,16 @@ class CycleBuilder:
|
|
218
222
|
"Convergence condition must be a non-empty string expression. "
|
219
223
|
"Examples: 'error < 0.01', 'quality > 0.9', 'count >= 10'"
|
220
224
|
)
|
221
|
-
|
225
|
+
|
222
226
|
# Basic validation - check for dangerous operations
|
223
|
-
dangerous_patterns = [
|
227
|
+
dangerous_patterns = ["import ", "exec(", "eval(", "__"]
|
224
228
|
for pattern in dangerous_patterns:
|
225
229
|
if pattern in condition:
|
226
230
|
raise CycleConfigurationError(
|
227
231
|
f"Convergence condition contains potentially unsafe operation: '{pattern}'. "
|
228
232
|
"Use simple comparison expressions only."
|
229
233
|
)
|
230
|
-
|
234
|
+
|
231
235
|
self._convergence_check = condition
|
232
236
|
return self
|
233
237
|
|
@@ -235,12 +239,12 @@ class CycleBuilder:
|
|
235
239
|
"""
|
236
240
|
Set a timeout limit for cycle execution.
|
237
241
|
|
238
|
-
Provides time-based safety limit to prevent cycles from running
|
239
|
-
indefinitely. Useful for cycles that might have unpredictable
|
242
|
+
Provides time-based safety limit to prevent cycles from running
|
243
|
+
indefinitely. Useful for cycles that might have unpredictable
|
240
244
|
convergence times.
|
241
245
|
|
242
246
|
Args:
|
243
|
-
seconds: Maximum execution time in seconds. Must be positive.
|
247
|
+
seconds: Maximum execution time in seconds. Must be positive.
|
244
248
|
Recommended: 30-3600 seconds.
|
245
249
|
|
246
250
|
Returns:
|
@@ -259,7 +263,7 @@ class CycleBuilder:
|
|
259
263
|
"Recommendation: Use 30-300 seconds for most cycles, "
|
260
264
|
"up to 3600 seconds for long-running optimization."
|
261
265
|
)
|
262
|
-
|
266
|
+
|
263
267
|
self._timeout = seconds
|
264
268
|
return self
|
265
269
|
|
@@ -271,7 +275,7 @@ class CycleBuilder:
|
|
271
275
|
excessive memory through data accumulation across iterations.
|
272
276
|
|
273
277
|
Args:
|
274
|
-
mb: Maximum memory usage in megabytes. Must be positive.
|
278
|
+
mb: Maximum memory usage in megabytes. Must be positive.
|
275
279
|
Recommended: 100-10000 MB.
|
276
280
|
|
277
281
|
Returns:
|
@@ -290,7 +294,7 @@ class CycleBuilder:
|
|
290
294
|
"Recommendation: Use 100-1000 MB for most cycles, "
|
291
295
|
"up to 10000 MB for data-intensive processing."
|
292
296
|
)
|
293
|
-
|
297
|
+
|
294
298
|
self._memory_limit = mb
|
295
299
|
return self
|
296
300
|
|
@@ -298,7 +302,7 @@ class CycleBuilder:
|
|
298
302
|
"""
|
299
303
|
Set a conditional expression for cycle routing.
|
300
304
|
|
301
|
-
Enables conditional cycle execution where the cycle only runs
|
305
|
+
Enables conditional cycle execution where the cycle only runs
|
302
306
|
when the specified condition is met. Useful for adaptive workflows.
|
303
307
|
|
304
308
|
Args:
|
@@ -320,7 +324,7 @@ class CycleBuilder:
|
|
320
324
|
"Condition must be a non-empty string expression. "
|
321
325
|
"Examples: 'retry_count < 3', 'needs_improvement == True'"
|
322
326
|
)
|
323
|
-
|
327
|
+
|
324
328
|
self._condition = condition
|
325
329
|
return self
|
326
330
|
|
@@ -328,8 +332,8 @@ class CycleBuilder:
|
|
328
332
|
"""
|
329
333
|
Make this cycle nested within another cycle.
|
330
334
|
|
331
|
-
Enables hierarchical cycle structures where one cycle operates
|
332
|
-
within the iterations of a parent cycle. Useful for multi-level
|
335
|
+
Enables hierarchical cycle structures where one cycle operates
|
336
|
+
within the iterations of a parent cycle. Useful for multi-level
|
333
337
|
optimization scenarios.
|
334
338
|
|
335
339
|
Args:
|
@@ -345,10 +349,8 @@ class CycleBuilder:
|
|
345
349
|
>>> cycle.nested_in("outer_optimization") # This cycle runs inside outer_optimization
|
346
350
|
"""
|
347
351
|
if not parent_cycle_id or not isinstance(parent_cycle_id, str):
|
348
|
-
raise CycleConfigurationError(
|
349
|
-
|
350
|
-
)
|
351
|
-
|
352
|
+
raise CycleConfigurationError("Parent cycle ID must be a non-empty string")
|
353
|
+
|
352
354
|
self._parent_cycle = parent_cycle_id
|
353
355
|
return self
|
354
356
|
|
@@ -356,8 +358,8 @@ class CycleBuilder:
|
|
356
358
|
"""
|
357
359
|
Build and add the configured cycle to the workflow.
|
358
360
|
|
359
|
-
Validates the cycle configuration and creates the actual cyclic
|
360
|
-
connection in the workflow. This finalizes the cycle builder
|
361
|
+
Validates the cycle configuration and creates the actual cyclic
|
362
|
+
connection in the workflow. This finalizes the cycle builder
|
361
363
|
pattern and applies all configured settings.
|
362
364
|
|
363
365
|
Raises:
|
@@ -376,15 +378,19 @@ class CycleBuilder:
|
|
376
378
|
"Cycle must have source and target nodes configured. "
|
377
379
|
"Call connect(source_node, target_node) before build()."
|
378
380
|
)
|
379
|
-
|
381
|
+
|
380
382
|
# Validate at least one termination condition
|
381
|
-
if
|
383
|
+
if (
|
384
|
+
not self._max_iterations
|
385
|
+
and not self._convergence_check
|
386
|
+
and not self._timeout
|
387
|
+
):
|
382
388
|
raise CycleConfigurationError(
|
383
389
|
"Cycle must have at least one termination condition. "
|
384
390
|
"Add max_iterations(), converge_when(), or timeout() before build(). "
|
385
391
|
"Recommendation: Always include max_iterations() as a safety net."
|
386
392
|
)
|
387
|
-
|
393
|
+
|
388
394
|
# Create the connection using the workflow's connect method
|
389
395
|
try:
|
390
396
|
self._workflow.connect(
|
@@ -398,16 +404,16 @@ class CycleBuilder:
|
|
398
404
|
timeout=self._timeout,
|
399
405
|
memory_limit=self._memory_limit,
|
400
406
|
condition=self._condition,
|
401
|
-
parent_cycle=self._parent_cycle
|
407
|
+
parent_cycle=self._parent_cycle,
|
402
408
|
)
|
403
|
-
|
409
|
+
|
404
410
|
logger.info(
|
405
411
|
f"Created cycle '{self._cycle_id or 'unnamed'}' from "
|
406
412
|
f"{self._source_node} to {self._target_node} with "
|
407
413
|
f"max_iterations={self._max_iterations}, "
|
408
414
|
f"convergence='{self._convergence_check}'"
|
409
415
|
)
|
410
|
-
|
416
|
+
|
411
417
|
except Exception as e:
|
412
418
|
raise WorkflowValidationError(
|
413
419
|
f"Failed to create cycle connection: {e}"
|
@@ -436,13 +442,13 @@ class CycleBuilder:
|
|
436
442
|
|
437
443
|
Examples:
|
438
444
|
Using a template:
|
439
|
-
|
445
|
+
|
440
446
|
>>> config = CycleTemplates.optimization_loop(max_iterations=50)
|
441
447
|
>>> builder = CycleBuilder.from_config(workflow, config)
|
442
448
|
>>> builder.connect("optimizer", "evaluator").build()
|
443
|
-
|
449
|
+
|
444
450
|
Using custom configuration:
|
445
|
-
|
451
|
+
|
446
452
|
>>> config = CycleConfig(max_iterations=100, timeout=300)
|
447
453
|
>>> builder = CycleBuilder.from_config(workflow, config)
|
448
454
|
>>> builder.connect("processor", "evaluator").build()
|
@@ -463,27 +469,27 @@ class CycleBuilder:
|
|
463
469
|
|
464
470
|
# Create builder with config values
|
465
471
|
builder = cls(workflow=workflow, cycle_id=config.cycle_id)
|
466
|
-
|
472
|
+
|
467
473
|
# Apply configuration parameters
|
468
474
|
if config.max_iterations is not None:
|
469
475
|
builder._max_iterations = config.max_iterations
|
470
|
-
|
476
|
+
|
471
477
|
if config.convergence_check is not None:
|
472
478
|
if isinstance(config.convergence_check, str):
|
473
479
|
builder._convergence_check = config.convergence_check
|
474
480
|
else:
|
475
481
|
# For callable convergence checks, convert to description
|
476
482
|
builder._convergence_check = "<callable_convergence_check>"
|
477
|
-
|
483
|
+
|
478
484
|
if config.timeout is not None:
|
479
485
|
builder._timeout = config.timeout
|
480
|
-
|
486
|
+
|
481
487
|
if config.memory_limit is not None:
|
482
488
|
builder._memory_limit = config.memory_limit
|
483
|
-
|
489
|
+
|
484
490
|
if config.condition is not None:
|
485
491
|
builder._condition = config.condition
|
486
|
-
|
492
|
+
|
487
493
|
if config.parent_cycle is not None:
|
488
494
|
builder._parent_cycle = config.parent_cycle
|
489
495
|
|
@@ -528,20 +534,20 @@ class CycleBuilder:
|
|
528
534
|
# Apply non-None configuration values
|
529
535
|
if config.max_iterations is not None:
|
530
536
|
self._max_iterations = config.max_iterations
|
531
|
-
|
537
|
+
|
532
538
|
if config.convergence_check is not None:
|
533
539
|
if isinstance(config.convergence_check, str):
|
534
540
|
self._convergence_check = config.convergence_check
|
535
|
-
|
541
|
+
|
536
542
|
if config.timeout is not None:
|
537
543
|
self._timeout = config.timeout
|
538
|
-
|
544
|
+
|
539
545
|
if config.memory_limit is not None:
|
540
546
|
self._memory_limit = config.memory_limit
|
541
|
-
|
547
|
+
|
542
548
|
if config.condition is not None:
|
543
549
|
self._condition = config.condition
|
544
|
-
|
550
|
+
|
545
551
|
if config.parent_cycle is not None:
|
546
552
|
self._parent_cycle = config.parent_cycle
|
547
553
|
|
@@ -570,4 +576,4 @@ class CycleBuilder:
|
|
570
576
|
f"max_iterations={self._max_iterations}, "
|
571
577
|
f"convergence='{self._convergence_check}'"
|
572
578
|
f")"
|
573
|
-
)
|
579
|
+
)
|