kailash 0.3.0__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 (114) 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/auth.py +11 -11
  34. kailash/nodes/api/graphql.py +13 -13
  35. kailash/nodes/api/http.py +19 -19
  36. kailash/nodes/api/monitoring.py +20 -20
  37. kailash/nodes/api/rate_limiting.py +9 -13
  38. kailash/nodes/api/rest.py +29 -29
  39. kailash/nodes/api/security.py +44 -47
  40. kailash/nodes/base.py +21 -23
  41. kailash/nodes/base_async.py +7 -7
  42. kailash/nodes/base_cycle_aware.py +12 -12
  43. kailash/nodes/base_with_acl.py +5 -5
  44. kailash/nodes/code/python.py +56 -55
  45. kailash/nodes/data/directory.py +6 -6
  46. kailash/nodes/data/event_generation.py +10 -10
  47. kailash/nodes/data/file_discovery.py +28 -31
  48. kailash/nodes/data/readers.py +8 -8
  49. kailash/nodes/data/retrieval.py +10 -10
  50. kailash/nodes/data/sharepoint_graph.py +17 -17
  51. kailash/nodes/data/sources.py +5 -5
  52. kailash/nodes/data/sql.py +13 -13
  53. kailash/nodes/data/streaming.py +25 -25
  54. kailash/nodes/data/vector_db.py +22 -22
  55. kailash/nodes/data/writers.py +7 -7
  56. kailash/nodes/logic/async_operations.py +17 -17
  57. kailash/nodes/logic/convergence.py +11 -11
  58. kailash/nodes/logic/loop.py +4 -4
  59. kailash/nodes/logic/operations.py +11 -11
  60. kailash/nodes/logic/workflow.py +8 -9
  61. kailash/nodes/mixins/mcp.py +17 -17
  62. kailash/nodes/mixins.py +8 -10
  63. kailash/nodes/transform/chunkers.py +3 -3
  64. kailash/nodes/transform/formatters.py +7 -7
  65. kailash/nodes/transform/processors.py +10 -10
  66. kailash/runtime/access_controlled.py +18 -18
  67. kailash/runtime/async_local.py +17 -19
  68. kailash/runtime/docker.py +20 -22
  69. kailash/runtime/local.py +16 -16
  70. kailash/runtime/parallel.py +23 -23
  71. kailash/runtime/parallel_cyclic.py +27 -27
  72. kailash/runtime/runner.py +6 -6
  73. kailash/runtime/testing.py +20 -20
  74. kailash/sdk_exceptions.py +0 -58
  75. kailash/security.py +14 -26
  76. kailash/tracking/manager.py +38 -38
  77. kailash/tracking/metrics_collector.py +15 -14
  78. kailash/tracking/models.py +53 -53
  79. kailash/tracking/storage/base.py +7 -17
  80. kailash/tracking/storage/database.py +22 -23
  81. kailash/tracking/storage/filesystem.py +38 -40
  82. kailash/utils/export.py +21 -21
  83. kailash/utils/templates.py +2 -3
  84. kailash/visualization/api.py +30 -34
  85. kailash/visualization/dashboard.py +17 -17
  86. kailash/visualization/performance.py +16 -16
  87. kailash/visualization/reports.py +25 -27
  88. kailash/workflow/builder.py +8 -8
  89. kailash/workflow/convergence.py +13 -12
  90. kailash/workflow/cycle_analyzer.py +30 -32
  91. kailash/workflow/cycle_builder.py +12 -12
  92. kailash/workflow/cycle_config.py +16 -15
  93. kailash/workflow/cycle_debugger.py +40 -40
  94. kailash/workflow/cycle_exceptions.py +29 -29
  95. kailash/workflow/cycle_profiler.py +21 -21
  96. kailash/workflow/cycle_state.py +20 -22
  97. kailash/workflow/cyclic_runner.py +44 -44
  98. kailash/workflow/graph.py +40 -40
  99. kailash/workflow/mermaid_visualizer.py +9 -11
  100. kailash/workflow/migration.py +22 -22
  101. kailash/workflow/mock_registry.py +6 -6
  102. kailash/workflow/runner.py +9 -9
  103. kailash/workflow/safety.py +12 -13
  104. kailash/workflow/state.py +8 -11
  105. kailash/workflow/templates.py +19 -19
  106. kailash/workflow/validation.py +14 -14
  107. kailash/workflow/visualization.py +22 -22
  108. {kailash-0.3.0.dist-info → kailash-0.3.1.dist-info}/METADATA +53 -5
  109. kailash-0.3.1.dist-info/RECORD +136 -0
  110. kailash-0.3.0.dist-info/RECORD +0 -130
  111. {kailash-0.3.0.dist-info → kailash-0.3.1.dist-info}/WHEEL +0 -0
  112. {kailash-0.3.0.dist-info → kailash-0.3.1.dist-info}/entry_points.txt +0 -0
  113. {kailash-0.3.0.dist-info → kailash-0.3.1.dist-info}/licenses/LICENSE +0 -0
  114. {kailash-0.3.0.dist-info → kailash-0.3.1.dist-info}/top_level.txt +0 -0
kailash/api/studio.py CHANGED
@@ -13,9 +13,9 @@ import json
13
13
  import logging
14
14
  import os
15
15
  import uuid
16
- from datetime import datetime, timezone
16
+ from datetime import UTC, datetime
17
17
  from pathlib import Path
18
- from typing import Any, Dict, List, Optional
18
+ from typing import Any
19
19
 
20
20
  import uvicorn
21
21
  from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
@@ -49,25 +49,25 @@ class NodeDefinition(BaseModel):
49
49
  category: str
50
50
  name: str
51
51
  description: str
52
- parameters: List[Dict[str, Any]]
53
- inputs: List[Dict[str, Any]]
54
- outputs: List[Dict[str, Any]]
52
+ parameters: list[dict[str, Any]]
53
+ inputs: list[dict[str, Any]]
54
+ outputs: list[dict[str, Any]]
55
55
 
56
56
 
57
57
  class WorkflowCreate(BaseModel):
58
58
  """Workflow creation request"""
59
59
 
60
60
  name: str
61
- description: Optional[str] = None
62
- definition: Dict[str, Any]
61
+ description: str | None = None
62
+ definition: dict[str, Any]
63
63
 
64
64
 
65
65
  class WorkflowUpdate(BaseModel):
66
66
  """Workflow update request"""
67
67
 
68
- name: Optional[str] = None
69
- description: Optional[str] = None
70
- definition: Optional[Dict[str, Any]] = None
68
+ name: str | None = None
69
+ description: str | None = None
70
+ definition: dict[str, Any] | None = None
71
71
 
72
72
 
73
73
  class WorkflowResponse(BaseModel):
@@ -75,8 +75,8 @@ class WorkflowResponse(BaseModel):
75
75
 
76
76
  id: str
77
77
  name: str
78
- description: Optional[str]
79
- definition: Dict[str, Any]
78
+ description: str | None
79
+ definition: dict[str, Any]
80
80
  created_at: datetime
81
81
  updated_at: datetime
82
82
 
@@ -84,7 +84,7 @@ class WorkflowResponse(BaseModel):
84
84
  class ExecutionRequest(BaseModel):
85
85
  """Workflow execution request"""
86
86
 
87
- parameters: Optional[Dict[str, Any]] = None
87
+ parameters: dict[str, Any] | None = None
88
88
 
89
89
 
90
90
  class ExecutionResponse(BaseModel):
@@ -94,16 +94,16 @@ class ExecutionResponse(BaseModel):
94
94
  workflow_id: str
95
95
  status: str
96
96
  started_at: datetime
97
- completed_at: Optional[datetime]
98
- result: Optional[Dict[str, Any]]
99
- error: Optional[str]
97
+ completed_at: datetime | None
98
+ result: dict[str, Any] | None
99
+ error: str | None
100
100
 
101
101
 
102
102
  class WorkflowImportRequest(BaseModel):
103
103
  """Workflow import request"""
104
104
 
105
105
  name: str
106
- description: Optional[str] = None
106
+ description: str | None = None
107
107
  format: str = Field(..., pattern="^(yaml|json|python)$")
108
108
  content: str
109
109
 
@@ -113,10 +113,10 @@ class WorkflowImportResponse(BaseModel):
113
113
 
114
114
  id: str
115
115
  name: str
116
- description: Optional[str]
117
- definition: Dict[str, Any]
116
+ description: str | None
117
+ definition: dict[str, Any]
118
118
  created_at: datetime
119
- warnings: List[str] = []
119
+ warnings: list[str] = []
120
120
 
121
121
 
122
122
  class WorkflowStudioAPI:
@@ -135,8 +135,8 @@ class WorkflowStudioAPI:
135
135
  self.setup_middleware()
136
136
  self.setup_routes()
137
137
  self.setup_storage()
138
- self.active_executions: Dict[str, asyncio.Task] = {}
139
- self.websocket_connections: Dict[str, List[WebSocket]] = {}
138
+ self.active_executions: dict[str, asyncio.Task] = {}
139
+ self.websocket_connections: dict[str, list[WebSocket]] = {}
140
140
 
141
141
  # Register custom nodes on startup
142
142
  self.app.add_event_handler("startup", self._register_custom_nodes)
@@ -231,7 +231,7 @@ class WorkflowStudioAPI:
231
231
  return {"status": "healthy", "tenant_id": self.tenant_id}
232
232
 
233
233
  # Node discovery endpoints
234
- @self.app.get("/api/nodes", response_model=Dict[str, List[NodeDefinition]])
234
+ @self.app.get("/api/nodes", response_model=dict[str, list[NodeDefinition]])
235
235
  async def list_nodes():
236
236
  """List all available nodes grouped by category"""
237
237
  try:
@@ -385,7 +385,7 @@ class WorkflowStudioAPI:
385
385
 
386
386
  # Add alias for backward compatibility
387
387
  @self.app.get(
388
- "/api/nodes/discover", response_model=Dict[str, List[NodeDefinition]]
388
+ "/api/nodes/discover", response_model=dict[str, list[NodeDefinition]]
389
389
  )
390
390
  async def discover_nodes():
391
391
  """Alias for list_nodes endpoint for backward compatibility"""
@@ -420,7 +420,7 @@ class WorkflowStudioAPI:
420
420
  )
421
421
 
422
422
  # Workflow management endpoints
423
- @self.app.get("/api/workflows", response_model=List[WorkflowResponse])
423
+ @self.app.get("/api/workflows", response_model=list[WorkflowResponse])
424
424
  async def list_workflows(
425
425
  limit: int = Query(100, ge=1, le=1000), offset: int = Query(0, ge=0)
426
426
  ):
@@ -434,7 +434,7 @@ class WorkflowStudioAPI:
434
434
 
435
435
  for workflow_file in workflow_files[offset : offset + limit]:
436
436
  try:
437
- with open(workflow_file, "r") as f:
437
+ with open(workflow_file) as f:
438
438
  data = json.load(f)
439
439
  workflows.append(WorkflowResponse(**data))
440
440
  except Exception as e:
@@ -446,7 +446,7 @@ class WorkflowStudioAPI:
446
446
  async def create_workflow(workflow: WorkflowCreate):
447
447
  """Create a new workflow"""
448
448
  workflow_id = str(uuid.uuid4())
449
- now = datetime.now(timezone.utc)
449
+ now = datetime.now(UTC)
450
450
 
451
451
  workflow_data = {
452
452
  "id": workflow_id,
@@ -471,7 +471,7 @@ class WorkflowStudioAPI:
471
471
  if not workflow_file.exists():
472
472
  raise HTTPException(status_code=404, detail="Workflow not found")
473
473
 
474
- with open(workflow_file, "r") as f:
474
+ with open(workflow_file) as f:
475
475
  data = json.load(f)
476
476
 
477
477
  return WorkflowResponse(**data)
@@ -484,7 +484,7 @@ class WorkflowStudioAPI:
484
484
  raise HTTPException(status_code=404, detail="Workflow not found")
485
485
 
486
486
  # Load existing workflow
487
- with open(workflow_file, "r") as f:
487
+ with open(workflow_file) as f:
488
488
  data = json.load(f)
489
489
 
490
490
  # Update fields
@@ -495,7 +495,7 @@ class WorkflowStudioAPI:
495
495
  if update.definition is not None:
496
496
  data["definition"] = update.definition
497
497
 
498
- data["updated_at"] = datetime.now(timezone.utc).isoformat()
498
+ data["updated_at"] = datetime.now(UTC).isoformat()
499
499
 
500
500
  # Save updated workflow
501
501
  with open(workflow_file, "w") as f:
@@ -524,7 +524,7 @@ class WorkflowStudioAPI:
524
524
  if not workflow_file.exists():
525
525
  raise HTTPException(status_code=404, detail="Workflow not found")
526
526
 
527
- with open(workflow_file, "r") as f:
527
+ with open(workflow_file) as f:
528
528
  workflow_data = json.load(f)
529
529
 
530
530
  # Create execution record
@@ -533,7 +533,7 @@ class WorkflowStudioAPI:
533
533
  "id": execution_id,
534
534
  "workflow_id": workflow_id,
535
535
  "status": "running",
536
- "started_at": datetime.now(timezone.utc).isoformat(),
536
+ "started_at": datetime.now(UTC).isoformat(),
537
537
  "completed_at": None,
538
538
  "result": None,
539
539
  "error": None,
@@ -560,7 +560,7 @@ class WorkflowStudioAPI:
560
560
  except Exception as e:
561
561
  execution_data["status"] = "failed"
562
562
  execution_data["error"] = str(e)
563
- execution_data["completed_at"] = datetime.now(timezone.utc).isoformat()
563
+ execution_data["completed_at"] = datetime.now(UTC).isoformat()
564
564
 
565
565
  with open(execution_file, "w") as f:
566
566
  json.dump(execution_data, f, indent=2)
@@ -576,7 +576,7 @@ class WorkflowStudioAPI:
576
576
  if not execution_file.exists():
577
577
  raise HTTPException(status_code=404, detail="Execution not found")
578
578
 
579
- with open(execution_file, "r") as f:
579
+ with open(execution_file) as f:
580
580
  data = json.load(f)
581
581
 
582
582
  return ExecutionResponse(**data)
@@ -602,7 +602,7 @@ class WorkflowStudioAPI:
602
602
  break
603
603
 
604
604
  # Send current status
605
- with open(execution_file, "r") as f:
605
+ with open(execution_file) as f:
606
606
  data = json.load(f)
607
607
  await websocket.send_json(data)
608
608
 
@@ -633,7 +633,7 @@ class WorkflowStudioAPI:
633
633
  if not workflow_file.exists():
634
634
  raise HTTPException(status_code=404, detail="Workflow not found")
635
635
 
636
- with open(workflow_file, "r") as f:
636
+ with open(workflow_file) as f:
637
637
  workflow_data = json.load(f)
638
638
 
639
639
  # Create workflow from definition
@@ -686,7 +686,7 @@ class WorkflowStudioAPI:
686
686
  warnings.append(f"Workflow validation warning: {str(e)}")
687
687
 
688
688
  # Create workflow record
689
- now = datetime.now(timezone.utc)
689
+ now = datetime.now(UTC)
690
690
  workflow_data = {
691
691
  "id": workflow_id,
692
692
  "name": request.name,
@@ -718,7 +718,7 @@ class WorkflowStudioAPI:
718
718
  execution_id: str,
719
719
  workflow: Workflow,
720
720
  runtime: LocalRuntime,
721
- parameters: Dict[str, Any],
721
+ parameters: dict[str, Any],
722
722
  ):
723
723
  """Execute workflow asynchronously and update status"""
724
724
  execution_file = self.executions_path / f"{execution_id}.json"
@@ -728,11 +728,11 @@ class WorkflowStudioAPI:
728
728
  result, run_id = runtime.execute(workflow, parameters=parameters)
729
729
 
730
730
  # Update execution record
731
- with open(execution_file, "r") as f:
731
+ with open(execution_file) as f:
732
732
  execution_data = json.load(f)
733
733
 
734
734
  execution_data["status"] = "completed"
735
- execution_data["completed_at"] = datetime.now(timezone.utc).isoformat()
735
+ execution_data["completed_at"] = datetime.now(UTC).isoformat()
736
736
  execution_data["result"] = result
737
737
 
738
738
  with open(execution_file, "w") as f:
@@ -743,11 +743,11 @@ class WorkflowStudioAPI:
743
743
 
744
744
  except Exception as e:
745
745
  # Update execution record with error
746
- with open(execution_file, "r") as f:
746
+ with open(execution_file) as f:
747
747
  execution_data = json.load(f)
748
748
 
749
749
  execution_data["status"] = "failed"
750
- execution_data["completed_at"] = datetime.now(timezone.utc).isoformat()
750
+ execution_data["completed_at"] = datetime.now(UTC).isoformat()
751
751
  execution_data["error"] = str(e)
752
752
 
753
753
  with open(execution_file, "w") as f:
@@ -761,7 +761,7 @@ class WorkflowStudioAPI:
761
761
  if execution_id in self.active_executions:
762
762
  del self.active_executions[execution_id]
763
763
 
764
- async def _notify_websocket_clients(self, execution_id: str, data: Dict[str, Any]):
764
+ async def _notify_websocket_clients(self, execution_id: str, data: dict[str, Any]):
765
765
  """Notify all WebSocket clients watching this execution"""
766
766
  if execution_id in self.websocket_connections:
767
767
  for websocket in self.websocket_connections[execution_id]:
@@ -851,7 +851,7 @@ class WorkflowStudioAPI:
851
851
 
852
852
  return "\n".join(lines)
853
853
 
854
- def _parse_python_workflow(self, python_code: str) -> Dict[str, Any]:
854
+ def _parse_python_workflow(self, python_code: str) -> dict[str, Any]:
855
855
  """Parse Python code to extract workflow definition.
856
856
 
857
857
  This is a simplified parser that extracts workflow structure from Python code.
@@ -868,7 +868,7 @@ class WorkflowStudioAPI:
868
868
  },
869
869
  }
870
870
 
871
- def _format_config(self, config: Dict[str, Any]) -> str:
871
+ def _format_config(self, config: dict[str, Any]) -> str:
872
872
  """Format config dict as Python code"""
873
873
  if not config:
874
874
  return ""
@@ -8,7 +8,7 @@ workflow as a REST API with minimal configuration.
8
8
  import asyncio
9
9
  from contextlib import asynccontextmanager
10
10
  from enum import Enum
11
- from typing import Any, Dict, List, Optional, Union
11
+ from typing import Any
12
12
 
13
13
  import uvicorn
14
14
  from fastapi import BackgroundTasks, FastAPI, HTTPException
@@ -31,8 +31,8 @@ class ExecutionMode(str, Enum):
31
31
  class WorkflowRequest(BaseModel):
32
32
  """Base request model for workflow execution."""
33
33
 
34
- inputs: Dict[str, Any] = Field(..., description="Input data for workflow nodes")
35
- config: Optional[Dict[str, Any]] = Field(
34
+ inputs: dict[str, Any] = Field(..., description="Input data for workflow nodes")
35
+ config: dict[str, Any] | None = Field(
36
36
  None, description="Node configuration overrides"
37
37
  )
38
38
  mode: ExecutionMode = Field(ExecutionMode.SYNC, description="Execution mode")
@@ -41,7 +41,7 @@ class WorkflowRequest(BaseModel):
41
41
  class WorkflowResponse(BaseModel):
42
42
  """Base response model for workflow execution."""
43
43
 
44
- outputs: Dict[str, Any] = Field(..., description="Output data from workflow nodes")
44
+ outputs: dict[str, Any] = Field(..., description="Output data from workflow nodes")
45
45
  execution_time: float = Field(..., description="Execution time in seconds")
46
46
  workflow_id: str = Field(..., description="Workflow identifier")
47
47
  version: str = Field(..., description="Workflow version")
@@ -63,7 +63,7 @@ class WorkflowAPI:
63
63
 
64
64
  def __init__(
65
65
  self,
66
- workflow: Union[WorkflowBuilder, Workflow],
66
+ workflow: WorkflowBuilder | Workflow,
67
67
  app_name: str = "Kailash Workflow API",
68
68
  version: str = "1.0.0",
69
69
  description: str = "API wrapper for Kailash workflow execution",
@@ -102,7 +102,7 @@ class WorkflowAPI:
102
102
  self._setup_routes()
103
103
 
104
104
  # Cache for async executions
105
- self._execution_cache: Dict[str, Dict[str, Any]] = {}
105
+ self._execution_cache: dict[str, dict[str, Any]] = {}
106
106
 
107
107
  @asynccontextmanager
108
108
  async def _lifespan(self, app: FastAPI):
@@ -317,12 +317,12 @@ class HierarchicalRAGAPI(WorkflowAPI):
317
317
 
318
318
  class RAGResponse(BaseModel):
319
319
  answer: str
320
- sources: List[Dict[str, Any]]
320
+ sources: list[dict[str, Any]]
321
321
  query: str
322
322
  execution_time: float
323
323
 
324
324
  @self.app.post("/documents")
325
- async def add_documents(documents: List[Document]):
325
+ async def add_documents(documents: list[Document]):
326
326
  """Add documents to the knowledge base."""
327
327
  # This would integrate with document storage
328
328
  return {"message": f"Added {len(documents)} documents"}
kailash/cli/commands.py CHANGED
@@ -4,7 +4,6 @@ import json
4
4
  import logging
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Optional
8
7
 
9
8
  import click
10
9
 
@@ -77,7 +76,7 @@ def init(name: str, template: str):
77
76
  @click.option("--params", "-p", help="JSON file with parameter overrides")
78
77
  @click.option("--debug", is_flag=True, help="Enable debug mode")
79
78
  @click.option("--no-tracking", is_flag=True, help="Disable task tracking")
80
- def run(workflow_file: str, params: Optional[str], debug: bool, no_tracking: bool):
79
+ def run(workflow_file: str, params: str | None, debug: bool, no_tracking: bool):
81
80
  """Run a workflow locally."""
82
81
  try:
83
82
  # Validate workflow file exists
@@ -97,7 +96,7 @@ def run(workflow_file: str, params: Optional[str], debug: bool, no_tracking: boo
97
96
  parameters = {}
98
97
  if params:
99
98
  try:
100
- with open(params, "r") as f:
99
+ with open(params) as f:
101
100
  parameters = json.load(f)
102
101
  except FileNotFoundError:
103
102
  raise CLIException(f"Parameters file not found: {params}")
@@ -190,7 +189,7 @@ def validate(workflow_file: str):
190
189
  "--format", default="yaml", type=click.Choice(["yaml", "json", "manifest"])
191
190
  )
192
191
  @click.option("--registry", help="Container registry URL")
193
- def export(workflow_file: str, output_file: str, format: str, registry: Optional[str]):
192
+ def export(workflow_file: str, output_file: str, format: str, registry: str | None):
194
193
  """Export workflow to Kailash format."""
195
194
  try:
196
195
  # Validate workflow file exists
@@ -233,14 +232,13 @@ def export(workflow_file: str, output_file: str, format: str, registry: Optional
233
232
  @cli.group()
234
233
  def tasks():
235
234
  """Task tracking commands."""
236
- pass
237
235
 
238
236
 
239
237
  @tasks.command("list")
240
238
  @click.option("--workflow", help="Filter by workflow name")
241
239
  @click.option("--status", help="Filter by status")
242
240
  @click.option("--limit", default=10, help="Number of runs to show")
243
- def list_tasks(workflow: Optional[str], status: Optional[str], limit: int):
241
+ def list_tasks(workflow: str | None, status: str | None, limit: int):
244
242
  """List workflow runs."""
245
243
  try:
246
244
  task_manager = TaskManager()
@@ -380,7 +378,6 @@ def clear_tasks():
380
378
  @cli.group()
381
379
  def nodes():
382
380
  """Node management commands."""
383
- pass
384
381
 
385
382
 
386
383
  @nodes.command("list")
@@ -497,7 +494,7 @@ def _load_python_workflow(workflow_file: str) -> Workflow:
497
494
  try:
498
495
  # Read and execute Python file
499
496
  global_scope = {}
500
- with open(workflow_file, "r") as f:
497
+ with open(workflow_file) as f:
501
498
  code = f.read()
502
499
 
503
500
  exec(code, global_scope)