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.
- kailash/__init__.py +1 -1
- kailash/access_control.py +40 -39
- kailash/api/auth.py +26 -32
- kailash/api/custom_nodes.py +29 -29
- kailash/api/custom_nodes_secure.py +35 -35
- kailash/api/database.py +17 -17
- kailash/api/gateway.py +19 -19
- kailash/api/mcp_integration.py +24 -23
- kailash/api/studio.py +45 -45
- kailash/api/workflow_api.py +8 -8
- kailash/cli/commands.py +5 -8
- kailash/manifest.py +42 -42
- kailash/mcp/__init__.py +1 -1
- kailash/mcp/ai_registry_server.py +20 -20
- kailash/mcp/client.py +9 -11
- kailash/mcp/client_new.py +10 -10
- kailash/mcp/server.py +1 -2
- kailash/mcp/server_enhanced.py +449 -0
- kailash/mcp/servers/ai_registry.py +6 -6
- kailash/mcp/utils/__init__.py +31 -0
- kailash/mcp/utils/cache.py +267 -0
- kailash/mcp/utils/config.py +263 -0
- kailash/mcp/utils/formatters.py +293 -0
- kailash/mcp/utils/metrics.py +418 -0
- kailash/nodes/ai/agents.py +9 -9
- kailash/nodes/ai/ai_providers.py +33 -34
- kailash/nodes/ai/embedding_generator.py +31 -32
- kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
- kailash/nodes/ai/iterative_llm_agent.py +48 -48
- kailash/nodes/ai/llm_agent.py +32 -33
- kailash/nodes/ai/models.py +13 -13
- kailash/nodes/ai/self_organizing.py +44 -44
- kailash/nodes/api/__init__.py +5 -0
- kailash/nodes/api/auth.py +11 -11
- kailash/nodes/api/graphql.py +13 -13
- kailash/nodes/api/http.py +19 -19
- kailash/nodes/api/monitoring.py +463 -0
- kailash/nodes/api/rate_limiting.py +9 -13
- kailash/nodes/api/rest.py +29 -29
- kailash/nodes/api/security.py +819 -0
- kailash/nodes/base.py +24 -26
- kailash/nodes/base_async.py +7 -7
- kailash/nodes/base_cycle_aware.py +12 -12
- kailash/nodes/base_with_acl.py +5 -5
- kailash/nodes/code/python.py +56 -55
- kailash/nodes/data/__init__.py +6 -0
- kailash/nodes/data/directory.py +6 -6
- kailash/nodes/data/event_generation.py +297 -0
- kailash/nodes/data/file_discovery.py +598 -0
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/retrieval.py +10 -10
- kailash/nodes/data/sharepoint_graph.py +17 -17
- kailash/nodes/data/sources.py +5 -5
- kailash/nodes/data/sql.py +13 -13
- kailash/nodes/data/streaming.py +25 -25
- kailash/nodes/data/vector_db.py +22 -22
- kailash/nodes/data/writers.py +7 -7
- kailash/nodes/logic/async_operations.py +17 -17
- kailash/nodes/logic/convergence.py +11 -11
- kailash/nodes/logic/loop.py +4 -4
- kailash/nodes/logic/operations.py +11 -11
- kailash/nodes/logic/workflow.py +8 -9
- kailash/nodes/mixins/mcp.py +17 -17
- kailash/nodes/mixins.py +8 -10
- kailash/nodes/transform/chunkers.py +3 -3
- kailash/nodes/transform/formatters.py +7 -7
- kailash/nodes/transform/processors.py +11 -11
- kailash/runtime/access_controlled.py +18 -18
- kailash/runtime/async_local.py +18 -20
- kailash/runtime/docker.py +24 -26
- kailash/runtime/local.py +55 -31
- kailash/runtime/parallel.py +25 -25
- kailash/runtime/parallel_cyclic.py +29 -29
- kailash/runtime/runner.py +6 -6
- kailash/runtime/testing.py +22 -22
- kailash/sdk_exceptions.py +0 -58
- kailash/security.py +14 -26
- kailash/tracking/manager.py +38 -38
- kailash/tracking/metrics_collector.py +15 -14
- kailash/tracking/models.py +53 -53
- kailash/tracking/storage/base.py +7 -17
- kailash/tracking/storage/database.py +22 -23
- kailash/tracking/storage/filesystem.py +38 -40
- kailash/utils/export.py +21 -21
- kailash/utils/templates.py +8 -9
- kailash/visualization/api.py +30 -34
- kailash/visualization/dashboard.py +17 -17
- kailash/visualization/performance.py +32 -19
- kailash/visualization/reports.py +30 -28
- kailash/workflow/builder.py +8 -8
- kailash/workflow/convergence.py +13 -12
- kailash/workflow/cycle_analyzer.py +38 -33
- kailash/workflow/cycle_builder.py +12 -12
- kailash/workflow/cycle_config.py +16 -15
- kailash/workflow/cycle_debugger.py +40 -40
- kailash/workflow/cycle_exceptions.py +29 -29
- kailash/workflow/cycle_profiler.py +21 -21
- kailash/workflow/cycle_state.py +20 -22
- kailash/workflow/cyclic_runner.py +45 -45
- kailash/workflow/graph.py +57 -45
- kailash/workflow/mermaid_visualizer.py +9 -11
- kailash/workflow/migration.py +22 -22
- kailash/workflow/mock_registry.py +6 -6
- kailash/workflow/runner.py +9 -9
- kailash/workflow/safety.py +12 -13
- kailash/workflow/state.py +8 -11
- kailash/workflow/templates.py +19 -19
- kailash/workflow/validation.py +14 -14
- kailash/workflow/visualization.py +32 -24
- kailash-0.3.1.dist-info/METADATA +476 -0
- kailash-0.3.1.dist-info/RECORD +136 -0
- kailash-0.2.2.dist-info/METADATA +0 -121
- kailash-0.2.2.dist-info/RECORD +0 -126
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/WHEEL +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.2.2.dist-info → kailash-0.3.1.dist-info}/top_level.txt +0 -0
kailash/nodes/base.py
CHANGED
@@ -21,8 +21,8 @@ Key Components:
|
|
21
21
|
import json
|
22
22
|
import logging
|
23
23
|
from abc import ABC, abstractmethod
|
24
|
-
from datetime import
|
25
|
-
from typing import Any
|
24
|
+
from datetime import UTC, datetime
|
25
|
+
from typing import Any
|
26
26
|
|
27
27
|
from pydantic import BaseModel, Field, ValidationError
|
28
28
|
|
@@ -60,10 +60,10 @@ class NodeMetadata(BaseModel):
|
|
60
60
|
version: str = Field("1.0.0", description="Node version")
|
61
61
|
author: str = Field("", description="Node author")
|
62
62
|
created_at: datetime = Field(
|
63
|
-
default_factory=lambda: datetime.now(
|
63
|
+
default_factory=lambda: datetime.now(UTC),
|
64
64
|
description="Node creation date",
|
65
65
|
)
|
66
|
-
tags:
|
66
|
+
tags: set[str] = Field(default_factory=set, description="Node tags")
|
67
67
|
|
68
68
|
|
69
69
|
class NodeParameter(BaseModel):
|
@@ -94,7 +94,7 @@ class NodeParameter(BaseModel):
|
|
94
94
|
"""
|
95
95
|
|
96
96
|
name: str
|
97
|
-
type:
|
97
|
+
type: type
|
98
98
|
required: bool = True
|
99
99
|
default: Any = None
|
100
100
|
description: str = ""
|
@@ -208,7 +208,7 @@ class Node(ABC):
|
|
208
208
|
) from e
|
209
209
|
|
210
210
|
@abstractmethod
|
211
|
-
def get_parameters(self) ->
|
211
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
212
212
|
"""Define the parameters this node accepts.
|
213
213
|
|
214
214
|
This abstract method must be implemented by all concrete nodes to
|
@@ -254,9 +254,8 @@ class Node(ABC):
|
|
254
254
|
- to_dict(): Includes parameters in serialization
|
255
255
|
- Workflow.connect(): Validates compatible connections
|
256
256
|
"""
|
257
|
-
pass
|
258
257
|
|
259
|
-
def get_output_schema(self) ->
|
258
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
260
259
|
"""Define output parameters for this node.
|
261
260
|
|
262
261
|
This optional method allows nodes to specify their output schema for
|
@@ -313,7 +312,7 @@ class Node(ABC):
|
|
313
312
|
return {}
|
314
313
|
|
315
314
|
@abstractmethod
|
316
|
-
def run(self, **kwargs) ->
|
315
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
317
316
|
"""Execute the node's logic.
|
318
317
|
|
319
318
|
This is the core method that implements the node's data processing
|
@@ -361,7 +360,6 @@ class Node(ABC):
|
|
361
360
|
- LocalRuntime: During workflow execution
|
362
361
|
- TestRunner: During unit testing
|
363
362
|
"""
|
364
|
-
pass
|
365
363
|
|
366
364
|
def _validate_config(self):
|
367
365
|
"""Validate node configuration against defined parameters.
|
@@ -407,9 +405,9 @@ class Node(ABC):
|
|
407
405
|
for param_name, param_def in params.items():
|
408
406
|
if param_name not in self.config:
|
409
407
|
if param_def.required and param_def.default is None:
|
410
|
-
|
411
|
-
|
412
|
-
|
408
|
+
# During node construction, we may not have all parameters yet
|
409
|
+
# Skip validation for required parameters - they will be validated at execution time
|
410
|
+
continue
|
413
411
|
elif param_def.default is not None:
|
414
412
|
self.config[param_name] = param_def.default
|
415
413
|
|
@@ -428,8 +426,8 @@ class Node(ABC):
|
|
428
426
|
f"Conversion failed: {e}"
|
429
427
|
) from e
|
430
428
|
|
431
|
-
def validate_inputs(self, **kwargs) ->
|
432
|
-
"""Validate runtime inputs against node requirements.
|
429
|
+
def validate_inputs(self, **kwargs) -> dict[str, Any]:
|
430
|
+
r"""Validate runtime inputs against node requirements.
|
433
431
|
|
434
432
|
This method validates inputs provided at execution time against the
|
435
433
|
node's parameter schema. It ensures type safety and provides helpful
|
@@ -521,7 +519,7 @@ class Node(ABC):
|
|
521
519
|
|
522
520
|
return validated
|
523
521
|
|
524
|
-
def validate_outputs(self, outputs:
|
522
|
+
def validate_outputs(self, outputs: dict[str, Any]) -> dict[str, Any]:
|
525
523
|
"""Validate outputs against schema and JSON-serializability.
|
526
524
|
|
527
525
|
This enhanced method validates outputs in two ways:
|
@@ -661,7 +659,7 @@ class Node(ABC):
|
|
661
659
|
except (TypeError, ValueError):
|
662
660
|
return False
|
663
661
|
|
664
|
-
def execute(self, **runtime_inputs) ->
|
662
|
+
def execute(self, **runtime_inputs) -> dict[str, Any]:
|
665
663
|
"""Execute the node with validation and error handling.
|
666
664
|
|
667
665
|
This is the main entry point for node execution that orchestrates
|
@@ -711,7 +709,7 @@ class Node(ABC):
|
|
711
709
|
- Metrics enable performance monitoring
|
712
710
|
- Validation ensures data integrity
|
713
711
|
"""
|
714
|
-
start_time = datetime.now(
|
712
|
+
start_time = datetime.now(UTC)
|
715
713
|
try:
|
716
714
|
self.logger.info(f"Executing node {self.id}")
|
717
715
|
|
@@ -735,7 +733,7 @@ class Node(ABC):
|
|
735
733
|
# Validate outputs
|
736
734
|
validated_outputs = self.validate_outputs(outputs)
|
737
735
|
|
738
|
-
execution_time = (datetime.now(
|
736
|
+
execution_time = (datetime.now(UTC) - start_time).total_seconds()
|
739
737
|
self.logger.info(
|
740
738
|
f"Node {self.id} executed successfully in {execution_time:.3f}s"
|
741
739
|
)
|
@@ -754,7 +752,7 @@ class Node(ABC):
|
|
754
752
|
f"Node '{self.id}' execution failed: {type(e).__name__}: {e}"
|
755
753
|
) from e
|
756
754
|
|
757
|
-
def to_dict(self) ->
|
755
|
+
def to_dict(self) -> dict[str, Any]:
|
758
756
|
"""Convert node to dictionary representation.
|
759
757
|
|
760
758
|
Serializes the node instance to a dictionary format suitable for:
|
@@ -866,7 +864,7 @@ class NodeRegistry:
|
|
866
864
|
"""
|
867
865
|
|
868
866
|
_instance = None
|
869
|
-
_nodes:
|
867
|
+
_nodes: dict[str, type[Node]] = {}
|
870
868
|
|
871
869
|
def __new__(cls):
|
872
870
|
"""Ensure singleton instance.
|
@@ -882,7 +880,7 @@ class NodeRegistry:
|
|
882
880
|
return cls._instance
|
883
881
|
|
884
882
|
@classmethod
|
885
|
-
def register(cls, node_class:
|
883
|
+
def register(cls, node_class: type[Node], alias: str | None = None):
|
886
884
|
"""Register a node class.
|
887
885
|
|
888
886
|
Adds a node class to the global registry, making it available
|
@@ -940,7 +938,7 @@ class NodeRegistry:
|
|
940
938
|
logging.info(f"Registered node '{node_name}'")
|
941
939
|
|
942
940
|
@classmethod
|
943
|
-
def get(cls, node_name: str) ->
|
941
|
+
def get(cls, node_name: str) -> type[Node]:
|
944
942
|
"""Get a registered node class by name.
|
945
943
|
|
946
944
|
Retrieves a node class from the registry for instantiation.
|
@@ -983,7 +981,7 @@ class NodeRegistry:
|
|
983
981
|
return cls._nodes[node_name]
|
984
982
|
|
985
983
|
@classmethod
|
986
|
-
def list_nodes(cls) ->
|
984
|
+
def list_nodes(cls) -> dict[str, type[Node]]:
|
987
985
|
"""List all registered nodes.
|
988
986
|
|
989
987
|
Returns a copy of the registry for discovery purposes.
|
@@ -1030,7 +1028,7 @@ class NodeRegistry:
|
|
1030
1028
|
logging.info("Cleared all registered nodes")
|
1031
1029
|
|
1032
1030
|
|
1033
|
-
def register_node(alias:
|
1031
|
+
def register_node(alias: str | None = None):
|
1034
1032
|
"""Decorator to register a node class.
|
1035
1033
|
|
1036
1034
|
Provides a convenient decorator pattern for automatic node
|
@@ -1077,7 +1075,7 @@ def register_node(alias: Optional[str] = None):
|
|
1077
1075
|
... return pd.read_csv(file)
|
1078
1076
|
"""
|
1079
1077
|
|
1080
|
-
def decorator(node_class:
|
1078
|
+
def decorator(node_class: type[Node]):
|
1081
1079
|
"""Inner decorator that performs registration.
|
1082
1080
|
|
1083
1081
|
Args:
|
kailash/nodes/base_async.py
CHANGED
@@ -4,8 +4,8 @@ This module extends the base Node class with asynchronous execution capabilities
|
|
4
4
|
allowing for more efficient handling of I/O-bound operations in workflows.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from datetime import
|
8
|
-
from typing import Any
|
7
|
+
from datetime import UTC, datetime
|
8
|
+
from typing import Any
|
9
9
|
|
10
10
|
from kailash.nodes.base import Node
|
11
11
|
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
@@ -45,7 +45,7 @@ class AsyncNode(Node):
|
|
45
45
|
- TaskManager: Tracks node execution status
|
46
46
|
"""
|
47
47
|
|
48
|
-
def execute(self, **runtime_inputs) ->
|
48
|
+
def execute(self, **runtime_inputs) -> dict[str, Any]:
|
49
49
|
"""Execute the node synchronously by running async code in a new event loop.
|
50
50
|
|
51
51
|
This override allows AsyncNode to work with synchronous runtimes like LocalRuntime.
|
@@ -90,7 +90,7 @@ class AsyncNode(Node):
|
|
90
90
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
91
91
|
return asyncio.run(self.execute_async(**runtime_inputs))
|
92
92
|
|
93
|
-
async def async_run(self, **kwargs) ->
|
93
|
+
async def async_run(self, **kwargs) -> dict[str, Any]:
|
94
94
|
"""Asynchronous execution method for the node.
|
95
95
|
|
96
96
|
This method should be overridden by subclasses that require asynchronous
|
@@ -108,7 +108,7 @@ class AsyncNode(Node):
|
|
108
108
|
# Default implementation calls the synchronous run() method
|
109
109
|
return self.run(**kwargs)
|
110
110
|
|
111
|
-
async def execute_async(self, **runtime_inputs) ->
|
111
|
+
async def execute_async(self, **runtime_inputs) -> dict[str, Any]:
|
112
112
|
"""Execute the node asynchronously with validation and error handling.
|
113
113
|
|
114
114
|
This method follows the same pattern as execute() but supports asynchronous
|
@@ -129,7 +129,7 @@ class AsyncNode(Node):
|
|
129
129
|
NodeValidationError: If inputs or outputs are invalid
|
130
130
|
NodeExecutionError: If execution fails
|
131
131
|
"""
|
132
|
-
start_time = datetime.now(
|
132
|
+
start_time = datetime.now(UTC)
|
133
133
|
try:
|
134
134
|
self.logger.info(f"Executing node {self.id} asynchronously")
|
135
135
|
|
@@ -153,7 +153,7 @@ class AsyncNode(Node):
|
|
153
153
|
# Validate outputs
|
154
154
|
validated_outputs = self.validate_outputs(outputs)
|
155
155
|
|
156
|
-
execution_time = (datetime.now(
|
156
|
+
execution_time = (datetime.now(UTC) - start_time).total_seconds()
|
157
157
|
self.logger.info(
|
158
158
|
f"Node {self.id} executed successfully in {execution_time:.3f}s"
|
159
159
|
)
|
@@ -37,7 +37,7 @@ Example usage:
|
|
37
37
|
"""
|
38
38
|
|
39
39
|
import time
|
40
|
-
from typing import Any
|
40
|
+
from typing import Any
|
41
41
|
|
42
42
|
from .base import Node
|
43
43
|
|
@@ -103,7 +103,7 @@ class CycleAwareNode(Node):
|
|
103
103
|
... }
|
104
104
|
"""
|
105
105
|
|
106
|
-
def get_cycle_info(self, context:
|
106
|
+
def get_cycle_info(self, context: dict[str, Any]) -> dict[str, Any]:
|
107
107
|
"""
|
108
108
|
Get cycle information with sensible defaults.
|
109
109
|
|
@@ -138,7 +138,7 @@ class CycleAwareNode(Node):
|
|
138
138
|
**cycle_info, # Include any additional cycle information
|
139
139
|
}
|
140
140
|
|
141
|
-
def get_iteration(self, context:
|
141
|
+
def get_iteration(self, context: dict[str, Any]) -> int:
|
142
142
|
"""
|
143
143
|
Get current iteration number.
|
144
144
|
|
@@ -155,7 +155,7 @@ class CycleAwareNode(Node):
|
|
155
155
|
"""
|
156
156
|
return self.get_cycle_info(context)["iteration"]
|
157
157
|
|
158
|
-
def is_first_iteration(self, context:
|
158
|
+
def is_first_iteration(self, context: dict[str, Any]) -> bool:
|
159
159
|
"""
|
160
160
|
Check if this is the first iteration of the cycle.
|
161
161
|
|
@@ -172,7 +172,7 @@ class CycleAwareNode(Node):
|
|
172
172
|
"""
|
173
173
|
return self.get_iteration(context) == 0
|
174
174
|
|
175
|
-
def is_last_iteration(self, context:
|
175
|
+
def is_last_iteration(self, context: dict[str, Any]) -> bool:
|
176
176
|
"""
|
177
177
|
Check if this is the last iteration of the cycle.
|
178
178
|
|
@@ -189,7 +189,7 @@ class CycleAwareNode(Node):
|
|
189
189
|
cycle_info = self.get_cycle_info(context)
|
190
190
|
return cycle_info["iteration"] >= cycle_info["max_iterations"] - 1
|
191
191
|
|
192
|
-
def get_previous_state(self, context:
|
192
|
+
def get_previous_state(self, context: dict[str, Any]) -> dict[str, Any]:
|
193
193
|
"""
|
194
194
|
Get previous iteration state safely.
|
195
195
|
|
@@ -209,7 +209,7 @@ class CycleAwareNode(Node):
|
|
209
209
|
"""
|
210
210
|
return self.get_cycle_info(context).get("node_state", {})
|
211
211
|
|
212
|
-
def set_cycle_state(self, state:
|
212
|
+
def set_cycle_state(self, state: dict[str, Any]) -> dict[str, Any]:
|
213
213
|
"""
|
214
214
|
Set state to persist to next iteration.
|
215
215
|
|
@@ -233,7 +233,7 @@ class CycleAwareNode(Node):
|
|
233
233
|
"""
|
234
234
|
return {"_cycle_state": state}
|
235
235
|
|
236
|
-
def get_cycle_progress(self, context:
|
236
|
+
def get_cycle_progress(self, context: dict[str, Any]) -> float:
|
237
237
|
"""
|
238
238
|
Get cycle progress as a percentage.
|
239
239
|
|
@@ -256,7 +256,7 @@ class CycleAwareNode(Node):
|
|
256
256
|
|
257
257
|
return min(iteration / max_iterations, 1.0)
|
258
258
|
|
259
|
-
def log_cycle_info(self, context:
|
259
|
+
def log_cycle_info(self, context: dict[str, Any], message: str = "") -> None:
|
260
260
|
"""
|
261
261
|
Log cycle information for debugging.
|
262
262
|
|
@@ -284,7 +284,7 @@ class CycleAwareNode(Node):
|
|
284
284
|
|
285
285
|
print(log_msg)
|
286
286
|
|
287
|
-
def should_continue_cycle(self, context:
|
287
|
+
def should_continue_cycle(self, context: dict[str, Any], **kwargs) -> bool:
|
288
288
|
"""
|
289
289
|
Helper method to determine if cycle should continue.
|
290
290
|
|
@@ -307,7 +307,7 @@ class CycleAwareNode(Node):
|
|
307
307
|
return not self.is_last_iteration(context)
|
308
308
|
|
309
309
|
def accumulate_values(
|
310
|
-
self, context:
|
310
|
+
self, context: dict[str, Any], key: str, value: Any, max_history: int = 100
|
311
311
|
) -> list:
|
312
312
|
"""
|
313
313
|
Accumulate values across iterations with automatic history management.
|
@@ -340,7 +340,7 @@ class CycleAwareNode(Node):
|
|
340
340
|
|
341
341
|
def detect_convergence_trend(
|
342
342
|
self,
|
343
|
-
context:
|
343
|
+
context: dict[str, Any],
|
344
344
|
key: str,
|
345
345
|
threshold: float = 0.01,
|
346
346
|
window: int = 3,
|
kailash/nodes/base_with_acl.py
CHANGED
@@ -14,7 +14,7 @@ Key Design Principles:
|
|
14
14
|
"""
|
15
15
|
|
16
16
|
import logging
|
17
|
-
from typing import Any
|
17
|
+
from typing import Any
|
18
18
|
|
19
19
|
from kailash.access_control import (
|
20
20
|
AccessDecision,
|
@@ -146,7 +146,7 @@ class NodeWithAccessControl(Node):
|
|
146
146
|
else:
|
147
147
|
raise NotImplementedError("Node must implement _execute() method")
|
148
148
|
|
149
|
-
def _should_check_access(self, user_context:
|
149
|
+
def _should_check_access(self, user_context: UserContext | None) -> bool:
|
150
150
|
"""
|
151
151
|
Determine if access control should be checked.
|
152
152
|
|
@@ -179,7 +179,7 @@ class NodeWithAccessControl(Node):
|
|
179
179
|
# Fall back to class name
|
180
180
|
return self.__class__.__name__
|
181
181
|
|
182
|
-
def _mask_fields(self, data:
|
182
|
+
def _mask_fields(self, data: dict[str, Any], fields: list[str]) -> dict[str, Any]:
|
183
183
|
"""Mask specified fields in output data"""
|
184
184
|
masked_data = data.copy()
|
185
185
|
for field in fields:
|
@@ -188,7 +188,7 @@ class NodeWithAccessControl(Node):
|
|
188
188
|
return masked_data
|
189
189
|
|
190
190
|
def _handle_access_denied(
|
191
|
-
self, decision: AccessDecision, inputs:
|
191
|
+
self, decision: AccessDecision, inputs: dict[str, Any]
|
192
192
|
) -> Any:
|
193
193
|
"""
|
194
194
|
Handle access denied scenarios.
|
@@ -333,6 +333,6 @@ def add_access_control(node_instance, **acl_config):
|
|
333
333
|
setattr(node_instance, key, value)
|
334
334
|
|
335
335
|
# Mark this node as access-controlled
|
336
|
-
|
336
|
+
node_instance._access_controlled = True
|
337
337
|
|
338
338
|
return node_instance
|
kailash/nodes/code/python.py
CHANGED
@@ -52,8 +52,9 @@ import inspect
|
|
52
52
|
import logging
|
53
53
|
import resource
|
54
54
|
import traceback
|
55
|
+
from collections.abc import Callable
|
55
56
|
from pathlib import Path
|
56
|
-
from typing import Any,
|
57
|
+
from typing import Any, get_type_hints
|
57
58
|
|
58
59
|
from kailash.nodes.base import Node, NodeMetadata, NodeParameter, register_node
|
59
60
|
from kailash.sdk_exceptions import (
|
@@ -171,8 +172,8 @@ class CodeExecutor:
|
|
171
172
|
|
172
173
|
def __init__(
|
173
174
|
self,
|
174
|
-
allowed_modules:
|
175
|
-
security_config:
|
175
|
+
allowed_modules: list[str] | None = None,
|
176
|
+
security_config: SecurityConfig | None = None,
|
176
177
|
):
|
177
178
|
"""Initialize the code executor.
|
178
179
|
|
@@ -269,7 +270,7 @@ class CodeExecutor:
|
|
269
270
|
except SyntaxError as e:
|
270
271
|
raise NodeExecutionError(f"Invalid Python syntax: {e}")
|
271
272
|
|
272
|
-
def execute_code(self, code: str, inputs:
|
273
|
+
def execute_code(self, code: str, inputs: dict[str, Any]) -> dict[str, Any]:
|
273
274
|
"""Execute Python code with given inputs.
|
274
275
|
|
275
276
|
Args:
|
@@ -350,7 +351,7 @@ class CodeExecutor:
|
|
350
351
|
logger.error(error_msg)
|
351
352
|
raise NodeExecutionError(error_msg)
|
352
353
|
|
353
|
-
def execute_function(self, func: Callable, inputs:
|
354
|
+
def execute_function(self, func: Callable, inputs: dict[str, Any]) -> Any:
|
354
355
|
"""Execute a Python function with given inputs.
|
355
356
|
|
356
357
|
Args:
|
@@ -404,7 +405,7 @@ class FunctionWrapper:
|
|
404
405
|
node = wrapper.to_node(name="dropna_processor")
|
405
406
|
"""
|
406
407
|
|
407
|
-
def __init__(self, func: Callable, executor:
|
408
|
+
def __init__(self, func: Callable, executor: CodeExecutor | None = None):
|
408
409
|
"""Initialize the function wrapper.
|
409
410
|
|
410
411
|
Args:
|
@@ -422,7 +423,7 @@ class FunctionWrapper:
|
|
422
423
|
# Handle cases where type hints can't be resolved
|
423
424
|
self.type_hints = {}
|
424
425
|
|
425
|
-
def get_input_types(self) ->
|
426
|
+
def get_input_types(self) -> dict[str, type]:
|
426
427
|
"""Extract input types from function signature.
|
427
428
|
|
428
429
|
Returns:
|
@@ -438,7 +439,7 @@ class FunctionWrapper:
|
|
438
439
|
input_types[param_name] = param_type
|
439
440
|
return input_types
|
440
441
|
|
441
|
-
def get_output_type(self) ->
|
442
|
+
def get_output_type(self) -> type:
|
442
443
|
"""Extract output type from function signature.
|
443
444
|
|
444
445
|
Returns:
|
@@ -446,7 +447,7 @@ class FunctionWrapper:
|
|
446
447
|
"""
|
447
448
|
return self.type_hints.get("return", Any)
|
448
449
|
|
449
|
-
def execute(self, inputs:
|
450
|
+
def execute(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
450
451
|
"""Execute the wrapped function."""
|
451
452
|
result = self.executor.execute_function(self.func, inputs)
|
452
453
|
|
@@ -458,10 +459,10 @@ class FunctionWrapper:
|
|
458
459
|
|
459
460
|
def to_node(
|
460
461
|
self,
|
461
|
-
name:
|
462
|
-
description:
|
463
|
-
input_schema:
|
464
|
-
output_schema:
|
462
|
+
name: str | None = None,
|
463
|
+
description: str | None = None,
|
464
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
465
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
465
466
|
) -> "PythonCodeNode":
|
466
467
|
"""Convert function to a PythonCodeNode.
|
467
468
|
|
@@ -507,9 +508,9 @@ class ClassWrapper:
|
|
507
508
|
|
508
509
|
def __init__(
|
509
510
|
self,
|
510
|
-
cls:
|
511
|
-
method_name:
|
512
|
-
executor:
|
511
|
+
cls: type,
|
512
|
+
method_name: str | None = None,
|
513
|
+
executor: CodeExecutor | None = None,
|
513
514
|
):
|
514
515
|
"""Initialize the class wrapper.
|
515
516
|
|
@@ -570,7 +571,7 @@ class ClassWrapper:
|
|
570
571
|
# Handle descriptor objects like properties
|
571
572
|
self.type_hints = {}
|
572
573
|
|
573
|
-
def get_input_types(self) ->
|
574
|
+
def get_input_types(self) -> dict[str, type]:
|
574
575
|
"""Extract input types from method signature."""
|
575
576
|
input_types = {}
|
576
577
|
for param_name, param in self.signature.parameters.items():
|
@@ -582,11 +583,11 @@ class ClassWrapper:
|
|
582
583
|
input_types[param_name] = param_type
|
583
584
|
return input_types
|
584
585
|
|
585
|
-
def get_output_type(self) ->
|
586
|
+
def get_output_type(self) -> type:
|
586
587
|
"""Extract output type from method signature."""
|
587
588
|
return self.type_hints.get("return", Any)
|
588
589
|
|
589
|
-
def execute(self, inputs:
|
590
|
+
def execute(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
590
591
|
"""Execute the wrapped method."""
|
591
592
|
# Create instance if needed
|
592
593
|
if self.instance is None:
|
@@ -611,10 +612,10 @@ class ClassWrapper:
|
|
611
612
|
|
612
613
|
def to_node(
|
613
614
|
self,
|
614
|
-
name:
|
615
|
-
description:
|
616
|
-
input_schema:
|
617
|
-
output_schema:
|
615
|
+
name: str | None = None,
|
616
|
+
description: str | None = None,
|
617
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
618
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
618
619
|
) -> "PythonCodeNode":
|
619
620
|
"""Convert class to a PythonCodeNode.
|
620
621
|
|
@@ -707,15 +708,15 @@ class PythonCodeNode(Node):
|
|
707
708
|
def __init__(
|
708
709
|
self,
|
709
710
|
name: str,
|
710
|
-
code:
|
711
|
-
function:
|
712
|
-
class_type:
|
713
|
-
process_method:
|
714
|
-
input_types:
|
715
|
-
output_type:
|
716
|
-
input_schema:
|
717
|
-
output_schema:
|
718
|
-
description:
|
711
|
+
code: str | None = None,
|
712
|
+
function: Callable | None = None,
|
713
|
+
class_type: type | None = None,
|
714
|
+
process_method: str | None = None,
|
715
|
+
input_types: dict[str, type] | None = None,
|
716
|
+
output_type: type | None = None,
|
717
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
718
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
719
|
+
description: str | None = None,
|
719
720
|
**kwargs,
|
720
721
|
):
|
721
722
|
"""Initialize a Python code node.
|
@@ -784,7 +785,7 @@ class PythonCodeNode(Node):
|
|
784
785
|
if not hasattr(self, "_skip_validation"):
|
785
786
|
self._skip_validation = True
|
786
787
|
|
787
|
-
def get_parameters(self) ->
|
788
|
+
def get_parameters(self) -> dict[str, "NodeParameter"]:
|
788
789
|
"""Define the parameters this node accepts.
|
789
790
|
|
790
791
|
Returns:
|
@@ -841,7 +842,7 @@ class PythonCodeNode(Node):
|
|
841
842
|
|
842
843
|
return parameters
|
843
844
|
|
844
|
-
def validate_inputs(self, **kwargs) ->
|
845
|
+
def validate_inputs(self, **kwargs) -> dict[str, Any]:
|
845
846
|
"""Validate runtime inputs.
|
846
847
|
|
847
848
|
For code-based nodes, we accept any inputs since the code
|
@@ -860,7 +861,7 @@ class PythonCodeNode(Node):
|
|
860
861
|
# Otherwise use standard validation for function/class nodes
|
861
862
|
return super().validate_inputs(**kwargs)
|
862
863
|
|
863
|
-
def get_output_schema(self) ->
|
864
|
+
def get_output_schema(self) -> dict[str, "NodeParameter"]:
|
864
865
|
"""Define output parameters for this node.
|
865
866
|
|
866
867
|
Returns:
|
@@ -880,7 +881,7 @@ class PythonCodeNode(Node):
|
|
880
881
|
)
|
881
882
|
}
|
882
883
|
|
883
|
-
def run(self, **kwargs) ->
|
884
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
884
885
|
"""Execute the node's logic.
|
885
886
|
|
886
887
|
Args:
|
@@ -923,10 +924,10 @@ class PythonCodeNode(Node):
|
|
923
924
|
def from_function(
|
924
925
|
cls,
|
925
926
|
func: Callable,
|
926
|
-
name:
|
927
|
-
description:
|
928
|
-
input_schema:
|
929
|
-
output_schema:
|
927
|
+
name: str | None = None,
|
928
|
+
description: str | None = None,
|
929
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
930
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
930
931
|
**kwargs,
|
931
932
|
) -> "PythonCodeNode":
|
932
933
|
"""Create a node from a Python function.
|
@@ -961,12 +962,12 @@ class PythonCodeNode(Node):
|
|
961
962
|
@classmethod
|
962
963
|
def from_class(
|
963
964
|
cls,
|
964
|
-
class_type:
|
965
|
-
process_method:
|
966
|
-
name:
|
967
|
-
description:
|
968
|
-
input_schema:
|
969
|
-
output_schema:
|
965
|
+
class_type: type,
|
966
|
+
process_method: str | None = None,
|
967
|
+
name: str | None = None,
|
968
|
+
description: str | None = None,
|
969
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
970
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
970
971
|
**kwargs,
|
971
972
|
) -> "PythonCodeNode":
|
972
973
|
"""Create a node from a Python class.
|
@@ -1003,13 +1004,13 @@ class PythonCodeNode(Node):
|
|
1003
1004
|
@classmethod
|
1004
1005
|
def from_file(
|
1005
1006
|
cls,
|
1006
|
-
file_path:
|
1007
|
-
function_name:
|
1008
|
-
class_name:
|
1009
|
-
name:
|
1010
|
-
description:
|
1011
|
-
input_schema:
|
1012
|
-
output_schema:
|
1007
|
+
file_path: str | Path,
|
1008
|
+
function_name: str | None = None,
|
1009
|
+
class_name: str | None = None,
|
1010
|
+
name: str | None = None,
|
1011
|
+
description: str | None = None,
|
1012
|
+
input_schema: dict[str, "NodeParameter"] | None = None,
|
1013
|
+
output_schema: dict[str, "NodeParameter"] | None = None,
|
1013
1014
|
) -> "PythonCodeNode":
|
1014
1015
|
"""Create a node from a Python file.
|
1015
1016
|
|
@@ -1084,7 +1085,7 @@ class PythonCodeNode(Node):
|
|
1084
1085
|
f"No suitable function or class found in {file_path}"
|
1085
1086
|
)
|
1086
1087
|
|
1087
|
-
def execute_code(self, inputs:
|
1088
|
+
def execute_code(self, inputs: dict[str, Any]) -> Any:
|
1088
1089
|
"""Execute the code with given inputs.
|
1089
1090
|
|
1090
1091
|
This is a convenience method that directly executes the code
|
@@ -1115,7 +1116,7 @@ class PythonCodeNode(Node):
|
|
1115
1116
|
else:
|
1116
1117
|
raise NodeExecutionError("No execution method available")
|
1117
1118
|
|
1118
|
-
def get_config(self) ->
|
1119
|
+
def get_config(self) -> dict[str, Any]:
|
1119
1120
|
"""Get node configuration for serialization.
|
1120
1121
|
|
1121
1122
|
Returns:
|
kailash/nodes/data/__init__.py
CHANGED
@@ -81,6 +81,8 @@ Example Workflows:
|
|
81
81
|
"""
|
82
82
|
|
83
83
|
from kailash.nodes.data.directory import DirectoryReaderNode
|
84
|
+
from kailash.nodes.data.event_generation import EventGeneratorNode
|
85
|
+
from kailash.nodes.data.file_discovery import FileDiscoveryNode
|
84
86
|
from kailash.nodes.data.readers import CSVReaderNode, JSONReaderNode, TextReaderNode
|
85
87
|
from kailash.nodes.data.retrieval import RelevanceScorerNode
|
86
88
|
from kailash.nodes.data.sharepoint_graph import (
|
@@ -105,6 +107,10 @@ from kailash.nodes.data.writers import CSVWriterNode, JSONWriterNode, TextWriter
|
|
105
107
|
__all__ = [
|
106
108
|
# Directory
|
107
109
|
"DirectoryReaderNode",
|
110
|
+
# Event Generation
|
111
|
+
"EventGeneratorNode",
|
112
|
+
# File Discovery
|
113
|
+
"FileDiscoveryNode",
|
108
114
|
# Readers
|
109
115
|
"CSVReaderNode",
|
110
116
|
"JSONReaderNode",
|