kailash 0.2.2__py3-none-any.whl → 0.3.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 (117) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/access_control.py +40 -39
  3. kailash/api/auth.py +26 -32
  4. kailash/api/custom_nodes.py +29 -29
  5. kailash/api/custom_nodes_secure.py +35 -35
  6. kailash/api/database.py +17 -17
  7. kailash/api/gateway.py +19 -19
  8. kailash/api/mcp_integration.py +24 -23
  9. kailash/api/studio.py +45 -45
  10. kailash/api/workflow_api.py +8 -8
  11. kailash/cli/commands.py +5 -8
  12. kailash/manifest.py +42 -42
  13. kailash/mcp/__init__.py +1 -1
  14. kailash/mcp/ai_registry_server.py +20 -20
  15. kailash/mcp/client.py +9 -11
  16. kailash/mcp/client_new.py +10 -10
  17. kailash/mcp/server.py +1 -2
  18. kailash/mcp/server_enhanced.py +449 -0
  19. kailash/mcp/servers/ai_registry.py +6 -6
  20. kailash/mcp/utils/__init__.py +31 -0
  21. kailash/mcp/utils/cache.py +267 -0
  22. kailash/mcp/utils/config.py +263 -0
  23. kailash/mcp/utils/formatters.py +293 -0
  24. kailash/mcp/utils/metrics.py +418 -0
  25. kailash/nodes/ai/agents.py +9 -9
  26. kailash/nodes/ai/ai_providers.py +33 -34
  27. kailash/nodes/ai/embedding_generator.py +31 -32
  28. kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
  29. kailash/nodes/ai/iterative_llm_agent.py +48 -48
  30. kailash/nodes/ai/llm_agent.py +32 -33
  31. kailash/nodes/ai/models.py +13 -13
  32. kailash/nodes/ai/self_organizing.py +44 -44
  33. kailash/nodes/api/__init__.py +5 -0
  34. kailash/nodes/api/auth.py +11 -11
  35. kailash/nodes/api/graphql.py +13 -13
  36. kailash/nodes/api/http.py +19 -19
  37. kailash/nodes/api/monitoring.py +463 -0
  38. kailash/nodes/api/rate_limiting.py +9 -13
  39. kailash/nodes/api/rest.py +29 -29
  40. kailash/nodes/api/security.py +819 -0
  41. kailash/nodes/base.py +24 -26
  42. kailash/nodes/base_async.py +7 -7
  43. kailash/nodes/base_cycle_aware.py +12 -12
  44. kailash/nodes/base_with_acl.py +5 -5
  45. kailash/nodes/code/python.py +56 -55
  46. kailash/nodes/data/__init__.py +6 -0
  47. kailash/nodes/data/directory.py +6 -6
  48. kailash/nodes/data/event_generation.py +297 -0
  49. kailash/nodes/data/file_discovery.py +598 -0
  50. kailash/nodes/data/readers.py +8 -8
  51. kailash/nodes/data/retrieval.py +10 -10
  52. kailash/nodes/data/sharepoint_graph.py +17 -17
  53. kailash/nodes/data/sources.py +5 -5
  54. kailash/nodes/data/sql.py +13 -13
  55. kailash/nodes/data/streaming.py +25 -25
  56. kailash/nodes/data/vector_db.py +22 -22
  57. kailash/nodes/data/writers.py +7 -7
  58. kailash/nodes/logic/async_operations.py +17 -17
  59. kailash/nodes/logic/convergence.py +11 -11
  60. kailash/nodes/logic/loop.py +4 -4
  61. kailash/nodes/logic/operations.py +11 -11
  62. kailash/nodes/logic/workflow.py +8 -9
  63. kailash/nodes/mixins/mcp.py +17 -17
  64. kailash/nodes/mixins.py +8 -10
  65. kailash/nodes/transform/chunkers.py +3 -3
  66. kailash/nodes/transform/formatters.py +7 -7
  67. kailash/nodes/transform/processors.py +11 -11
  68. kailash/runtime/access_controlled.py +18 -18
  69. kailash/runtime/async_local.py +18 -20
  70. kailash/runtime/docker.py +24 -26
  71. kailash/runtime/local.py +55 -31
  72. kailash/runtime/parallel.py +25 -25
  73. kailash/runtime/parallel_cyclic.py +29 -29
  74. kailash/runtime/runner.py +6 -6
  75. kailash/runtime/testing.py +22 -22
  76. kailash/sdk_exceptions.py +0 -58
  77. kailash/security.py +14 -26
  78. kailash/tracking/manager.py +38 -38
  79. kailash/tracking/metrics_collector.py +15 -14
  80. kailash/tracking/models.py +53 -53
  81. kailash/tracking/storage/base.py +7 -17
  82. kailash/tracking/storage/database.py +22 -23
  83. kailash/tracking/storage/filesystem.py +38 -40
  84. kailash/utils/export.py +21 -21
  85. kailash/utils/templates.py +8 -9
  86. kailash/visualization/api.py +30 -34
  87. kailash/visualization/dashboard.py +17 -17
  88. kailash/visualization/performance.py +32 -19
  89. kailash/visualization/reports.py +30 -28
  90. kailash/workflow/builder.py +8 -8
  91. kailash/workflow/convergence.py +13 -12
  92. kailash/workflow/cycle_analyzer.py +38 -33
  93. kailash/workflow/cycle_builder.py +12 -12
  94. kailash/workflow/cycle_config.py +16 -15
  95. kailash/workflow/cycle_debugger.py +40 -40
  96. kailash/workflow/cycle_exceptions.py +29 -29
  97. kailash/workflow/cycle_profiler.py +21 -21
  98. kailash/workflow/cycle_state.py +20 -22
  99. kailash/workflow/cyclic_runner.py +45 -45
  100. kailash/workflow/graph.py +57 -45
  101. kailash/workflow/mermaid_visualizer.py +9 -11
  102. kailash/workflow/migration.py +22 -22
  103. kailash/workflow/mock_registry.py +6 -6
  104. kailash/workflow/runner.py +9 -9
  105. kailash/workflow/safety.py +12 -13
  106. kailash/workflow/state.py +8 -11
  107. kailash/workflow/templates.py +19 -19
  108. kailash/workflow/validation.py +14 -14
  109. kailash/workflow/visualization.py +32 -24
  110. kailash-0.3.1.dist-info/METADATA +476 -0
  111. kailash-0.3.1.dist-info/RECORD +136 -0
  112. kailash-0.2.2.dist-info/METADATA +0 -121
  113. kailash-0.2.2.dist-info/RECORD +0 -126
  114. {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/WHEEL +0 -0
  115. {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/entry_points.txt +0 -0
  116. {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/licenses/LICENSE +0 -0
  117. {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/top_level.txt +0 -0
kailash/runtime/docker.py CHANGED
@@ -21,7 +21,7 @@ import subprocess
21
21
  import sys
22
22
  import tempfile
23
23
  from pathlib import Path
24
- from typing import Any, Dict, Optional, Tuple
24
+ from typing import Any
25
25
 
26
26
  from kailash.nodes.base import Node
27
27
 
@@ -50,8 +50,8 @@ class DockerNodeWrapper:
50
50
  node: Node,
51
51
  node_id: str,
52
52
  base_image: str = "python:3.11-slim",
53
- work_dir: Optional[Path] = None,
54
- sdk_path: Optional[Path] = None,
53
+ work_dir: Path | None = None,
54
+ sdk_path: Path | None = None,
55
55
  ):
56
56
  """
57
57
  Initialize a Docker node wrapper.
@@ -174,7 +174,7 @@ def main():
174
174
  logger.info(f"Loaded configuration for {node_data['class']} node")
175
175
 
176
176
  # Load runtime inputs if available
177
- input_path = Path("/examples/data/input/inputs.json")
177
+ input_path = Path("/data/inputs/json/inputs.json")
178
178
  runtime_inputs = {}
179
179
  if input_path.exists():
180
180
  logger.info(f"Loading inputs from {input_path}")
@@ -206,7 +206,7 @@ def main():
206
206
  except Exception as e:
207
207
  logger.error(f"Node execution failed: {e}")
208
208
  # Save error information
209
- with open("/examples/data/output/error.json", 'w') as f:
209
+ with open("/data/outputs/json/error.json", 'w') as f:
210
210
  json.dump({
211
211
  "error": str(e),
212
212
  "type": e.__class__.__name__
@@ -216,7 +216,7 @@ def main():
216
216
  # Save results
217
217
  logger.info("Saving execution results")
218
218
  try:
219
- result_path = Path("/examples/data/output/result.json")
219
+ result_path = Path("/data/outputs/json/result.json")
220
220
  with open(result_path, 'w') as f:
221
221
  # Handle non-serializable objects with basic conversion
222
222
  try:
@@ -335,7 +335,7 @@ ENTRYPOINT ["/app/entrypoint.py"]
335
335
  logger.error(error_msg)
336
336
  raise RuntimeError(error_msg)
337
337
 
338
- def prepare_inputs(self, inputs: Dict[str, Any]):
338
+ def prepare_inputs(self, inputs: dict[str, Any]):
339
339
  """
340
340
  Prepare inputs for node execution.
341
341
 
@@ -349,8 +349,8 @@ ENTRYPOINT ["/app/entrypoint.py"]
349
349
  def run_container(
350
350
  self,
351
351
  network: str = None,
352
- env_vars: Dict[str, str] = None,
353
- resource_limits: Dict[str, str] = None,
352
+ env_vars: dict[str, str] = None,
353
+ resource_limits: dict[str, str] = None,
354
354
  ) -> bool:
355
355
  """
356
356
  Run the node in a Docker container.
@@ -416,13 +416,13 @@ ENTRYPOINT ["/app/entrypoint.py"]
416
416
  # Check if there's an error file
417
417
  error_file = self.output_dir / "error.json"
418
418
  if error_file.exists():
419
- with open(error_file, "r") as f:
419
+ with open(error_file) as f:
420
420
  error_data = json.load(f)
421
421
  error_msg = f"Node execution error: {error_data.get('error', 'Unknown error')}"
422
422
 
423
423
  raise NodeExecutionError(error_msg)
424
424
 
425
- def get_results(self) -> Dict[str, Any]:
425
+ def get_results(self) -> dict[str, Any]:
426
426
  """
427
427
  Get the results of node execution.
428
428
 
@@ -431,12 +431,12 @@ ENTRYPOINT ["/app/entrypoint.py"]
431
431
  """
432
432
  result_file = self.output_dir / "result.json"
433
433
  if result_file.exists():
434
- with open(result_file, "r") as f:
434
+ with open(result_file) as f:
435
435
  return json.load(f)
436
436
 
437
437
  error_file = self.output_dir / "error.json"
438
438
  if error_file.exists():
439
- with open(error_file, "r") as f:
439
+ with open(error_file) as f:
440
440
  error_data = json.load(f)
441
441
  raise NodeExecutionError(
442
442
  f"Node {self.node_id} execution failed: {error_data.get('error', 'Unknown error')}"
@@ -464,10 +464,10 @@ class DockerRuntime:
464
464
  self,
465
465
  base_image: str = "python:3.11-slim",
466
466
  network_name: str = "kailash-network",
467
- work_dir: Optional[str] = None,
468
- sdk_path: Optional[str] = None,
469
- resource_limits: Optional[Dict[str, str]] = None,
470
- task_manager: Optional[TaskManager] = None,
467
+ work_dir: str | None = None,
468
+ sdk_path: str | None = None,
469
+ resource_limits: dict[str, str] | None = None,
470
+ task_manager: TaskManager | None = None,
471
471
  ):
472
472
  """
473
473
  Initialize the Docker runtime.
@@ -516,14 +516,14 @@ class DockerRuntime:
516
516
  # Track node wrappers
517
517
  self.node_wrappers = {}
518
518
 
519
- def _create_task_run(self, workflow: Workflow) -> Optional[str]:
519
+ def _create_task_run(self, workflow: Workflow) -> str | None:
520
520
  """Create a task run if task manager is available."""
521
521
  if self.task_manager:
522
522
  return self.task_manager.create_run(workflow.name)
523
523
  return None
524
524
 
525
525
  def _update_task_status(
526
- self, run_id: Optional[str], node_id: str, status: str, output: Any = None
526
+ self, run_id: str | None, node_id: str, status: str, output: Any = None
527
527
  ):
528
528
  """Update task status if task manager is available."""
529
529
  if self.task_manager and run_id:
@@ -534,9 +534,7 @@ class DockerRuntime:
534
534
  )
535
535
  self.task_manager.update_run_status(run_id, "failed", error_msg)
536
536
 
537
- def _complete_task_run(
538
- self, run_id: Optional[str], status: str, result: Any = None
539
- ):
537
+ def _complete_task_run(self, run_id: str | None, status: str, result: Any = None):
540
538
  """Complete task run if task manager is available."""
541
539
  if self.task_manager and run_id:
542
540
  if status == "completed":
@@ -567,9 +565,9 @@ class DockerRuntime:
567
565
  def execute(
568
566
  self,
569
567
  workflow: Workflow,
570
- inputs: Dict[str, Dict[str, Any]] = None,
571
- node_resource_limits: Dict[str, Dict[str, str]] = None,
572
- ) -> Tuple[Dict[str, Dict[str, Any]], str]:
568
+ inputs: dict[str, dict[str, Any]] = None,
569
+ node_resource_limits: dict[str, dict[str, str]] = None,
570
+ ) -> tuple[dict[str, dict[str, Any]], str]:
573
571
  """
574
572
  Execute a workflow using Docker containers.
575
573
 
@@ -590,7 +588,7 @@ class DockerRuntime:
590
588
 
591
589
  try:
592
590
  # Validate workflow
593
- workflow.validate()
591
+ workflow.validate(runtime_parameters=inputs)
594
592
 
595
593
  # Get execution order
596
594
  execution_order = workflow.get_execution_order()
kailash/runtime/local.py CHANGED
@@ -40,8 +40,8 @@ Examples:
40
40
  """
41
41
 
42
42
  import logging
43
- from datetime import datetime, timezone
44
- from typing import Any, Dict, List, Optional, Tuple
43
+ from datetime import UTC, datetime
44
+ from typing import Any
45
45
 
46
46
  import networkx as nx
47
47
 
@@ -90,9 +90,9 @@ class LocalRuntime:
90
90
  def execute(
91
91
  self,
92
92
  workflow: Workflow,
93
- task_manager: Optional[TaskManager] = None,
94
- parameters: Optional[Dict[str, Dict[str, Any]]] = None,
95
- ) -> Tuple[Dict[str, Any], Optional[str]]:
93
+ task_manager: TaskManager | None = None,
94
+ parameters: dict[str, dict[str, Any]] | None = None,
95
+ ) -> tuple[dict[str, Any], str | None]:
96
96
  """Execute a workflow locally.
97
97
 
98
98
  Args:
@@ -113,8 +113,8 @@ class LocalRuntime:
113
113
  run_id = None
114
114
 
115
115
  try:
116
- # Validate workflow
117
- workflow.validate()
116
+ # Validate workflow with runtime parameters (Session 061)
117
+ workflow.validate(runtime_parameters=parameters)
118
118
 
119
119
  # Initialize tracking
120
120
  if task_manager:
@@ -197,10 +197,10 @@ class LocalRuntime:
197
197
  def _execute_workflow(
198
198
  self,
199
199
  workflow: Workflow,
200
- task_manager: Optional[TaskManager],
201
- run_id: Optional[str],
202
- parameters: Dict[str, Dict[str, Any]],
203
- ) -> Dict[str, Any]:
200
+ task_manager: TaskManager | None,
201
+ run_id: str | None,
202
+ parameters: dict[str, dict[str, Any]],
203
+ ) -> dict[str, Any]:
204
204
  """Execute the workflow nodes in topological order.
205
205
 
206
206
  Args:
@@ -273,7 +273,7 @@ class LocalRuntime:
273
273
  run_id=run_id,
274
274
  node_id=node_id,
275
275
  node_type=node_instance.__class__.__name__,
276
- started_at=datetime.now(timezone.utc),
276
+ started_at=datetime.now(UTC),
277
277
  metadata=node_metadata,
278
278
  )
279
279
  # Start the task
@@ -296,6 +296,10 @@ class LocalRuntime:
296
296
  parameters=parameters.get(node_id, {}),
297
297
  )
298
298
 
299
+ # Update node config with parameters (Session 061: direct config update)
300
+ {**node_instance.config, **parameters.get(node_id, {})}
301
+ node_instance.config.update(parameters.get(node_id, {}))
302
+
299
303
  if self.debug:
300
304
  self.logger.debug(f"Node {node_id} inputs: {inputs}")
301
305
 
@@ -325,7 +329,7 @@ class LocalRuntime:
325
329
  task.task_id,
326
330
  TaskStatus.COMPLETED,
327
331
  result=outputs,
328
- ended_at=datetime.now(timezone.utc),
332
+ ended_at=datetime.now(UTC),
329
333
  metadata={"execution_time": performance_metrics.duration},
330
334
  )
331
335
 
@@ -346,7 +350,7 @@ class LocalRuntime:
346
350
  task.task_id,
347
351
  TaskStatus.FAILED,
348
352
  error=str(e),
349
- ended_at=datetime.now(timezone.utc),
353
+ ended_at=datetime.now(UTC),
350
354
  )
351
355
 
352
356
  # Determine if we should continue
@@ -371,9 +375,9 @@ class LocalRuntime:
371
375
  workflow: Workflow,
372
376
  node_id: str,
373
377
  node_instance: Node,
374
- node_outputs: Dict[str, Dict[str, Any]],
375
- parameters: Dict[str, Any],
376
- ) -> Dict[str, Any]:
378
+ node_outputs: dict[str, dict[str, Any]],
379
+ parameters: dict[str, Any],
380
+ ) -> dict[str, Any]:
377
381
  """Prepare inputs for a node execution.
378
382
 
379
383
  Args:
@@ -391,21 +395,36 @@ class LocalRuntime:
391
395
  """
392
396
  inputs = {}
393
397
 
394
- # Start with node configuration
395
- inputs.update(node_instance.config)
398
+ # NOTE: Node configuration is handled separately in configure() call
399
+ # Only add runtime inputs and data from connected nodes here
400
+
401
+ # Add runtime parameters (those not used for node configuration)
402
+ # Map specific runtime parameters for known node types
403
+ if "consumer_timeout_ms" in parameters:
404
+ inputs["timeout_ms"] = parameters["consumer_timeout_ms"]
405
+
406
+ # Add other potential runtime parameters that are not configuration
407
+ runtime_param_names = {"max_messages", "timeout_ms", "limit", "offset"}
408
+ for param_name, param_value in parameters.items():
409
+ if param_name in runtime_param_names:
410
+ inputs[param_name] = param_value
396
411
 
397
412
  # Add connected inputs from other nodes
398
413
  for edge in workflow.graph.in_edges(node_id, data=True):
399
414
  source_node_id = edge[0]
400
415
  mapping = edge[2].get("mapping", {})
401
416
 
402
- print(f"LOCAL RUNTIME DEBUG: Processing edge {source_node_id} -> {node_id}")
403
- print(f" Edge data: {edge[2]}")
404
- print(f" Mapping: {mapping}")
417
+ if self.debug:
418
+ self.logger.debug(f"Processing edge {source_node_id} -> {node_id}")
419
+ self.logger.debug(f" Edge data: {edge[2]}")
420
+ self.logger.debug(f" Mapping: {mapping}")
405
421
 
406
422
  if source_node_id in node_outputs:
407
423
  source_outputs = node_outputs[source_node_id]
408
- print(f" Source outputs: {list(source_outputs.keys())}")
424
+ if self.debug:
425
+ self.logger.debug(
426
+ f" Source outputs: {list(source_outputs.keys())}"
427
+ )
409
428
 
410
429
  # Check if the source node failed
411
430
  if isinstance(source_outputs, dict) and source_outputs.get("failed"):
@@ -416,19 +435,24 @@ class LocalRuntime:
416
435
  for source_key, target_key in mapping.items():
417
436
  if source_key in source_outputs:
418
437
  inputs[target_key] = source_outputs[source_key]
419
- print(
420
- f" MAPPED: {source_key} -> {target_key} (type: {type(source_outputs[source_key])})"
421
- )
438
+ if self.debug:
439
+ self.logger.debug(
440
+ f" MAPPED: {source_key} -> {target_key} (type: {type(source_outputs[source_key])})"
441
+ )
422
442
  else:
423
- print(
424
- f" MISSING: {source_key} not in {list(source_outputs.keys())}"
425
- )
443
+ if self.debug:
444
+ self.logger.debug(
445
+ f" MISSING: {source_key} not in {list(source_outputs.keys())}"
446
+ )
426
447
  self.logger.warning(
427
448
  f"Source output '{source_key}' not found in node '{source_node_id}'. "
428
449
  f"Available outputs: {list(source_outputs.keys())}"
429
450
  )
430
451
  else:
431
- print(f" No outputs found for source node {source_node_id}")
452
+ if self.debug:
453
+ self.logger.debug(
454
+ f" No outputs found for source node {source_node_id}"
455
+ )
432
456
 
433
457
  # Apply parameter overrides
434
458
  inputs.update(parameters)
@@ -452,7 +476,7 @@ class LocalRuntime:
452
476
  # Future: implement configurable error handling policies
453
477
  return has_dependents
454
478
 
455
- def validate_workflow(self, workflow: Workflow) -> List[str]:
479
+ def validate_workflow(self, workflow: Workflow) -> list[str]:
456
480
  """Validate a workflow before execution.
457
481
 
458
482
  Args:
@@ -8,8 +8,8 @@ import asyncio
8
8
  import logging
9
9
  import time
10
10
  from collections import deque
11
- from datetime import datetime, timezone
12
- from typing import Any, Deque, Dict, Optional, Set, Tuple
11
+ from datetime import UTC, datetime
12
+ from typing import Any
13
13
 
14
14
  import networkx as nx
15
15
 
@@ -66,9 +66,9 @@ class ParallelRuntime:
66
66
  async def execute(
67
67
  self,
68
68
  workflow: Workflow,
69
- task_manager: Optional[TaskManager] = None,
70
- parameters: Optional[Dict[str, Dict[str, Any]]] = None,
71
- ) -> Tuple[Dict[str, Any], Optional[str]]:
69
+ task_manager: TaskManager | None = None,
70
+ parameters: dict[str, dict[str, Any]] | None = None,
71
+ ) -> tuple[dict[str, Any], str | None]:
72
72
  """Execute a workflow with parallel node execution.
73
73
 
74
74
  Args:
@@ -91,7 +91,7 @@ class ParallelRuntime:
91
91
 
92
92
  try:
93
93
  # Validate workflow
94
- workflow.validate()
94
+ workflow.validate(runtime_parameters=parameters)
95
95
 
96
96
  # Initialize semaphore for concurrent execution control
97
97
  self.semaphore = asyncio.Semaphore(self.max_workers)
@@ -159,10 +159,10 @@ class ParallelRuntime:
159
159
  async def _execute_workflow_parallel(
160
160
  self,
161
161
  workflow: Workflow,
162
- task_manager: Optional[TaskManager],
163
- run_id: Optional[str],
164
- parameters: Dict[str, Dict[str, Any]],
165
- ) -> Dict[str, Any]:
162
+ task_manager: TaskManager | None,
163
+ run_id: str | None,
164
+ parameters: dict[str, dict[str, Any]],
165
+ ) -> dict[str, Any]:
166
166
  """Execute the workflow nodes in parallel where possible.
167
167
 
168
168
  This method uses a dynamic scheduling approach to run independent nodes
@@ -323,11 +323,11 @@ class ParallelRuntime:
323
323
  self,
324
324
  workflow: Workflow,
325
325
  node_id: str,
326
- node_outputs: Dict[str, Dict[str, Any]],
327
- parameters: Dict[str, Any],
328
- task_manager: Optional[TaskManager],
329
- run_id: Optional[str],
330
- ) -> Tuple[Dict[str, Any], bool]:
326
+ node_outputs: dict[str, dict[str, Any]],
327
+ parameters: dict[str, Any],
328
+ task_manager: TaskManager | None,
329
+ run_id: str | None,
330
+ ) -> tuple[dict[str, Any], bool]:
331
331
  """Execute a single node asynchronously.
332
332
 
333
333
  Args:
@@ -359,7 +359,7 @@ class ParallelRuntime:
359
359
  run_id=run_id,
360
360
  node_id=node_id,
361
361
  node_type=node_instance.__class__.__name__,
362
- started_at=datetime.now(timezone.utc),
362
+ started_at=datetime.now(UTC),
363
363
  )
364
364
  except Exception as e:
365
365
  self.logger.warning(f"Failed to create task for node '{node_id}': {e}")
@@ -398,7 +398,7 @@ class ParallelRuntime:
398
398
  async def execute_with_metrics():
399
399
  with collector.collect(node_id=node_id) as context:
400
400
  result = await loop.run_in_executor(
401
- None, lambda: node_instance.execute(**inputs)
401
+ None, lambda: node_instance.run(**inputs)
402
402
  )
403
403
  return result, context.result()
404
404
 
@@ -409,7 +409,7 @@ class ParallelRuntime:
409
409
  task.update_status(
410
410
  TaskStatus.COMPLETED,
411
411
  result=outputs,
412
- ended_at=datetime.now(timezone.utc),
412
+ ended_at=datetime.now(UTC),
413
413
  metadata={"execution_time": performance_metrics.duration},
414
414
  )
415
415
 
@@ -431,7 +431,7 @@ class ParallelRuntime:
431
431
  # Update task status
432
432
  if task:
433
433
  task.update_status(
434
- TaskStatus.FAILED, error=str(e), ended_at=datetime.now(timezone.utc)
434
+ TaskStatus.FAILED, error=str(e), ended_at=datetime.now(UTC)
435
435
  )
436
436
 
437
437
  # Return error result
@@ -448,9 +448,9 @@ class ParallelRuntime:
448
448
  workflow: Workflow,
449
449
  node_id: str,
450
450
  node_instance: Any,
451
- node_outputs: Dict[str, Dict[str, Any]],
452
- parameters: Dict[str, Any],
453
- ) -> Dict[str, Any]:
451
+ node_outputs: dict[str, dict[str, Any]],
452
+ parameters: dict[str, Any],
453
+ ) -> dict[str, Any]:
454
454
  """Prepare inputs for a node execution.
455
455
 
456
456
  Args:
@@ -520,9 +520,9 @@ class ParallelRuntime:
520
520
  self,
521
521
  workflow: Workflow,
522
522
  failed_node: str,
523
- failed_nodes: Set[str],
524
- pending_nodes: Set[str],
525
- ready_nodes: Deque[str],
523
+ failed_nodes: set[str],
524
+ pending_nodes: set[str],
525
+ ready_nodes: deque[str],
526
526
  ) -> None:
527
527
  """Mark all dependent nodes as failed.
528
528
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  import logging
4
4
  from concurrent.futures import ThreadPoolExecutor, as_completed
5
- from datetime import datetime, timezone
6
- from typing import Any, Dict, List, Optional, Set, Tuple
5
+ from datetime import UTC, datetime
6
+ from typing import Any
7
7
 
8
8
  import networkx as nx
9
9
 
@@ -56,10 +56,10 @@ class ParallelCyclicRuntime:
56
56
  def execute(
57
57
  self,
58
58
  workflow: Workflow,
59
- task_manager: Optional[TaskManager] = None,
60
- parameters: Optional[Dict[str, Dict[str, Any]]] = None,
61
- parallel_nodes: Optional[Set[str]] = None,
62
- ) -> Tuple[Dict[str, Any], Optional[str]]:
59
+ task_manager: TaskManager | None = None,
60
+ parameters: dict[str, dict[str, Any]] | None = None,
61
+ parallel_nodes: set[str] | None = None,
62
+ ) -> tuple[dict[str, Any], str | None]:
63
63
  """Execute a workflow with parallel and cyclic support.
64
64
 
65
65
  Args:
@@ -80,7 +80,7 @@ class ParallelCyclicRuntime:
80
80
 
81
81
  try:
82
82
  # Validate workflow
83
- workflow.validate()
83
+ workflow.validate(runtime_parameters=parameters)
84
84
 
85
85
  # Check for cycles first
86
86
  if self.enable_cycles and workflow.has_cycles():
@@ -108,9 +108,9 @@ class ParallelCyclicRuntime:
108
108
  def _execute_cyclic_workflow(
109
109
  self,
110
110
  workflow: Workflow,
111
- task_manager: Optional[TaskManager],
112
- parameters: Optional[Dict[str, Dict[str, Any]]],
113
- ) -> Tuple[Dict[str, Any], str]:
111
+ task_manager: TaskManager | None,
112
+ parameters: dict[str, dict[str, Any]] | None,
113
+ ) -> tuple[dict[str, Any], str]:
114
114
  """Execute a cyclic workflow with potential parallel optimizations.
115
115
 
116
116
  Args:
@@ -141,10 +141,10 @@ class ParallelCyclicRuntime:
141
141
  def _execute_parallel_dag(
142
142
  self,
143
143
  workflow: Workflow,
144
- task_manager: Optional[TaskManager],
145
- parameters: Optional[Dict[str, Dict[str, Any]]],
146
- parallel_nodes: Optional[Set[str]],
147
- ) -> Tuple[Dict[str, Any], Optional[str]]:
144
+ task_manager: TaskManager | None,
145
+ parameters: dict[str, dict[str, Any]] | None,
146
+ parallel_nodes: set[str] | None,
147
+ ) -> tuple[dict[str, Any], str | None]:
148
148
  """Execute a DAG workflow with parallel node execution.
149
149
 
150
150
  Args:
@@ -252,8 +252,8 @@ class ParallelCyclicRuntime:
252
252
  raise
253
253
 
254
254
  def _analyze_parallel_groups(
255
- self, workflow: Workflow, parallel_nodes: Optional[Set[str]]
256
- ) -> List[List[str]]:
255
+ self, workflow: Workflow, parallel_nodes: set[str] | None
256
+ ) -> list[list[str]]:
257
257
  """Analyze workflow to identify groups of nodes that can be executed in parallel.
258
258
 
259
259
  Args:
@@ -320,11 +320,11 @@ class ParallelCyclicRuntime:
320
320
  self,
321
321
  workflow: Workflow,
322
322
  node_id: str,
323
- previous_results: Dict[str, Any],
324
- parameters: Optional[Dict[str, Dict[str, Any]]],
325
- task_manager: Optional[TaskManager],
326
- run_id: Optional[str],
327
- ) -> Dict[str, Any]:
323
+ previous_results: dict[str, Any],
324
+ parameters: dict[str, dict[str, Any]] | None,
325
+ task_manager: TaskManager | None,
326
+ run_id: str | None,
327
+ ) -> dict[str, Any]:
328
328
  """Execute a single node in isolation.
329
329
 
330
330
  Args:
@@ -356,7 +356,7 @@ class ParallelCyclicRuntime:
356
356
  run_id=run_id,
357
357
  node_id=node_id,
358
358
  node_type=node_instance.__class__.__name__,
359
- started_at=datetime.now(timezone.utc),
359
+ started_at=datetime.now(UTC),
360
360
  metadata={},
361
361
  )
362
362
  if task:
@@ -380,7 +380,7 @@ class ParallelCyclicRuntime:
380
380
  # Execute node with metrics collection
381
381
  collector = MetricsCollector()
382
382
  with collector.collect(node_id=node_id) as metrics_context:
383
- outputs = node_instance.execute(**inputs)
383
+ outputs = node_instance.run(**inputs)
384
384
 
385
385
  # Get performance metrics
386
386
  performance_metrics = metrics_context.result()
@@ -397,7 +397,7 @@ class ParallelCyclicRuntime:
397
397
  task.task_id,
398
398
  TaskStatus.COMPLETED,
399
399
  result=outputs,
400
- ended_at=datetime.now(timezone.utc),
400
+ ended_at=datetime.now(UTC),
401
401
  metadata={"execution_time": performance_metrics.duration},
402
402
  )
403
403
  task_manager.update_task_metrics(task.task_id, task_metrics)
@@ -415,7 +415,7 @@ class ParallelCyclicRuntime:
415
415
  task.task_id,
416
416
  TaskStatus.FAILED,
417
417
  error=str(e),
418
- ended_at=datetime.now(timezone.utc),
418
+ ended_at=datetime.now(UTC),
419
419
  )
420
420
 
421
421
  self.logger.error(f"Node {node_id} failed: {e}", exc_info=self.debug)
@@ -428,9 +428,9 @@ class ParallelCyclicRuntime:
428
428
  workflow: Workflow,
429
429
  node_id: str,
430
430
  node_instance: Node,
431
- previous_results: Dict[str, Any],
432
- parameters: Dict[str, Any],
433
- ) -> Dict[str, Any]:
431
+ previous_results: dict[str, Any],
432
+ parameters: dict[str, Any],
433
+ ) -> dict[str, Any]:
434
434
  """Prepare inputs for a node execution in parallel context.
435
435
 
436
436
  Args:
@@ -509,7 +509,7 @@ class ParallelCyclicRuntime:
509
509
  return False
510
510
 
511
511
  def _should_stop_on_group_error(
512
- self, workflow: Workflow, failed_node: str, node_group: List[str]
512
+ self, workflow: Workflow, failed_node: str, node_group: list[str]
513
513
  ) -> bool:
514
514
  """Determine if execution should stop when a node in a parallel group fails.
515
515
 
kailash/runtime/runner.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Main runner for workflow execution."""
2
2
 
3
3
  import logging
4
- from typing import Any, Dict, Optional, Tuple
4
+ from typing import Any
5
5
 
6
6
  from kailash.runtime.local import LocalRuntime
7
7
  from kailash.tracking import TaskManager
@@ -11,7 +11,7 @@ from kailash.workflow import Workflow
11
11
  class WorkflowRunner:
12
12
  """High-level interface for running workflows."""
13
13
 
14
- def __init__(self, debug: bool = False, task_manager: Optional[TaskManager] = None):
14
+ def __init__(self, debug: bool = False, task_manager: TaskManager | None = None):
15
15
  """Initialize the workflow runner.
16
16
 
17
17
  Args:
@@ -36,9 +36,9 @@ class WorkflowRunner:
36
36
  def run(
37
37
  self,
38
38
  workflow: Workflow,
39
- parameters: Optional[Dict[str, Dict[str, Any]]] = None,
39
+ parameters: dict[str, dict[str, Any]] | None = None,
40
40
  runtime_type: str = "local",
41
- ) -> Tuple[Dict[str, Any], str]:
41
+ ) -> tuple[dict[str, Any], str]:
42
42
  """Run a workflow.
43
43
 
44
44
  Args:
@@ -82,7 +82,7 @@ class WorkflowRunner:
82
82
  runtime = LocalRuntime(debug=self.debug)
83
83
  return runtime.validate_workflow(workflow)
84
84
 
85
- def get_run_status(self, run_id: str) -> Dict[str, Any]:
85
+ def get_run_status(self, run_id: str) -> dict[str, Any]:
86
86
  """Get status of a workflow run.
87
87
 
88
88
  Args:
@@ -94,7 +94,7 @@ class WorkflowRunner:
94
94
  return self.task_manager.get_run_status(run_id)
95
95
 
96
96
  def get_run_history(
97
- self, workflow_name: Optional[str] = None, limit: int = 10
97
+ self, workflow_name: str | None = None, limit: int = 10
98
98
  ) -> list:
99
99
  """Get run history.
100
100