kailash 0.2.0__py3-none-any.whl → 0.2.2__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.
@@ -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, TYPE_CHECKING
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(self, source_node: str, target_node: str,
103
- mapping: Optional[Dict[str, str]] = None) -> "CycleBuilder":
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 = ['import ', 'exec(', 'eval(', '__']
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
- "Parent cycle ID must be a non-empty string"
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 not self._max_iterations and not self._convergence_check and not self._timeout:
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
+ )