kailash 0.1.5__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.
Files changed (77) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/access_control.py +740 -0
  3. kailash/api/__main__.py +6 -0
  4. kailash/api/auth.py +668 -0
  5. kailash/api/custom_nodes.py +285 -0
  6. kailash/api/custom_nodes_secure.py +377 -0
  7. kailash/api/database.py +620 -0
  8. kailash/api/studio.py +915 -0
  9. kailash/api/studio_secure.py +893 -0
  10. kailash/mcp/__init__.py +53 -0
  11. kailash/mcp/__main__.py +13 -0
  12. kailash/mcp/ai_registry_server.py +712 -0
  13. kailash/mcp/client.py +447 -0
  14. kailash/mcp/client_new.py +334 -0
  15. kailash/mcp/server.py +293 -0
  16. kailash/mcp/server_new.py +336 -0
  17. kailash/mcp/servers/__init__.py +12 -0
  18. kailash/mcp/servers/ai_registry.py +289 -0
  19. kailash/nodes/__init__.py +4 -2
  20. kailash/nodes/ai/__init__.py +2 -0
  21. kailash/nodes/ai/a2a.py +714 -67
  22. kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
  23. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  24. kailash/nodes/ai/llm_agent.py +324 -1
  25. kailash/nodes/ai/self_organizing.py +5 -6
  26. kailash/nodes/base.py +15 -2
  27. kailash/nodes/base_async.py +45 -0
  28. kailash/nodes/base_cycle_aware.py +374 -0
  29. kailash/nodes/base_with_acl.py +338 -0
  30. kailash/nodes/code/python.py +135 -27
  31. kailash/nodes/data/__init__.py +1 -2
  32. kailash/nodes/data/readers.py +16 -6
  33. kailash/nodes/data/sql.py +699 -256
  34. kailash/nodes/data/writers.py +16 -6
  35. kailash/nodes/logic/__init__.py +8 -0
  36. kailash/nodes/logic/convergence.py +642 -0
  37. kailash/nodes/logic/loop.py +153 -0
  38. kailash/nodes/logic/operations.py +187 -27
  39. kailash/nodes/mixins/__init__.py +11 -0
  40. kailash/nodes/mixins/mcp.py +228 -0
  41. kailash/nodes/mixins.py +387 -0
  42. kailash/runtime/__init__.py +2 -1
  43. kailash/runtime/access_controlled.py +458 -0
  44. kailash/runtime/local.py +106 -33
  45. kailash/runtime/parallel_cyclic.py +529 -0
  46. kailash/sdk_exceptions.py +90 -5
  47. kailash/security.py +845 -0
  48. kailash/tracking/manager.py +38 -15
  49. kailash/tracking/models.py +1 -1
  50. kailash/tracking/storage/filesystem.py +30 -2
  51. kailash/utils/__init__.py +8 -0
  52. kailash/workflow/__init__.py +18 -0
  53. kailash/workflow/convergence.py +270 -0
  54. kailash/workflow/cycle_analyzer.py +889 -0
  55. kailash/workflow/cycle_builder.py +579 -0
  56. kailash/workflow/cycle_config.py +725 -0
  57. kailash/workflow/cycle_debugger.py +860 -0
  58. kailash/workflow/cycle_exceptions.py +615 -0
  59. kailash/workflow/cycle_profiler.py +741 -0
  60. kailash/workflow/cycle_state.py +338 -0
  61. kailash/workflow/cyclic_runner.py +985 -0
  62. kailash/workflow/graph.py +500 -39
  63. kailash/workflow/migration.py +809 -0
  64. kailash/workflow/safety.py +365 -0
  65. kailash/workflow/templates.py +763 -0
  66. kailash/workflow/validation.py +751 -0
  67. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/METADATA +259 -12
  68. kailash-0.2.1.dist-info/RECORD +125 -0
  69. kailash/nodes/mcp/__init__.py +0 -11
  70. kailash/nodes/mcp/client.py +0 -554
  71. kailash/nodes/mcp/resource.py +0 -682
  72. kailash/nodes/mcp/server.py +0 -577
  73. kailash-0.1.5.dist-info/RECORD +0 -88
  74. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
  75. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
  76. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
  77. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,579 @@
1
+ """
2
+ Fluent API for Cyclic Workflow Creation.
3
+
4
+ This module provides an intuitive, chainable API for creating cyclic workflows
5
+ with enhanced developer experience and IDE support. The CycleBuilder pattern
6
+ makes cycle configuration more discoverable and type-safe while maintaining
7
+ full backward compatibility with existing workflow construction methods.
8
+
9
+ Examples:
10
+ Basic cycle creation:
11
+
12
+ >>> workflow = Workflow("optimization", "Optimization Loop")
13
+ >>> cycle = workflow.create_cycle("quality_improvement")
14
+ >>> cycle.connect("processor", "evaluator") \
15
+ ... .max_iterations(50) \
16
+ ... .converge_when("quality > 0.9") \
17
+ ... .timeout(300) \
18
+ ... .build()
19
+
20
+ Advanced configuration:
21
+
22
+ >>> cycle = workflow.create_cycle("complex_optimization") \
23
+ ... .connect("optimizer", "evaluator", {"result": "input_data"}) \
24
+ ... .max_iterations(100) \
25
+ ... .converge_when("improvement < 0.001") \
26
+ ... .timeout(600) \
27
+ ... .memory_limit(2048) \
28
+ ... .when("needs_optimization == True") \
29
+ ... .nested_in("outer_cycle") \
30
+ ... .build()
31
+
32
+ Template integration:
33
+
34
+ >>> from kailash.workflow.cycle_config import CycleTemplates
35
+ >>> config = CycleTemplates.optimization_loop(max_iterations=50)
36
+ >>> cycle = CycleBuilder.from_config(workflow, config) \
37
+ ... .connect("processor", "evaluator") \
38
+ ... .timeout(120) # Override template value
39
+ ... .build()
40
+ """
41
+
42
+ import logging
43
+ from typing import TYPE_CHECKING, Dict, Optional
44
+
45
+ from kailash.sdk_exceptions import WorkflowValidationError
46
+ from kailash.workflow.cycle_exceptions import (
47
+ CycleConfigurationError,
48
+ CycleConnectionError,
49
+ )
50
+
51
+ if TYPE_CHECKING:
52
+ from .cycle_config import CycleConfig
53
+ from .graph import Workflow
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+
58
+ class CycleBuilder:
59
+ """
60
+ Fluent builder for creating cyclic workflow connections.
61
+
62
+ This class provides an intuitive, chainable API for configuring cyclic
63
+ connections in workflows. It replaces the verbose parameter-heavy approach
64
+ with a more discoverable and type-safe builder pattern.
65
+
66
+ Examples:
67
+ Creating a basic cycle:
68
+
69
+ >>> workflow = Workflow("optimization", "Optimization Loop")
70
+ >>> cycle = workflow.create_cycle("quality_improvement")
71
+ >>> cycle.connect("processor", "evaluator") \
72
+ ... .max_iterations(50) \
73
+ ... .converge_when("quality > 0.9") \
74
+ ... .timeout(300) \
75
+ ... .build()
76
+ """
77
+
78
+ def __init__(self, workflow: "Workflow", cycle_id: Optional[str] = None):
79
+ """
80
+ Initialize a new CycleBuilder.
81
+
82
+ Args:
83
+ workflow: The workflow to add the cycle to.
84
+ cycle_id: Optional identifier for the cycle group.
85
+ """
86
+ self._workflow = workflow
87
+ self._cycle_id = cycle_id
88
+
89
+ # Connection parameters
90
+ self._source_node: Optional[str] = None
91
+ self._target_node: Optional[str] = None
92
+ self._mapping: Optional[Dict[str, str]] = None
93
+
94
+ # Cycle parameters
95
+ self._max_iterations: Optional[int] = None
96
+ self._convergence_check: Optional[str] = None
97
+ self._timeout: Optional[float] = None
98
+ self._memory_limit: Optional[int] = None
99
+ self._condition: Optional[str] = None
100
+ self._parent_cycle: Optional[str] = None
101
+
102
+ def connect(
103
+ self,
104
+ source_node: str,
105
+ target_node: str,
106
+ mapping: Optional[Dict[str, str]] = None,
107
+ ) -> "CycleBuilder":
108
+ """
109
+ Configure the source and target nodes for the cycle connection.
110
+
111
+ Establishes which nodes will be connected in a cyclic pattern. The
112
+ mapping parameter defines how outputs from the source node map to
113
+ inputs of the target node.
114
+
115
+ Args:
116
+ source_node: Node ID that produces output for the cycle.
117
+ target_node: Node ID that receives input from the cycle.
118
+ mapping: Output-to-input mapping. Keys are source output fields,
119
+ values are target input fields. If None, attempts automatic
120
+ mapping based on parameter names.
121
+
122
+ Returns:
123
+ Self for method chaining.
124
+
125
+ Raises:
126
+ WorkflowValidationError: If source or target node doesn't exist.
127
+ CycleConfigurationError: If nodes are invalid for cyclic connection.
128
+
129
+ Examples:
130
+ >>> cycle.connect("processor", "evaluator", {"result": "input_data"})
131
+ >>> # Or with automatic mapping
132
+ >>> cycle.connect("node_a", "node_b")
133
+ """
134
+ # Validate nodes exist in workflow
135
+ available_nodes = list(self._workflow.nodes.keys())
136
+
137
+ if source_node not in self._workflow.nodes:
138
+ raise CycleConnectionError(
139
+ f"Source node '{source_node}' not found in workflow",
140
+ source_node=source_node,
141
+ available_nodes=available_nodes,
142
+ error_code="CYCLE_CONN_001",
143
+ )
144
+
145
+ if target_node not in self._workflow.nodes:
146
+ raise CycleConnectionError(
147
+ f"Target node '{target_node}' not found in workflow",
148
+ target_node=target_node,
149
+ available_nodes=available_nodes,
150
+ error_code="CYCLE_CONN_002",
151
+ )
152
+
153
+ self._source_node = source_node
154
+ self._target_node = target_node
155
+ self._mapping = mapping
156
+
157
+ return self
158
+
159
+ def max_iterations(self, iterations: int) -> "CycleBuilder":
160
+ """
161
+ Set the maximum number of cycle iterations for safety.
162
+
163
+ Provides a hard limit on cycle execution to prevent infinite loops.
164
+ This is a critical safety mechanism for production workflows.
165
+
166
+ Args:
167
+ iterations: Maximum number of iterations allowed. Must be positive.
168
+ Recommended range: 10-1000 depending on use case.
169
+
170
+ Returns:
171
+ Self for method chaining.
172
+
173
+ Raises:
174
+ CycleConfigurationError: If iterations is not positive.
175
+
176
+ Examples:
177
+ >>> cycle.max_iterations(100) # Allow up to 100 iterations
178
+ >>> cycle.max_iterations(10) # Quick convergence expected
179
+ """
180
+ if iterations <= 0:
181
+ raise CycleConfigurationError(
182
+ "max_iterations must be positive",
183
+ cycle_id=self._cycle_id,
184
+ invalid_params={"max_iterations": iterations},
185
+ error_code="CYCLE_CONFIG_002",
186
+ suggestions=[
187
+ "Use 10-100 iterations for quick convergence",
188
+ "Use 100-1000 iterations for complex optimization",
189
+ "Consider adding convergence_check for early termination",
190
+ ],
191
+ )
192
+
193
+ self._max_iterations = iterations
194
+ return self
195
+
196
+ def converge_when(self, condition: str) -> "CycleBuilder":
197
+ """
198
+ Set the convergence condition to terminate the cycle early.
199
+
200
+ Defines an expression that, when true, will stop cycle execution
201
+ before reaching max_iterations. This enables efficient early
202
+ termination when the desired result is achieved.
203
+
204
+ Args:
205
+ condition: Python expression evaluated against node outputs.
206
+ Can reference any output field from cycle nodes.
207
+ Examples: "error < 0.01", "quality > 0.9", "improvement < 0.001"
208
+
209
+ Returns:
210
+ Self for method chaining.
211
+
212
+ Raises:
213
+ CycleConfigurationError: If condition syntax is invalid.
214
+
215
+ Examples:
216
+ >>> cycle.converge_when("error < 0.01") # Numerical convergence
217
+ >>> cycle.converge_when("quality > 0.95") # Quality threshold
218
+ >>> cycle.converge_when("improvement < 0.001") # Minimal improvement
219
+ """
220
+ if not condition or not isinstance(condition, str):
221
+ raise CycleConfigurationError(
222
+ "Convergence condition must be a non-empty string expression. "
223
+ "Examples: 'error < 0.01', 'quality > 0.9', 'count >= 10'"
224
+ )
225
+
226
+ # Basic validation - check for dangerous operations
227
+ dangerous_patterns = ["import ", "exec(", "eval(", "__"]
228
+ for pattern in dangerous_patterns:
229
+ if pattern in condition:
230
+ raise CycleConfigurationError(
231
+ f"Convergence condition contains potentially unsafe operation: '{pattern}'. "
232
+ "Use simple comparison expressions only."
233
+ )
234
+
235
+ self._convergence_check = condition
236
+ return self
237
+
238
+ def timeout(self, seconds: float) -> "CycleBuilder":
239
+ """
240
+ Set a timeout limit for cycle execution.
241
+
242
+ Provides time-based safety limit to prevent cycles from running
243
+ indefinitely. Useful for cycles that might have unpredictable
244
+ convergence times.
245
+
246
+ Args:
247
+ seconds: Maximum execution time in seconds. Must be positive.
248
+ Recommended: 30-3600 seconds.
249
+
250
+ Returns:
251
+ Self for method chaining.
252
+
253
+ Raises:
254
+ CycleConfigurationError: If timeout is not positive.
255
+
256
+ Examples:
257
+ >>> cycle.timeout(300) # 5 minutes maximum
258
+ >>> cycle.timeout(30.5) # 30.5 seconds for quick cycles
259
+ """
260
+ if seconds <= 0:
261
+ raise CycleConfigurationError(
262
+ f"Timeout must be positive, got {seconds}. "
263
+ "Recommendation: Use 30-300 seconds for most cycles, "
264
+ "up to 3600 seconds for long-running optimization."
265
+ )
266
+
267
+ self._timeout = seconds
268
+ return self
269
+
270
+ def memory_limit(self, mb: int) -> "CycleBuilder":
271
+ """
272
+ Set a memory usage limit for cycle execution.
273
+
274
+ Provides memory-based safety limit to prevent cycles from consuming
275
+ excessive memory through data accumulation across iterations.
276
+
277
+ Args:
278
+ mb: Maximum memory usage in megabytes. Must be positive.
279
+ Recommended: 100-10000 MB.
280
+
281
+ Returns:
282
+ Self for method chaining.
283
+
284
+ Raises:
285
+ CycleConfigurationError: If memory limit is not positive.
286
+
287
+ Examples:
288
+ >>> cycle.memory_limit(1024) # 1GB limit
289
+ >>> cycle.memory_limit(512) # 512MB for smaller workflows
290
+ """
291
+ if mb <= 0:
292
+ raise CycleConfigurationError(
293
+ f"Memory limit must be positive, got {mb}. "
294
+ "Recommendation: Use 100-1000 MB for most cycles, "
295
+ "up to 10000 MB for data-intensive processing."
296
+ )
297
+
298
+ self._memory_limit = mb
299
+ return self
300
+
301
+ def when(self, condition: str) -> "CycleBuilder":
302
+ """
303
+ Set a conditional expression for cycle routing.
304
+
305
+ Enables conditional cycle execution where the cycle only runs
306
+ when the specified condition is met. Useful for adaptive workflows.
307
+
308
+ Args:
309
+ condition: Python expression for conditional execution.
310
+ Evaluated before each cycle iteration.
311
+
312
+ Returns:
313
+ Self for method chaining.
314
+
315
+ Raises:
316
+ CycleConfigurationError: If condition syntax is invalid.
317
+
318
+ Examples:
319
+ >>> cycle.when("retry_count < 3") # Retry logic
320
+ >>> cycle.when("needs_optimization") # Conditional optimization
321
+ """
322
+ if not condition or not isinstance(condition, str):
323
+ raise CycleConfigurationError(
324
+ "Condition must be a non-empty string expression. "
325
+ "Examples: 'retry_count < 3', 'needs_improvement == True'"
326
+ )
327
+
328
+ self._condition = condition
329
+ return self
330
+
331
+ def nested_in(self, parent_cycle_id: str) -> "CycleBuilder":
332
+ """
333
+ Make this cycle nested within another cycle.
334
+
335
+ Enables hierarchical cycle structures where one cycle operates
336
+ within the iterations of a parent cycle. Useful for multi-level
337
+ optimization scenarios.
338
+
339
+ Args:
340
+ parent_cycle_id: Identifier of the parent cycle.
341
+
342
+ Returns:
343
+ Self for method chaining.
344
+
345
+ Raises:
346
+ CycleConfigurationError: If parent cycle ID is invalid.
347
+
348
+ Examples:
349
+ >>> cycle.nested_in("outer_optimization") # This cycle runs inside outer_optimization
350
+ """
351
+ if not parent_cycle_id or not isinstance(parent_cycle_id, str):
352
+ raise CycleConfigurationError("Parent cycle ID must be a non-empty string")
353
+
354
+ self._parent_cycle = parent_cycle_id
355
+ return self
356
+
357
+ def build(self) -> None:
358
+ """
359
+ Build and add the configured cycle to the workflow.
360
+
361
+ Validates the cycle configuration and creates the actual cyclic
362
+ connection in the workflow. This finalizes the cycle builder
363
+ pattern and applies all configured settings.
364
+
365
+ Raises:
366
+ CycleConfigurationError: If cycle configuration is incomplete or invalid.
367
+ WorkflowValidationError: If workflow connection fails.
368
+
369
+ Examples:
370
+ >>> cycle.connect("node_a", "node_b") \
371
+ ... .max_iterations(50) \
372
+ ... .converge_when("quality > 0.9") \
373
+ ... .build() # Creates the cycle in the workflow
374
+ """
375
+ # Validate required parameters
376
+ if not self._source_node or not self._target_node:
377
+ raise CycleConfigurationError(
378
+ "Cycle must have source and target nodes configured. "
379
+ "Call connect(source_node, target_node) before build()."
380
+ )
381
+
382
+ # Validate at least one termination condition
383
+ if (
384
+ not self._max_iterations
385
+ and not self._convergence_check
386
+ and not self._timeout
387
+ ):
388
+ raise CycleConfigurationError(
389
+ "Cycle must have at least one termination condition. "
390
+ "Add max_iterations(), converge_when(), or timeout() before build(). "
391
+ "Recommendation: Always include max_iterations() as a safety net."
392
+ )
393
+
394
+ # Create the connection using the workflow's connect method
395
+ try:
396
+ self._workflow.connect(
397
+ source_node=self._source_node,
398
+ target_node=self._target_node,
399
+ mapping=self._mapping,
400
+ cycle=True,
401
+ max_iterations=self._max_iterations,
402
+ convergence_check=self._convergence_check,
403
+ cycle_id=self._cycle_id,
404
+ timeout=self._timeout,
405
+ memory_limit=self._memory_limit,
406
+ condition=self._condition,
407
+ parent_cycle=self._parent_cycle,
408
+ )
409
+
410
+ logger.info(
411
+ f"Created cycle '{self._cycle_id or 'unnamed'}' from "
412
+ f"{self._source_node} to {self._target_node} with "
413
+ f"max_iterations={self._max_iterations}, "
414
+ f"convergence='{self._convergence_check}'"
415
+ )
416
+
417
+ except Exception as e:
418
+ raise WorkflowValidationError(
419
+ f"Failed to create cycle connection: {e}"
420
+ ) from e
421
+
422
+ @classmethod
423
+ def from_config(cls, workflow: "Workflow", config: "CycleConfig") -> "CycleBuilder":
424
+ """
425
+ Create a CycleBuilder from a CycleConfig instance.
426
+
427
+ Provides an alternative constructor that initializes the builder
428
+ with all configuration from a type-safe CycleConfig object. This
429
+ enables configuration reuse, templating, and structured configuration
430
+ management across multiple cycles.
431
+
432
+ Args:
433
+ workflow: Target workflow for the cycle.
434
+ config: Pre-configured cycle parameters.
435
+
436
+ Returns:
437
+ Builder instance initialized with config values.
438
+
439
+ Raises:
440
+ CycleConfigurationError: If config is invalid.
441
+ ImportError: If CycleConfig module is not available.
442
+
443
+ Examples:
444
+ Using a template:
445
+
446
+ >>> config = CycleTemplates.optimization_loop(max_iterations=50)
447
+ >>> builder = CycleBuilder.from_config(workflow, config)
448
+ >>> builder.connect("optimizer", "evaluator").build()
449
+
450
+ Using custom configuration:
451
+
452
+ >>> config = CycleConfig(max_iterations=100, timeout=300)
453
+ >>> builder = CycleBuilder.from_config(workflow, config)
454
+ >>> builder.connect("processor", "evaluator").build()
455
+ """
456
+ try:
457
+ from kailash.workflow.cycle_config import CycleConfig
458
+ except ImportError as e:
459
+ raise ImportError(
460
+ "CycleConfig not available. Ensure kailash.workflow.cycle_config is installed."
461
+ ) from e
462
+
463
+ # Validate config is correct type
464
+ if not isinstance(config, CycleConfig):
465
+ raise CycleConfigurationError(
466
+ f"Expected CycleConfig instance, got {type(config)}. "
467
+ "Use CycleConfig() or CycleTemplates.* to create valid configuration."
468
+ )
469
+
470
+ # Create builder with config values
471
+ builder = cls(workflow=workflow, cycle_id=config.cycle_id)
472
+
473
+ # Apply configuration parameters
474
+ if config.max_iterations is not None:
475
+ builder._max_iterations = config.max_iterations
476
+
477
+ if config.convergence_check is not None:
478
+ if isinstance(config.convergence_check, str):
479
+ builder._convergence_check = config.convergence_check
480
+ else:
481
+ # For callable convergence checks, convert to description
482
+ builder._convergence_check = "<callable_convergence_check>"
483
+
484
+ if config.timeout is not None:
485
+ builder._timeout = config.timeout
486
+
487
+ if config.memory_limit is not None:
488
+ builder._memory_limit = config.memory_limit
489
+
490
+ if config.condition is not None:
491
+ builder._condition = config.condition
492
+
493
+ if config.parent_cycle is not None:
494
+ builder._parent_cycle = config.parent_cycle
495
+
496
+ return builder
497
+
498
+ def apply_config(self, config: "CycleConfig") -> "CycleBuilder":
499
+ """
500
+ Apply configuration from a CycleConfig instance to this builder.
501
+
502
+ Merges configuration parameters from a CycleConfig object into
503
+ the current builder state. This allows combining fluent builder
504
+ calls with structured configuration objects for maximum flexibility.
505
+
506
+ Args:
507
+ config: Configuration to apply to this builder.
508
+
509
+ Returns:
510
+ Self for method chaining.
511
+
512
+ Raises:
513
+ CycleConfigurationError: If config is invalid.
514
+
515
+ Examples:
516
+ >>> builder = workflow.create_cycle("custom") \
517
+ ... .connect("a", "b") \
518
+ ... .apply_config(CycleTemplates.optimization_loop()) \
519
+ ... .timeout(120) # Override the template timeout
520
+ ... .build()
521
+ """
522
+ try:
523
+ from kailash.workflow.cycle_config import CycleConfig
524
+ except ImportError as e:
525
+ raise ImportError(
526
+ "CycleConfig not available. Ensure kailash.workflow.cycle_config is installed."
527
+ ) from e
528
+
529
+ if not isinstance(config, CycleConfig):
530
+ raise CycleConfigurationError(
531
+ f"Expected CycleConfig instance, got {type(config)}"
532
+ )
533
+
534
+ # Apply non-None configuration values
535
+ if config.max_iterations is not None:
536
+ self._max_iterations = config.max_iterations
537
+
538
+ if config.convergence_check is not None:
539
+ if isinstance(config.convergence_check, str):
540
+ self._convergence_check = config.convergence_check
541
+
542
+ if config.timeout is not None:
543
+ self._timeout = config.timeout
544
+
545
+ if config.memory_limit is not None:
546
+ self._memory_limit = config.memory_limit
547
+
548
+ if config.condition is not None:
549
+ self._condition = config.condition
550
+
551
+ if config.parent_cycle is not None:
552
+ self._parent_cycle = config.parent_cycle
553
+
554
+ # Update cycle_id if specified in config
555
+ if config.cycle_id is not None:
556
+ self._cycle_id = config.cycle_id
557
+
558
+ return self
559
+
560
+ def __repr__(self) -> str:
561
+ """
562
+ Return string representation of the cycle builder configuration.
563
+
564
+ Returns:
565
+ Human-readable representation of current configuration.
566
+
567
+ Examples:
568
+ >>> str(cycle)
569
+ 'CycleBuilder(cycle_id=optimization, source=processor, target=evaluator, max_iterations=50)'
570
+ """
571
+ return (
572
+ f"CycleBuilder("
573
+ f"cycle_id={self._cycle_id}, "
574
+ f"source={self._source_node}, "
575
+ f"target={self._target_node}, "
576
+ f"max_iterations={self._max_iterations}, "
577
+ f"convergence='{self._convergence_check}'"
578
+ f")"
579
+ )