kailash 0.1.3__py3-none-any.whl → 0.1.5__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 (40) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/api/__init__.py +11 -1
  3. kailash/api/gateway.py +394 -0
  4. kailash/api/mcp_integration.py +478 -0
  5. kailash/api/workflow_api.py +29 -13
  6. kailash/nodes/ai/__init__.py +40 -4
  7. kailash/nodes/ai/a2a.py +1143 -0
  8. kailash/nodes/ai/agents.py +120 -6
  9. kailash/nodes/ai/ai_providers.py +224 -30
  10. kailash/nodes/ai/embedding_generator.py +34 -38
  11. kailash/nodes/ai/intelligent_agent_orchestrator.py +2114 -0
  12. kailash/nodes/ai/llm_agent.py +351 -356
  13. kailash/nodes/ai/self_organizing.py +1624 -0
  14. kailash/nodes/api/http.py +106 -25
  15. kailash/nodes/api/rest.py +116 -21
  16. kailash/nodes/base.py +60 -64
  17. kailash/nodes/code/python.py +61 -42
  18. kailash/nodes/data/__init__.py +10 -10
  19. kailash/nodes/data/readers.py +117 -66
  20. kailash/nodes/data/retrieval.py +1 -1
  21. kailash/nodes/data/sharepoint_graph.py +23 -25
  22. kailash/nodes/data/sql.py +24 -26
  23. kailash/nodes/data/writers.py +41 -44
  24. kailash/nodes/logic/__init__.py +9 -3
  25. kailash/nodes/logic/async_operations.py +60 -21
  26. kailash/nodes/logic/operations.py +43 -22
  27. kailash/nodes/logic/workflow.py +26 -18
  28. kailash/nodes/mcp/client.py +29 -33
  29. kailash/nodes/transform/__init__.py +8 -1
  30. kailash/nodes/transform/formatters.py +1 -1
  31. kailash/nodes/transform/processors.py +119 -4
  32. kailash/tracking/metrics_collector.py +6 -7
  33. kailash/utils/export.py +2 -2
  34. kailash/utils/templates.py +16 -16
  35. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/METADATA +293 -29
  36. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/RECORD +40 -35
  37. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/WHEEL +0 -0
  38. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/entry_points.txt +0 -0
  39. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/licenses/LICENSE +0 -0
  40. {kailash-0.1.3.dist-info → kailash-0.1.5.dist-info}/top_level.txt +0 -0
@@ -13,14 +13,14 @@ from kailash.nodes.base_async import AsyncNode
13
13
 
14
14
 
15
15
  @register_node()
16
- class AsyncMerge(AsyncNode):
16
+ class AsyncMergeNode(AsyncNode):
17
17
  """Asynchronously merges multiple data sources.
18
18
 
19
19
  Note: We implement run() to fulfill the Node abstract base class requirement,
20
20
  but it's just a pass-through to async_run().
21
21
 
22
22
 
23
- This node extends the standard Merge node with asynchronous execution capabilities,
23
+ This node extends the standard MergeNode with asynchronous execution capabilities,
24
24
  making it more efficient for:
25
25
 
26
26
  1. Combining large datasets from parallel branches
@@ -28,23 +28,33 @@ class AsyncMerge(AsyncNode):
28
28
  3. Processing streaming data in chunks
29
29
  4. Aggregating results from various API calls
30
30
 
31
- The merge operation supports the same types as the standard Merge node:
31
+ The merge operation supports the same types as the standard MergeNode:
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 AsyncMerge node in a workflow
37
- async_merge = AsyncMerge(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]:
47
- """Define parameters for the AsyncMerge node."""
57
+ """Define parameters for the AsyncMergeNode."""
48
58
  # Reuse parameters from SyncMerge
49
59
  return {
50
60
  "data1": NodeParameter(
@@ -107,7 +117,7 @@ class AsyncMerge(AsyncNode):
107
117
  }
108
118
 
109
119
  def get_output_schema(self) -> Dict[str, NodeParameter]:
110
- """Define the output schema for AsyncMerge."""
120
+ """Define the output schema for AsyncMergeNode."""
111
121
  return {
112
122
  "merged_data": NodeParameter(
113
123
  name="merged_data",
@@ -155,7 +165,7 @@ class AsyncMerge(AsyncNode):
155
165
 
156
166
  # Check if we have at least one valid input
157
167
  if not data_inputs:
158
- self.logger.warning("No valid data inputs provided to AsyncMerge node")
168
+ self.logger.warning("No valid data inputs provided to AsyncMergeNode")
159
169
  return {"merged_data": None}
160
170
 
161
171
  # If only one input was provided, return it directly
@@ -207,7 +217,7 @@ class AsyncMerge(AsyncNode):
207
217
  # This will be properly wrapped by the execute() method
208
218
  # which will call it in a sync context
209
219
  raise RuntimeError(
210
- "AsyncMerge.run() was called directly. Use execute() or execute_async() instead."
220
+ "AsyncMergeNode.run() was called directly. Use execute() or execute_async() instead."
211
221
  )
212
222
 
213
223
  async def _async_concat(self, data_inputs: List[Any], chunk_size: int) -> Any:
@@ -349,25 +359,54 @@ class AsyncMerge(AsyncNode):
349
359
 
350
360
 
351
361
  @register_node()
352
- class AsyncSwitch(AsyncNode):
362
+ class AsyncSwitchNode(AsyncNode):
353
363
  """Asynchronously routes data to different outputs based on conditions.
354
364
 
355
365
  Note: We implement run() to fulfill the Node abstract base class requirement,
356
366
  but it's just a pass-through to async_run().
357
367
 
358
- This node extends the standard Switch node with asynchronous execution capabilities,
368
+ This node extends the standard SwitchNode with asynchronous execution capabilities,
359
369
  making it more efficient for:
360
370
 
361
371
  1. Processing conditional routing with I/O-bound condition evaluation
362
372
  2. Handling large datasets that need to be routed based on complex criteria
363
373
  3. Integrating with other asynchronous nodes in a workflow
364
374
 
365
- The basic functionality is the same as the synchronous Switch node but optimized
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]:
370
- """Define parameters for the AsyncSwitch node."""
409
+ """Define parameters for the AsyncSwitchNode."""
371
410
  return {
372
411
  "input_data": NodeParameter(
373
412
  name="input_data",
@@ -603,7 +642,7 @@ class AsyncSwitch(AsyncNode):
603
642
  # This will be properly wrapped by the execute() method
604
643
  # which will call it in a sync context
605
644
  raise RuntimeError(
606
- "AsyncSwitch.run() was called directly. Use execute() or execute_async() instead."
645
+ "AsyncSwitchNode.run() was called directly. Use execute() or execute_async() instead."
607
646
  )
608
647
 
609
648
  async def _evaluate_condition(
@@ -11,7 +11,7 @@ from kailash.nodes.base import Node, NodeParameter, register_node
11
11
 
12
12
 
13
13
  @register_node()
14
- class Switch(Node):
14
+ class SwitchNode(Node):
15
15
  """Routes data to different outputs based on conditions.
16
16
 
17
17
  The Switch node enables conditional branching in workflows by evaluating
@@ -23,25 +23,21 @@ class Switch(Node):
23
23
  3. Dynamic workflow paths based on data values
24
24
 
25
25
  The outputs of Switch nodes are typically connected to different processing
26
- nodes, and those branches can be rejoined later using a Merge node.
27
-
28
- Example usage::
29
-
30
- # Simple boolean condition
31
- switch_node = Switch(condition_field="status", operator="==", value="success")
32
- workflow.add_node("router", switch_node)
33
- workflow.connect("router", "success_handler", {"true_output": "input"})
34
- workflow.connect("router", "error_handler", {"false_output": "input"})
35
-
36
- # Multi-case switching
37
- switch_node = Switch(
38
- condition_field="status",
39
- cases=["success", "warning", "error"]
40
- )
41
- workflow.connect("router", "success_handler", {"case_success": "input"})
42
- workflow.connect("router", "warning_handler", {"case_warning": "input"})
43
- workflow.connect("router", "error_handler", {"case_error": "input"})
44
- workflow.connect("router", "default_handler", {"default": "input"})
26
+ nodes, and those branches can be rejoined later using a MergeNode.
27
+
28
+ Example usage:
29
+ >>> # Simple boolean condition
30
+ >>> switch_node = SwitchNode(condition_field="status", operator="==", value="success")
31
+ >>> switch_node.metadata.name
32
+ 'SwitchNode'
33
+
34
+ >>> # Multi-case switching
35
+ >>> switch_node = SwitchNode(
36
+ ... condition_field="status",
37
+ ... cases=["success", "warning", "error"]
38
+ ... )
39
+ >>> 'cases' in switch_node.get_parameters()
40
+ True
45
41
  """
46
42
 
47
43
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -360,7 +356,7 @@ class Switch(Node):
360
356
 
361
357
 
362
358
  @register_node()
363
- class Merge(Node):
359
+ class MergeNode(Node):
364
360
  """Merges multiple data sources.
365
361
 
366
362
  This node can combine data from multiple input sources in various ways,
@@ -368,12 +364,37 @@ class Merge(Node):
368
364
 
369
365
  1. Combining results from parallel branches in a workflow
370
366
  2. Joining related data sets
371
- 3. Combining outputs after conditional branching with the Switch node
367
+ 3. Combining outputs after conditional branching with the SwitchNode
372
368
  4. Aggregating collections of data
373
369
 
374
370
  The merge operation is determined by the merge_type parameter, which supports
375
371
  concat (list concatenation), zip (parallel iteration), and merge_dict (dictionary
376
372
  merging with optional key-based joining for lists of dictionaries).
373
+
374
+ Example usage:
375
+ >>> # Simple list concatenation
376
+ >>> merge_node = MergeNode(merge_type="concat")
377
+ >>> result = merge_node.execute(data1=[1, 2], data2=[3, 4])
378
+ >>> result['merged_data']
379
+ [1, 2, 3, 4]
380
+
381
+ >>> # Dictionary merging
382
+ >>> merge_node = MergeNode(merge_type="merge_dict")
383
+ >>> result = merge_node.execute(
384
+ ... data1={"a": 1, "b": 2},
385
+ ... data2={"b": 3, "c": 4}
386
+ ... )
387
+ >>> result['merged_data']
388
+ {'a': 1, 'b': 3, 'c': 4}
389
+
390
+ >>> # List of dicts merging by key
391
+ >>> merge_node = MergeNode(merge_type="merge_dict", key="id")
392
+ >>> result = merge_node.execute(
393
+ ... data1=[{"id": 1, "name": "Alice"}],
394
+ ... data2=[{"id": 1, "age": 30}]
395
+ ... )
396
+ >>> result['merged_data']
397
+ [{'id': 1, 'name': 'Alice', 'age': 30}]
377
398
  """
378
399
 
379
400
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -52,24 +52,32 @@ class WorkflowNode(Node):
52
52
  - Runtime executing the inner workflow
53
53
  - Results passed to subsequent nodes
54
54
 
55
- Usage Patterns:
56
- 1. Direct workflow wrapping:
57
- ```python
58
- inner_workflow = Workflow("data_processing")
59
- # ... build workflow ...
60
- node = WorkflowNode(workflow=inner_workflow)
61
- ```
62
-
63
- 2. Loading from file:
64
- ```python
65
- node = WorkflowNode(workflow_path="workflows/processor.yaml")
66
- ```
67
-
68
- 3. Loading from dictionary:
69
- ```python
70
- workflow_dict = {"nodes": {...}, "connections": [...]}
71
- node = WorkflowNode(workflow_dict=workflow_dict)
72
- ```
55
+ Example usage:
56
+ >>> # Direct workflow wrapping
57
+ >>> from kailash.workflow.graph import Workflow
58
+ >>> from kailash.nodes.data.readers import CSVReaderNode
59
+ >>> inner_workflow = Workflow("wf-001", "data_processing")
60
+ >>> inner_workflow.add_node("reader", CSVReaderNode(file_path="data.csv"))
61
+ >>> node = WorkflowNode(workflow=inner_workflow)
62
+ >>> node.metadata.name
63
+ 'WorkflowNode'
64
+
65
+ >>> # Get parameters from wrapped workflow
66
+ >>> params = node.get_parameters()
67
+ >>> 'reader_file_path' in params
68
+ True
69
+ >>> 'inputs' in params
70
+ True
71
+
72
+ >>> # Loading from dictionary
73
+ >>> workflow_dict = {
74
+ ... "name": "simple",
75
+ ... "nodes": {"node1": {"type": "CSVReaderNode", "config": {"file_path": "test.csv"}}},
76
+ ... "connections": []
77
+ ... }
78
+ >>> node = WorkflowNode(workflow_dict=workflow_dict)
79
+ >>> node._workflow.name
80
+ 'simple'
73
81
 
74
82
  Implementation Details:
75
83
  - Parameters derived from workflow entry nodes
@@ -22,7 +22,7 @@ class MCPClient(Node):
22
22
  - Input parameters for resource requests and tool calls
23
23
 
24
24
  Downstream Consumers:
25
- - LLMAgent nodes that need context from MCP servers
25
+ - LLMAgentNode nodes that need context from MCP servers
26
26
  - Workflow nodes that orchestrate multi-step MCP interactions
27
27
  - Data processing nodes that consume MCP resources
28
28
 
@@ -53,38 +53,34 @@ class MCPClient(Node):
53
53
  - Logs connection events and errors for debugging
54
54
 
55
55
  Examples:
56
-
57
- Connect to an MCP server and list resources::
58
-
59
- client = MCPClient()
60
- result = client.run(
61
- server_config={
62
- "name": "filesystem-server",
63
- "command": "python",
64
- "args": ["-m", "mcp_filesystem"]
65
- },
66
- operation="list_resources"
67
- )
68
-
69
- Fetch a specific resource:
70
-
71
- resource = client.run(
72
- server_config=server_config,
73
- operation="read_resource",
74
- resource_uri="file:///path/to/document.txt"
75
- )
76
-
77
- Call a tool on the server:
78
-
79
- tool_result = client.run(
80
- server_config=server_config,
81
- operation="call_tool",
82
- tool_name="create_file",
83
- tool_arguments={
84
- "path": "/path/to/new_file.txt",
85
- "content": "Hello, World!"
86
- }
87
- )
56
+ >>> # Connect to an MCP server and list resources
57
+ >>> client = MCPClient()
58
+ >>> result = client.run(
59
+ ... server_config={
60
+ ... "name": "filesystem-server",
61
+ ... "command": "python",
62
+ ... "args": ["-m", "mcp_filesystem"]
63
+ ... },
64
+ ... operation="list_resources"
65
+ ... )
66
+
67
+ >>> # Fetch a specific resource
68
+ >>> resource = client.run(
69
+ ... server_config=server_config,
70
+ ... operation="read_resource",
71
+ ... resource_uri="file:///path/to/document.txt"
72
+ ... )
73
+
74
+ >>> # Call a tool on the server
75
+ >>> tool_result = client.run(
76
+ ... server_config=server_config,
77
+ ... operation="call_tool",
78
+ ... tool_name="create_file",
79
+ ... tool_arguments={
80
+ ... "path": "/path/to/new_file.txt",
81
+ ... "content": "Hello, World!"
82
+ ... }
83
+ ... )
88
84
  """
89
85
 
90
86
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -6,10 +6,17 @@ from kailash.nodes.transform.formatters import (
6
6
  ContextFormatterNode,
7
7
  QueryTextWrapperNode,
8
8
  )
9
- from kailash.nodes.transform.processors import DataTransformer, Filter, Map, Sort
9
+ from kailash.nodes.transform.processors import (
10
+ DataTransformer,
11
+ Filter,
12
+ FilterNode,
13
+ Map,
14
+ Sort,
15
+ )
10
16
 
11
17
  __all__ = [
12
18
  "Filter",
19
+ "FilterNode",
13
20
  "Map",
14
21
  "Sort",
15
22
  "DataTransformer",
@@ -90,7 +90,7 @@ Context:
90
90
 
91
91
  Please provide a comprehensive answer based on the information provided above."""
92
92
 
93
- # Create messages list for LLMAgent
93
+ # Create messages list for LLMAgentNode
94
94
  messages = [{"role": "user", "content": prompt}]
95
95
 
96
96
  return {"formatted_prompt": prompt, "messages": messages, "context": context}
@@ -7,8 +7,117 @@ from kailash.nodes.base import Node, NodeParameter, register_node
7
7
 
8
8
 
9
9
  @register_node()
10
- class Filter(Node):
11
- """Filters data based on a condition."""
10
+ class FilterNode(Node):
11
+ """
12
+ Filters data based on configurable conditions and operators.
13
+
14
+ This node provides flexible data filtering capabilities for lists and collections,
15
+ supporting various comparison operators and field-based filtering for structured
16
+ data. It's designed to work seamlessly in data processing pipelines, reducing
17
+ datasets to items that match specific criteria.
18
+
19
+ Design Philosophy:
20
+ The FilterNode embodies the principle of "declarative data selection." Rather
21
+ than writing custom filtering code, users declare their filtering criteria
22
+ through simple configuration. The design supports both simple value filtering
23
+ and complex field-based filtering for dictionaries, making it versatile for
24
+ various data structures.
25
+
26
+ Upstream Dependencies:
27
+ - Data source nodes providing lists to filter
28
+ - Transform nodes producing structured data
29
+ - Aggregation nodes generating collections
30
+ - API nodes returning result sets
31
+ - File readers loading datasets
32
+
33
+ Downstream Consumers:
34
+ - Processing nodes working with filtered subsets
35
+ - Aggregation nodes summarizing filtered data
36
+ - Writer nodes exporting filtered results
37
+ - Visualization nodes displaying subsets
38
+ - Decision nodes based on filter results
39
+
40
+ Configuration:
41
+ The node supports flexible filtering options:
42
+ - Field selection for dictionary filtering
43
+ - Multiple comparison operators
44
+ - Type-aware comparisons
45
+ - Null value handling
46
+ - String contains operations
47
+
48
+ Implementation Details:
49
+ - Handles lists of any type (dicts, primitives, objects)
50
+ - Type coercion for numeric comparisons
51
+ - Null-safe operations
52
+ - String conversion for contains operator
53
+ - Preserves original data structure
54
+ - Zero-copy filtering (returns references)
55
+
56
+ Error Handling:
57
+ - Graceful handling of type mismatches
58
+ - Null value comparison logic
59
+ - Empty data returns empty result
60
+ - Invalid field names return no matches
61
+ - Operator errors fail safely
62
+
63
+ Side Effects:
64
+ - No side effects (pure function)
65
+ - Does not modify input data
66
+ - Returns new filtered list
67
+
68
+ Examples:
69
+ >>> # Filter list of numbers
70
+ >>> filter_node = FilterNode()
71
+ >>> result = filter_node.run(
72
+ ... data=[1, 2, 3, 4, 5],
73
+ ... operator=">",
74
+ ... value=3
75
+ ... )
76
+ >>> assert result["filtered_data"] == [4, 5]
77
+ >>>
78
+ >>> # Filter list of dictionaries by field
79
+ >>> users = [
80
+ ... {"name": "Alice", "age": 30},
81
+ ... {"name": "Bob", "age": 25},
82
+ ... {"name": "Charlie", "age": 35}
83
+ ... ]
84
+ >>> result = filter_node.run(
85
+ ... data=users,
86
+ ... field="age",
87
+ ... operator=">=",
88
+ ... value=30
89
+ ... )
90
+ >>> assert len(result["filtered_data"]) == 2
91
+ >>> assert result["filtered_data"][0]["name"] == "Alice"
92
+ >>>
93
+ >>> # String contains filtering
94
+ >>> items = [
95
+ ... {"title": "Python Programming"},
96
+ ... {"title": "Java Development"},
97
+ ... {"title": "Python for Data Science"}
98
+ ... ]
99
+ >>> result = filter_node.run(
100
+ ... data=items,
101
+ ... field="title",
102
+ ... operator="contains",
103
+ ... value="Python"
104
+ ... )
105
+ >>> assert len(result["filtered_data"]) == 2
106
+ >>>
107
+ >>> # Null value handling
108
+ >>> data_with_nulls = [
109
+ ... {"value": 10},
110
+ ... {"value": None},
111
+ ... {"value": 20}
112
+ ... ]
113
+ >>> result = filter_node.run(
114
+ ... data=data_with_nulls,
115
+ ... field="value",
116
+ ... operator="!=",
117
+ ... value=None
118
+ ... )
119
+ >>> assert len(result["filtered_data"]) == 2
120
+ """
12
121
 
13
122
  def get_parameters(self) -> Dict[str, NodeParameter]:
14
123
  return {
@@ -67,8 +176,10 @@ class Filter(Node):
67
176
  try:
68
177
  # Handle None values - they fail most comparisons
69
178
  if item_value is None:
70
- if operator in ["==", "!="]:
71
- return (operator == "==") == (compare_value is None)
179
+ if operator == "==":
180
+ return compare_value is None
181
+ elif operator == "!=":
182
+ return compare_value is not None
72
183
  else:
73
184
  return False # None fails all other comparisons
74
185
 
@@ -379,3 +490,7 @@ class Sort(Node):
379
490
  sorted_data = sorted(data, reverse=reverse)
380
491
 
381
492
  return {"sorted_data": sorted_data}
493
+
494
+
495
+ # Backward compatibility aliases
496
+ Filter = FilterNode
@@ -88,13 +88,12 @@ class MetricsCollector:
88
88
  metrics during node execution, with support for both process-level and
89
89
  system-level monitoring.
90
90
 
91
- Usage::
92
-
93
- collector = MetricsCollector()
94
- with collector.collect() as metrics:
95
- # Execute node code here
96
- pass
97
- performance_data = metrics.result()
91
+ Usage:
92
+ >>> collector = MetricsCollector()
93
+ >>> with collector.collect() as metrics:
94
+ ... # Execute node code here
95
+ ... pass
96
+ >>> performance_data = metrics.result()
98
97
  """
99
98
 
100
99
  def __init__(self, sampling_interval: float = 0.1):
kailash/utils/export.py CHANGED
@@ -88,8 +88,8 @@ class NodeMapper:
88
88
  resources=ResourceSpec(cpu="100m", memory="256Mi"),
89
89
  )
90
90
 
91
- self.mappings["CSVReader"] = ContainerMapping(
92
- python_node="CSVReader",
91
+ self.mappings["CSVReaderNode"] = ContainerMapping(
92
+ python_node="CSVReaderNode",
93
93
  container_image="kailash/csv-reader:latest",
94
94
  command=["python", "-m", "kailash.nodes.data.csv_reader"],
95
95
  resources=ResourceSpec(cpu="100m", memory="512Mi"),