kailash 0.1.4__py3-none-any.whl → 0.2.0__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 (83) 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 +38 -0
  21. kailash/nodes/ai/a2a.py +1790 -0
  22. kailash/nodes/ai/agents.py +116 -2
  23. kailash/nodes/ai/ai_providers.py +206 -8
  24. kailash/nodes/ai/intelligent_agent_orchestrator.py +2108 -0
  25. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  26. kailash/nodes/ai/llm_agent.py +324 -1
  27. kailash/nodes/ai/self_organizing.py +1623 -0
  28. kailash/nodes/api/http.py +106 -25
  29. kailash/nodes/api/rest.py +116 -21
  30. kailash/nodes/base.py +15 -2
  31. kailash/nodes/base_async.py +45 -0
  32. kailash/nodes/base_cycle_aware.py +374 -0
  33. kailash/nodes/base_with_acl.py +338 -0
  34. kailash/nodes/code/python.py +135 -27
  35. kailash/nodes/data/readers.py +116 -53
  36. kailash/nodes/data/writers.py +16 -6
  37. kailash/nodes/logic/__init__.py +8 -0
  38. kailash/nodes/logic/async_operations.py +48 -9
  39. kailash/nodes/logic/convergence.py +642 -0
  40. kailash/nodes/logic/loop.py +153 -0
  41. kailash/nodes/logic/operations.py +212 -27
  42. kailash/nodes/logic/workflow.py +26 -18
  43. kailash/nodes/mixins/__init__.py +11 -0
  44. kailash/nodes/mixins/mcp.py +228 -0
  45. kailash/nodes/mixins.py +387 -0
  46. kailash/nodes/transform/__init__.py +8 -1
  47. kailash/nodes/transform/processors.py +119 -4
  48. kailash/runtime/__init__.py +2 -1
  49. kailash/runtime/access_controlled.py +458 -0
  50. kailash/runtime/local.py +106 -33
  51. kailash/runtime/parallel_cyclic.py +529 -0
  52. kailash/sdk_exceptions.py +90 -5
  53. kailash/security.py +845 -0
  54. kailash/tracking/manager.py +38 -15
  55. kailash/tracking/models.py +1 -1
  56. kailash/tracking/storage/filesystem.py +30 -2
  57. kailash/utils/__init__.py +8 -0
  58. kailash/workflow/__init__.py +18 -0
  59. kailash/workflow/convergence.py +270 -0
  60. kailash/workflow/cycle_analyzer.py +768 -0
  61. kailash/workflow/cycle_builder.py +573 -0
  62. kailash/workflow/cycle_config.py +709 -0
  63. kailash/workflow/cycle_debugger.py +760 -0
  64. kailash/workflow/cycle_exceptions.py +601 -0
  65. kailash/workflow/cycle_profiler.py +671 -0
  66. kailash/workflow/cycle_state.py +338 -0
  67. kailash/workflow/cyclic_runner.py +985 -0
  68. kailash/workflow/graph.py +500 -39
  69. kailash/workflow/migration.py +768 -0
  70. kailash/workflow/safety.py +365 -0
  71. kailash/workflow/templates.py +744 -0
  72. kailash/workflow/validation.py +693 -0
  73. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/METADATA +446 -13
  74. kailash-0.2.0.dist-info/RECORD +125 -0
  75. kailash/nodes/mcp/__init__.py +0 -11
  76. kailash/nodes/mcp/client.py +0 -554
  77. kailash/nodes/mcp/resource.py +0 -682
  78. kailash/nodes/mcp/server.py +0 -577
  79. kailash-0.1.4.dist-info/RECORD +0 -85
  80. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/WHEEL +0 -0
  81. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/entry_points.txt +0 -0
  82. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/licenses/LICENSE +0 -0
  83. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/top_level.txt +0 -0
@@ -33,63 +33,117 @@ import json
33
33
  from typing import Any, Dict
34
34
 
35
35
  from kailash.nodes.base import Node, NodeParameter, register_node
36
+ from kailash.security import safe_open, validate_file_path
36
37
 
37
38
 
38
39
  @register_node()
39
40
  class CSVReaderNode(Node):
40
- """Reads data from a CSV file.
41
-
42
- This node provides robust CSV file reading capabilities with support for
43
- various delimiters, header detection, and encoding options. It's designed
44
- to handle common CSV formats and edge cases.
45
-
46
- Design Features:
47
- 1. Automatic header detection
48
- 2. Configurable delimiters
49
- 3. Memory-efficient line-by-line reading
50
- 4. Consistent dictionary output format
51
- 5. Unicode support through encoding parameter
52
-
53
- Data Flow:
54
- - Input: File path and configuration parameters
55
- - Processing: Reads CSV line by line, converting to dictionaries
56
- - Output: List of dictionaries (with headers) or list of lists
57
-
58
- Common Usage Patterns:
59
- 1. Reading data exports from databases
60
- 2. Processing spreadsheet data
61
- 3. Loading configuration from CSV
62
- 4. Ingesting sensor data logs
63
-
64
- Upstream Sources:
65
- - File system paths from user input
66
- - Output paths from previous nodes
67
- - Configuration management systems
41
+ """
42
+ Reads data from CSV files with automatic header detection and type inference.
43
+
44
+ This node provides comprehensive CSV file reading capabilities, handling various
45
+ formats, encodings, and edge cases. It automatically detects headers, infers data
46
+ types, and provides consistent structured output for downstream processing in
47
+ Kailash workflows.
48
+
49
+ Design Philosophy:
50
+ The CSVReaderNode embodies the principle of "data accessibility without
51
+ complexity." It abstracts the intricacies of CSV parsing while providing
52
+ flexibility for various formats. The design prioritizes memory efficiency,
53
+ automatic format detection, and consistent output structure, making it easy
54
+ to integrate diverse CSV data sources into workflows.
55
+
56
+ Upstream Dependencies:
57
+ - File system providing CSV files
58
+ - Workflow orchestrators specifying file paths
59
+ - Configuration systems providing parsing options
60
+ - Previous nodes generating CSV file paths
61
+ - User inputs defining data sources
68
62
 
69
63
  Downstream Consumers:
70
- - DataTransformer: Processes tabular data
71
- - Aggregator: Summarizes data
72
- - CSVWriter: Reformats and saves
73
- - Visualizer: Creates charts from data
64
+ - DataTransformNode: Processes tabular data
65
+ - FilterNode: Applies row/column filtering
66
+ - AggregatorNode: Summarizes data
67
+ - PythonCodeNode: Custom data processing
68
+ - WriterNodes: Exports to other formats
69
+ - Visualization nodes: Creates charts
70
+ - ML nodes: Uses as training data
71
+
72
+ Configuration:
73
+ The node supports extensive CSV parsing options:
74
+ - Delimiter detection (comma, tab, pipe, etc.)
75
+ - Header row identification
76
+ - Encoding specification (UTF-8, Latin-1, etc.)
77
+ - Quote character handling
78
+ - Skip rows/comments functionality
79
+ - Column type inference
80
+ - Missing value handling
81
+
82
+ Implementation Details:
83
+ - Uses Python's csv module for robust parsing
84
+ - Implements streaming for large files
85
+ - Automatic delimiter detection when not specified
86
+ - Header detection based on first row analysis
87
+ - Type inference for numeric/date columns
88
+ - Memory-efficient processing with generators
89
+ - Unicode normalization for consistent encoding
74
90
 
75
91
  Error Handling:
76
- - FileNotFoundError: Invalid file path
77
- - PermissionError: Insufficient read permissions
78
- - UnicodeDecodeError: Encoding mismatch
79
- - csv.Error: Malformed CSV data
80
-
81
- Example:
82
- >>> # Read customer data with headers
83
- >>> reader = CSVReaderNode(
84
- ... file_path='customers.csv',
85
- ... headers=True,
86
- ... delimiter=','
92
+ - FileNotFoundError: Clear message with path
93
+ - PermissionError: Access rights guidance
94
+ - UnicodeDecodeError: Encoding detection hints
95
+ - csv.Error: Malformed data diagnostics
96
+ - EmptyFileError: Handles zero-byte files
97
+ - Partial read recovery for corrupted files
98
+
99
+ Side Effects:
100
+ - Reads from file system
101
+ - May consume significant memory for large files
102
+ - Creates file handles (properly closed)
103
+ - Updates internal read statistics
104
+
105
+ Examples:
106
+ >>> # Basic CSV reading with headers
107
+ >>> reader = CSVReaderNode()
108
+ >>> result = reader.run(
109
+ ... file_path="customers.csv",
110
+ ... headers=True
87
111
  ... )
88
- >>> result = reader.execute()
89
- >>> # result['data'] = [
90
- >>> # {'id': '1', 'name': 'John', 'age': '30'},
91
- >>> # {'id': '2', 'name': 'Jane', 'age': '25'}
112
+ >>> assert isinstance(result["data"], list)
113
+ >>> assert all(isinstance(row, dict) for row in result["data"])
114
+ >>> # Example output:
115
+ >>> # result["data"] = [
116
+ >>> # {"id": "1", "name": "John Doe", "age": "30"},
117
+ >>> # {"id": "2", "name": "Jane Smith", "age": "25"}
92
118
  >>> # ]
119
+ >>>
120
+ >>> # Reading with custom delimiter
121
+ >>> result = reader.run(
122
+ ... file_path="data.tsv",
123
+ ... delimiter="\\t",
124
+ ... headers=True
125
+ ... )
126
+ >>>
127
+ >>> # Reading without headers (returns list of lists)
128
+ >>> result = reader.run(
129
+ ... file_path="data.csv",
130
+ ... headers=False
131
+ ... )
132
+ >>> assert all(isinstance(row, list) for row in result["data"])
133
+ >>>
134
+ >>> # Reading with specific encoding
135
+ >>> result = reader.run(
136
+ ... file_path="european_data.csv",
137
+ ... encoding="iso-8859-1",
138
+ ... headers=True
139
+ ... )
140
+ >>>
141
+ >>> # Handling quoted fields
142
+ >>> result = reader.run(
143
+ ... file_path="complex.csv",
144
+ ... headers=True,
145
+ ... quotechar='"'
146
+ ... )
93
147
  """
94
148
 
95
149
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -192,7 +246,7 @@ class CSVReaderNode(Node):
192
246
  - Analyzers can process row-by-row
193
247
  - data_indexed is useful for lookups and joins
194
248
  """
195
- file_path = kwargs["file_path"]
249
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
196
250
  headers = kwargs.get("headers", True)
197
251
  delimiter = kwargs.get("delimiter", ",")
198
252
  index_column = kwargs.get("index_column")
@@ -200,7 +254,10 @@ class CSVReaderNode(Node):
200
254
  data = []
201
255
  data_indexed = {}
202
256
 
203
- with open(file_path, "r", encoding="utf-8") as f:
257
+ # Validate file path for security
258
+ validated_path = validate_file_path(file_path, operation="CSV read")
259
+
260
+ with safe_open(validated_path, "r", encoding="utf-8") as f:
204
261
  reader = csv.reader(f, delimiter=delimiter)
205
262
 
206
263
  if headers:
@@ -349,9 +406,12 @@ class JSONReaderNode(Node):
349
406
  - Compatible with JSONWriter for round-trip
350
407
  - Transform nodes can process nested data
351
408
  """
352
- file_path = kwargs["file_path"]
409
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
353
410
 
354
- with open(file_path, "r", encoding="utf-8") as f:
411
+ # Validate file path for security
412
+ validated_path = validate_file_path(file_path, operation="JSON read")
413
+
414
+ with safe_open(validated_path, "r", encoding="utf-8") as f:
355
415
  data = json.load(f)
356
416
 
357
417
  return {"data": data}
@@ -487,10 +547,13 @@ class TextReaderNode(Node):
487
547
  - Pattern nodes can search content
488
548
  - Writers can save processed text
489
549
  """
490
- file_path = kwargs["file_path"]
550
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
491
551
  encoding = kwargs.get("encoding", "utf-8")
492
552
 
493
- with open(file_path, "r", encoding=encoding) as f:
553
+ # Validate file path for security
554
+ validated_path = validate_file_path(file_path, operation="text read")
555
+
556
+ with safe_open(validated_path, "r", encoding=encoding) as f:
494
557
  text = f.read()
495
558
 
496
559
  return {"text": text}
@@ -34,6 +34,7 @@ import json
34
34
  from typing import Any, Dict
35
35
 
36
36
  from kailash.nodes.base import Node, NodeParameter, register_node
37
+ from kailash.security import safe_open, validate_file_path
37
38
 
38
39
 
39
40
  @register_node()
@@ -190,7 +191,7 @@ class CSVWriterNode(Node):
190
191
  - External tools can process output
191
192
  - Metrics available for monitoring
192
193
  """
193
- file_path = kwargs["file_path"]
194
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
194
195
  data = kwargs["data"]
195
196
  headers = kwargs.get("headers")
196
197
  delimiter = kwargs.get("delimiter", ",")
@@ -198,7 +199,10 @@ class CSVWriterNode(Node):
198
199
  if not data:
199
200
  return {"rows_written": 0}
200
201
 
201
- with open(file_path, "w", newline="", encoding="utf-8") as f:
202
+ # Validate file path for security
203
+ validated_path = validate_file_path(file_path, operation="CSV write")
204
+
205
+ with safe_open(validated_path, "w", newline="", encoding="utf-8") as f:
202
206
  if isinstance(data[0], dict):
203
207
  # Writing dictionaries
204
208
  if not headers:
@@ -357,11 +361,14 @@ class JSONWriterNode(Node):
357
361
  - Version control can track
358
362
  - APIs can import data
359
363
  """
360
- file_path = kwargs["file_path"]
364
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
361
365
  data = kwargs["data"]
362
366
  indent = kwargs.get("indent", 2)
363
367
 
364
- with open(file_path, "w", encoding="utf-8") as f:
368
+ # Validate file path for security
369
+ validated_path = validate_file_path(file_path, operation="JSON write")
370
+
371
+ with safe_open(validated_path, "w", encoding="utf-8") as f:
365
372
  json.dump(data, f, indent=indent, ensure_ascii=False)
366
373
 
367
374
  return {"file_path": file_path}
@@ -517,13 +524,16 @@ class TextWriterNode(Node):
517
524
  - Log analyzers can process
518
525
  - Metrics available for monitoring
519
526
  """
520
- file_path = kwargs["file_path"]
527
+ file_path = kwargs.get("file_path") or self.config.get("file_path")
521
528
  text = kwargs["text"]
522
529
  encoding = kwargs.get("encoding", "utf-8")
523
530
  append = kwargs.get("append", False)
524
531
 
525
532
  mode = "a" if append else "w"
526
- with open(file_path, mode, encoding=encoding) as f:
533
+ # Validate file path for security
534
+ validated_path = validate_file_path(file_path, operation="text write")
535
+
536
+ with safe_open(validated_path, mode, encoding=encoding) as f:
527
537
  f.write(text)
528
538
 
529
539
  return {"file_path": file_path, "bytes_written": len(text.encode(encoding))}
@@ -1,6 +1,11 @@
1
1
  """Logic operation nodes for the Kailash SDK."""
2
2
 
3
3
  from kailash.nodes.logic.async_operations import AsyncMergeNode, AsyncSwitchNode
4
+ from kailash.nodes.logic.convergence import (
5
+ ConvergenceCheckerNode,
6
+ MultiCriteriaConvergenceNode,
7
+ )
8
+ from kailash.nodes.logic.loop import LoopNode
4
9
  from kailash.nodes.logic.operations import MergeNode, SwitchNode
5
10
  from kailash.nodes.logic.workflow import WorkflowNode
6
11
 
@@ -10,4 +15,7 @@ __all__ = [
10
15
  "AsyncSwitchNode",
11
16
  "AsyncMergeNode",
12
17
  "WorkflowNode",
18
+ "LoopNode",
19
+ "ConvergenceCheckerNode",
20
+ "MultiCriteriaConvergenceNode",
13
21
  ]
@@ -32,15 +32,25 @@ class AsyncMergeNode(AsyncNode):
32
32
  concat (list concatenation), zip (parallel iteration), and merge_dict
33
33
  (dictionary merging with optional key-based joining).
34
34
 
35
- Usage example:
36
- # Create an AsyncMergeNode in a workflow
37
- async_merge = AsyncMergeNode(merge_type="merge_dict", key="id")
38
- workflow.add_node("data_combine", async_merge)
39
-
40
- # Connect multiple data sources
41
- workflow.connect("api_results", "data_combine", {"output": "data1"})
42
- workflow.connect("database_query", "data_combine", {"results": "data2"})
43
- workflow.connect("file_processor", "data_combine", {"processed_data": "data3"})
35
+ Example usage:
36
+ >>> # Create an AsyncMergeNode
37
+ >>> async_merge = AsyncMergeNode(merge_type="merge_dict", key="id")
38
+ >>> async_merge.metadata.name
39
+ 'AsyncMergeNode'
40
+
41
+ >>> # Using in a workflow
42
+ >>> from kailash.workflow.graph import Workflow
43
+ >>> workflow = Workflow("wf-001", "async_example")
44
+ >>> workflow.add_node("data_combine", async_merge)
45
+ >>> "data_combine" in workflow.nodes
46
+ True
47
+
48
+ >>> # Async execution with concat
49
+ >>> import asyncio
50
+ >>> async_merge = AsyncMergeNode(merge_type="concat")
51
+ >>> result = asyncio.run(async_merge.execute_async(data1=[1, 2], data2=[3, 4]))
52
+ >>> result['merged_data']
53
+ [1, 2, 3, 4]
44
54
  """
45
55
 
46
56
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -364,6 +374,35 @@ class AsyncSwitchNode(AsyncNode):
364
374
 
365
375
  The basic functionality is the same as the synchronous SwitchNode but optimized
366
376
  for asynchronous execution.
377
+
378
+ Example usage:
379
+ >>> # Boolean condition routing
380
+ >>> import asyncio
381
+ >>> async_switch = AsyncSwitchNode(
382
+ ... condition_field="status",
383
+ ... operator="==",
384
+ ... value="active"
385
+ ... )
386
+ >>> result = asyncio.run(async_switch.execute_async(
387
+ ... input_data={"status": "active", "data": "test"}
388
+ ... ))
389
+ >>> result['true_output']
390
+ {'status': 'active', 'data': 'test'}
391
+ >>> result['false_output'] is None
392
+ True
393
+
394
+ >>> # Multi-case switching
395
+ >>> async_switch = AsyncSwitchNode(
396
+ ... condition_field="priority",
397
+ ... cases=["high", "medium", "low"]
398
+ ... )
399
+ >>> result = asyncio.run(async_switch.execute_async(
400
+ ... input_data={"priority": "high", "task": "urgent"}
401
+ ... ))
402
+ >>> result['case_high']
403
+ {'priority': 'high', 'task': 'urgent'}
404
+ >>> result['default']
405
+ {'priority': 'high', 'task': 'urgent'}
367
406
  """
368
407
 
369
408
  def get_parameters(self) -> Dict[str, NodeParameter]: