kailash 0.3.2__py3-none-any.whl → 0.4.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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +25 -3
- kailash/nodes/admin/__init__.py +35 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1519 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +1 -0
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +407 -2
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +283 -10
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +91 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +132 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
- kailash-0.4.0.dist-info/RECORD +223 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,321 @@
|
|
1
|
+
"""Node validation framework with context-aware error suggestions.
|
2
|
+
|
3
|
+
This module provides validation utilities that enhance error messages with
|
4
|
+
helpful suggestions, code examples, and documentation links.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import inspect
|
8
|
+
import re
|
9
|
+
from dataclasses import dataclass
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Type
|
11
|
+
|
12
|
+
from kailash.nodes.base import Node, NodeParameter
|
13
|
+
|
14
|
+
|
15
|
+
@dataclass
|
16
|
+
class ValidationSuggestion:
|
17
|
+
"""Suggestion for fixing a validation error."""
|
18
|
+
|
19
|
+
message: str
|
20
|
+
code_example: Optional[str] = None
|
21
|
+
doc_link: Optional[str] = None
|
22
|
+
alternative_nodes: Optional[List[str]] = None
|
23
|
+
|
24
|
+
|
25
|
+
class NodeValidator:
|
26
|
+
"""Enhanced node validation with helpful error messages."""
|
27
|
+
|
28
|
+
# Common parameter mistakes and their fixes
|
29
|
+
PARAMETER_PATTERNS = {
|
30
|
+
# PythonCodeNode common mistakes
|
31
|
+
r"return\s+(?!.*\{.*result.*\})": ValidationSuggestion(
|
32
|
+
message="PythonCodeNode must return data wrapped in {'result': ...}",
|
33
|
+
code_example='return {"result": your_data} # Not: return your_data',
|
34
|
+
doc_link="sdk-users/developer/07-troubleshooting.md#pythoncodenode-output",
|
35
|
+
),
|
36
|
+
# File path mistakes
|
37
|
+
r"^(?!/).*\.(csv|json|txt)$": ValidationSuggestion(
|
38
|
+
message="File paths should be absolute, not relative",
|
39
|
+
code_example='file_path="/data/inputs/file.csv" # Not: file_path="file.csv"',
|
40
|
+
doc_link="sdk-users/developer/QUICK_REFERENCE.md#file-paths",
|
41
|
+
),
|
42
|
+
# Node naming mistakes
|
43
|
+
r"Node$": ValidationSuggestion(
|
44
|
+
message="Node names should describe their purpose, not end with 'Node'",
|
45
|
+
code_example='workflow.add_node("read_data", CSVReaderNode) # Not: "csv_reader_node"',
|
46
|
+
alternative_nodes=[
|
47
|
+
"Consider using descriptive names like 'load_config', 'process_data', 'save_results'"
|
48
|
+
],
|
49
|
+
),
|
50
|
+
# SQL injection risks
|
51
|
+
r"f['\"].*SELECT.*\{": ValidationSuggestion(
|
52
|
+
message="Avoid f-strings in SQL queries - use parameterized queries",
|
53
|
+
code_example='query="SELECT * FROM users WHERE id = %s", params=[user_id]',
|
54
|
+
doc_link="sdk-users/security/sql-best-practices.md",
|
55
|
+
),
|
56
|
+
# Missing required fields
|
57
|
+
r"TypeError.*missing.*required": ValidationSuggestion(
|
58
|
+
message="Required parameter missing",
|
59
|
+
code_example="Check node documentation for required parameters",
|
60
|
+
doc_link="sdk-users/nodes/comprehensive-node-catalog.md",
|
61
|
+
),
|
62
|
+
}
|
63
|
+
|
64
|
+
# Node-specific validations
|
65
|
+
NODE_VALIDATIONS: Dict[str, List[Callable]] = {
|
66
|
+
"PythonCodeNode": [
|
67
|
+
lambda config: "code" in config or "func" in config,
|
68
|
+
lambda config: not (
|
69
|
+
config.get("code", "").strip().startswith("import ")
|
70
|
+
and len(config.get("code", "").split("\n")) == 1
|
71
|
+
),
|
72
|
+
],
|
73
|
+
"SQLDatabaseNode": [
|
74
|
+
lambda config: "query" in config or "queries" in config,
|
75
|
+
lambda config: not re.search(
|
76
|
+
r"DROP|DELETE|TRUNCATE", config.get("query", ""), re.I
|
77
|
+
)
|
78
|
+
or config.get("allow_destructive", False),
|
79
|
+
],
|
80
|
+
"HTTPRequestNode": [
|
81
|
+
lambda config: "url" in config,
|
82
|
+
lambda config: config.get("url", "").startswith(("http://", "https://")),
|
83
|
+
],
|
84
|
+
}
|
85
|
+
|
86
|
+
# Alternative node suggestions
|
87
|
+
ALTERNATIVE_NODES = {
|
88
|
+
"csv_processing": ["CSVReaderNode", "PandasNode", "DataTransformerNode"],
|
89
|
+
"api_calls": ["HTTPRequestNode", "RESTClientNode", "GraphQLClientNode"],
|
90
|
+
"data_storage": ["SQLDatabaseNode", "VectorDatabaseNode", "JSONWriterNode"],
|
91
|
+
"llm_tasks": ["LLMAgentNode", "A2AAgentNode", "MCPAgentNode"],
|
92
|
+
"authentication": ["OAuth2Node", "CredentialManagerNode", "BasicAuthNode"],
|
93
|
+
}
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def validate_node_config(
|
97
|
+
cls,
|
98
|
+
node_type: str,
|
99
|
+
config: Dict[str, Any],
|
100
|
+
node_instance: Optional[Node] = None,
|
101
|
+
) -> List[ValidationSuggestion]:
|
102
|
+
"""Validate node configuration and return suggestions."""
|
103
|
+
suggestions = []
|
104
|
+
|
105
|
+
# Check node-specific validations
|
106
|
+
if node_type in cls.NODE_VALIDATIONS:
|
107
|
+
for validation in cls.NODE_VALIDATIONS[node_type]:
|
108
|
+
try:
|
109
|
+
if not validation(config):
|
110
|
+
suggestions.append(cls._get_node_suggestion(node_type, config))
|
111
|
+
except Exception:
|
112
|
+
pass
|
113
|
+
|
114
|
+
# Check parameter patterns
|
115
|
+
config_str = str(config)
|
116
|
+
for pattern, suggestion in cls.PARAMETER_PATTERNS.items():
|
117
|
+
if re.search(pattern, config_str):
|
118
|
+
suggestions.append(suggestion)
|
119
|
+
|
120
|
+
# Check for common type mismatches
|
121
|
+
if node_instance:
|
122
|
+
suggestions.extend(cls._validate_parameter_types(node_instance, config))
|
123
|
+
|
124
|
+
return suggestions
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def _validate_parameter_types(
|
128
|
+
cls, node: Node, config: Dict[str, Any]
|
129
|
+
) -> List[ValidationSuggestion]:
|
130
|
+
"""Validate parameter types match expected types."""
|
131
|
+
suggestions = []
|
132
|
+
|
133
|
+
try:
|
134
|
+
params = node.get_parameters()
|
135
|
+
for param_name, param_def in params.items():
|
136
|
+
if param_name in config:
|
137
|
+
value = config[param_name]
|
138
|
+
expected_type = param_def.type
|
139
|
+
|
140
|
+
# Type checking
|
141
|
+
if expected_type and not cls._check_type(value, expected_type):
|
142
|
+
suggestions.append(
|
143
|
+
ValidationSuggestion(
|
144
|
+
message=f"Parameter '{param_name}' expects {expected_type.__name__}, got {type(value).__name__}",
|
145
|
+
code_example=f"{param_name}={cls._get_type_example(expected_type)}",
|
146
|
+
doc_link=f"sdk-users/nodes/{node.__class__.__name__.lower()}.md",
|
147
|
+
)
|
148
|
+
)
|
149
|
+
except Exception:
|
150
|
+
pass
|
151
|
+
|
152
|
+
return suggestions
|
153
|
+
|
154
|
+
@classmethod
|
155
|
+
def _check_type(cls, value: Any, expected_type: Type) -> bool:
|
156
|
+
"""Check if value matches expected type."""
|
157
|
+
# Handle Optional types
|
158
|
+
if hasattr(expected_type, "__origin__"):
|
159
|
+
if expected_type.__origin__ is type(Optional):
|
160
|
+
if value is None:
|
161
|
+
return True
|
162
|
+
expected_type = expected_type.__args__[0]
|
163
|
+
|
164
|
+
# Direct type check
|
165
|
+
return isinstance(value, expected_type)
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
def _get_type_example(cls, type_hint: Type) -> str:
|
169
|
+
"""Get example value for a type."""
|
170
|
+
examples = {
|
171
|
+
str: '"example_string"',
|
172
|
+
int: "42",
|
173
|
+
float: "3.14",
|
174
|
+
bool: "True",
|
175
|
+
list: '["item1", "item2"]',
|
176
|
+
dict: '{"key": "value"}',
|
177
|
+
}
|
178
|
+
return examples.get(type_hint, f"<{type_hint.__name__} value>")
|
179
|
+
|
180
|
+
@classmethod
|
181
|
+
def _get_node_suggestion(
|
182
|
+
cls, node_type: str, config: Dict[str, Any]
|
183
|
+
) -> ValidationSuggestion:
|
184
|
+
"""Get node-specific suggestion."""
|
185
|
+
suggestions_map = {
|
186
|
+
"PythonCodeNode": ValidationSuggestion(
|
187
|
+
message="PythonCodeNode requires 'code' or use .from_function()",
|
188
|
+
code_example="""
|
189
|
+
# Option 1: Inline code
|
190
|
+
PythonCodeNode(code='return {"result": data}')
|
191
|
+
|
192
|
+
# Option 2: From function (recommended)
|
193
|
+
def process(data):
|
194
|
+
return {"result": data}
|
195
|
+
PythonCodeNode.from_function("processor", process)
|
196
|
+
""",
|
197
|
+
alternative_nodes=["DataTransformerNode", "FilterNode", "MapNode"],
|
198
|
+
),
|
199
|
+
"SQLDatabaseNode": ValidationSuggestion(
|
200
|
+
message="SQLDatabaseNode requires 'query' parameter",
|
201
|
+
code_example='SQLDatabaseNode(query="SELECT * FROM table", connection_string="...")',
|
202
|
+
alternative_nodes=["AsyncSQLDatabaseNode", "VectorDatabaseNode"],
|
203
|
+
),
|
204
|
+
"HTTPRequestNode": ValidationSuggestion(
|
205
|
+
message="HTTPRequestNode requires valid 'url' parameter",
|
206
|
+
code_example='HTTPRequestNode(url="https://api.example.com/data", method="GET")',
|
207
|
+
alternative_nodes=[
|
208
|
+
"RESTClientNode",
|
209
|
+
"GraphQLClientNode",
|
210
|
+
"WebhookNode",
|
211
|
+
],
|
212
|
+
),
|
213
|
+
}
|
214
|
+
return suggestions_map.get(
|
215
|
+
node_type,
|
216
|
+
ValidationSuggestion(message=f"Invalid configuration for {node_type}"),
|
217
|
+
)
|
218
|
+
|
219
|
+
@classmethod
|
220
|
+
def suggest_alternative_nodes(cls, use_case: str) -> List[str]:
|
221
|
+
"""Suggest alternative nodes for a use case."""
|
222
|
+
# Fuzzy match use case
|
223
|
+
for key, nodes in cls.ALTERNATIVE_NODES.items():
|
224
|
+
if key in use_case.lower() or use_case.lower() in key:
|
225
|
+
return nodes
|
226
|
+
return []
|
227
|
+
|
228
|
+
@classmethod
|
229
|
+
def format_error_with_suggestions(
|
230
|
+
cls,
|
231
|
+
error: Exception,
|
232
|
+
node_type: str,
|
233
|
+
config: Dict[str, Any],
|
234
|
+
context: Optional[Dict[str, Any]] = None,
|
235
|
+
) -> str:
|
236
|
+
"""Format error message with helpful suggestions."""
|
237
|
+
suggestions = cls.validate_node_config(node_type, config)
|
238
|
+
|
239
|
+
# Build formatted error message
|
240
|
+
lines = [f"❌ Error in {node_type}: {str(error)}", ""]
|
241
|
+
|
242
|
+
if suggestions:
|
243
|
+
lines.append("💡 Suggestions:")
|
244
|
+
for i, suggestion in enumerate(suggestions, 1):
|
245
|
+
lines.append(f"\n{i}. {suggestion.message}")
|
246
|
+
|
247
|
+
if suggestion.code_example:
|
248
|
+
lines.append(f"\n Example:\n {suggestion.code_example}")
|
249
|
+
|
250
|
+
if suggestion.alternative_nodes:
|
251
|
+
lines.append(
|
252
|
+
f"\n Alternative nodes: {', '.join(suggestion.alternative_nodes)}"
|
253
|
+
)
|
254
|
+
|
255
|
+
if suggestion.doc_link:
|
256
|
+
lines.append(f"\n 📚 Documentation: {suggestion.doc_link}")
|
257
|
+
|
258
|
+
# Add context if provided
|
259
|
+
if context:
|
260
|
+
lines.extend(
|
261
|
+
[
|
262
|
+
"",
|
263
|
+
"📋 Context:",
|
264
|
+
f" Workflow: {context.get('workflow_name', 'Unknown')}",
|
265
|
+
f" Node ID: {context.get('node_id', 'Unknown')}",
|
266
|
+
f" Previous Node: {context.get('previous_node', 'None')}",
|
267
|
+
]
|
268
|
+
)
|
269
|
+
|
270
|
+
# Add generic help
|
271
|
+
lines.extend(
|
272
|
+
[
|
273
|
+
"",
|
274
|
+
"🔗 Resources:",
|
275
|
+
" - Node Catalog: sdk-users/nodes/comprehensive-node-catalog.md",
|
276
|
+
" - Quick Reference: sdk-users/developer/QUICK_REFERENCE.md",
|
277
|
+
" - Troubleshooting: sdk-users/developer/07-troubleshooting.md",
|
278
|
+
]
|
279
|
+
)
|
280
|
+
|
281
|
+
return "\n".join(lines)
|
282
|
+
|
283
|
+
|
284
|
+
def validate_node_decorator(node_class: Type[Node]) -> Type[Node]:
|
285
|
+
"""Decorator to add validation to node classes."""
|
286
|
+
|
287
|
+
original_init = node_class.__init__
|
288
|
+
original_run = node_class.run
|
289
|
+
|
290
|
+
def new_init(self, *args, **kwargs):
|
291
|
+
"""Enhanced init with validation."""
|
292
|
+
try:
|
293
|
+
original_init(self, *args, **kwargs)
|
294
|
+
except Exception as e:
|
295
|
+
# Enhance error with suggestions
|
296
|
+
error_msg = NodeValidator.format_error_with_suggestions(
|
297
|
+
e, node_class.__name__, kwargs, {"node_id": kwargs.get("id", "unknown")}
|
298
|
+
)
|
299
|
+
raise type(e)(error_msg) from e
|
300
|
+
|
301
|
+
def new_run(self, **inputs):
|
302
|
+
"""Enhanced run with validation."""
|
303
|
+
try:
|
304
|
+
return original_run(self, **inputs)
|
305
|
+
except Exception as e:
|
306
|
+
# Enhance error with runtime context
|
307
|
+
error_msg = NodeValidator.format_error_with_suggestions(
|
308
|
+
e,
|
309
|
+
node_class.__name__,
|
310
|
+
inputs,
|
311
|
+
{
|
312
|
+
"node_id": getattr(self, "id", "unknown"),
|
313
|
+
"inputs": list(inputs.keys()),
|
314
|
+
},
|
315
|
+
)
|
316
|
+
raise type(e)(error_msg) from e
|
317
|
+
|
318
|
+
node_class.__init__ = new_init
|
319
|
+
node_class.run = new_run
|
320
|
+
|
321
|
+
return node_class
|
@@ -239,7 +239,7 @@ class AccessControlledRuntime:
|
|
239
239
|
return {}
|
240
240
|
|
241
241
|
# Execute the original node
|
242
|
-
result = self._original_node.
|
242
|
+
result = self._original_node.execute(**inputs)
|
243
243
|
|
244
244
|
# Check output read permission
|
245
245
|
output_decision = runtime.acm.check_node_access(
|
kailash/runtime/async_local.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
"""Asynchronous local runtime engine for executing workflows.
|
2
2
|
|
3
|
+
DEPRECATED: This module is deprecated. The LocalRuntime in local.py now provides
|
4
|
+
unified async/sync execution capabilities. For backward compatibility, this module
|
5
|
+
exports LocalRuntime as AsyncLocalRuntime.
|
6
|
+
|
3
7
|
This module provides an asynchronous execution engine for Kailash workflows,
|
4
8
|
particularly useful for workflows with I/O-bound nodes such as API calls,
|
5
9
|
database queries, or LLM interactions.
|
@@ -11,7 +15,6 @@ from typing import Any
|
|
11
15
|
|
12
16
|
import networkx as nx
|
13
17
|
|
14
|
-
from kailash.nodes.base_async import AsyncNode
|
15
18
|
from kailash.sdk_exceptions import (
|
16
19
|
RuntimeExecutionError,
|
17
20
|
WorkflowExecutionError,
|
@@ -83,8 +86,8 @@ class AsyncLocalRuntime:
|
|
83
86
|
run_id = None
|
84
87
|
|
85
88
|
try:
|
86
|
-
# Validate workflow
|
87
|
-
workflow.validate()
|
89
|
+
# Validate workflow with runtime parameters (Session 061)
|
90
|
+
workflow.validate(runtime_parameters=parameters)
|
88
91
|
|
89
92
|
# Initialize tracking
|
90
93
|
if task_manager:
|
@@ -222,12 +225,13 @@ class AsyncLocalRuntime:
|
|
222
225
|
# Execute node - check if it supports async execution
|
223
226
|
start_time = datetime.now(UTC)
|
224
227
|
|
225
|
-
if
|
226
|
-
# Use async execution
|
228
|
+
if hasattr(node_instance, "execute_async"):
|
229
|
+
# Use async execution if available
|
227
230
|
outputs = await node_instance.execute_async(**inputs)
|
228
231
|
else:
|
229
|
-
# Fall back to synchronous execution
|
230
|
-
|
232
|
+
# Fall back to synchronous execution using execute()
|
233
|
+
# This ensures proper validation and error handling
|
234
|
+
outputs = node_instance.execute(**inputs)
|
231
235
|
|
232
236
|
execution_time = (datetime.now(UTC) - start_time).total_seconds()
|
233
237
|
|
@@ -352,3 +356,33 @@ class AsyncLocalRuntime:
|
|
352
356
|
# For now, stop if the failed node has dependents
|
353
357
|
# Future: implement configurable error handling policies
|
354
358
|
return has_dependents
|
359
|
+
|
360
|
+
|
361
|
+
# Backward compatibility: Use the unified LocalRuntime
|
362
|
+
from kailash.runtime.local import LocalRuntime # noqa: E402
|
363
|
+
|
364
|
+
# Export LocalRuntime as AsyncLocalRuntime for backward compatibility
|
365
|
+
# AsyncLocalRuntime = LocalRuntime # Commented out to avoid redefinition warning
|
366
|
+
|
367
|
+
|
368
|
+
# For better backward compatibility, create a wrapper that sets enable_async=True by default
|
369
|
+
class AsyncLocalRuntimeCompat(LocalRuntime):
|
370
|
+
"""Backward compatibility wrapper for AsyncLocalRuntime.
|
371
|
+
|
372
|
+
This wrapper automatically enables async execution and provides the same
|
373
|
+
interface as the original AsyncLocalRuntime.
|
374
|
+
"""
|
375
|
+
|
376
|
+
def __init__(self, debug: bool = False, max_concurrency: int = 10, **kwargs):
|
377
|
+
"""Initialize with async enabled by default."""
|
378
|
+
super().__init__(
|
379
|
+
debug=debug, enable_async=True, max_concurrency=max_concurrency, **kwargs
|
380
|
+
)
|
381
|
+
|
382
|
+
async def execute(self, *args, **kwargs):
|
383
|
+
"""Async execute method for full backward compatibility."""
|
384
|
+
return await self.execute_async(*args, **kwargs)
|
385
|
+
|
386
|
+
|
387
|
+
# Use the compatibility wrapper as the main export
|
388
|
+
# AsyncLocalRuntime = AsyncLocalRuntimeCompat # Commented out to avoid redefinition warning - class definition at top takes precedence
|
kailash/runtime/docker.py
CHANGED
@@ -201,7 +201,7 @@ def main():
|
|
201
201
|
# Execute node
|
202
202
|
logger.info(f"Executing node with inputs: {list(runtime_inputs.keys())}")
|
203
203
|
try:
|
204
|
-
result = node.
|
204
|
+
result = node.execute(**runtime_inputs)
|
205
205
|
logger.info("Node execution completed successfully")
|
206
206
|
except Exception as e:
|
207
207
|
logger.error(f"Node execution failed: {e}")
|