kailash 0.3.1__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 +293 -12
- 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.1.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.1.dist-info/RECORD +0 -136
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
kailash/nodes/code/python.py
CHANGED
@@ -112,14 +112,21 @@ class SafeCodeChecker(ast.NodeVisitor):
|
|
112
112
|
|
113
113
|
def __init__(self):
|
114
114
|
self.violations = []
|
115
|
+
self.imports_found = []
|
115
116
|
|
116
117
|
def visit_Import(self, node):
|
117
118
|
"""Check import statements."""
|
118
119
|
for alias in node.names:
|
119
120
|
module_name = alias.name.split(".")[0]
|
121
|
+
self.imports_found.append(module_name)
|
120
122
|
if module_name not in ALLOWED_MODULES:
|
121
123
|
self.violations.append(
|
122
|
-
|
124
|
+
{
|
125
|
+
"type": "import",
|
126
|
+
"module": module_name,
|
127
|
+
"line": node.lineno,
|
128
|
+
"message": f"Import of module '{module_name}' is not allowed",
|
129
|
+
}
|
123
130
|
)
|
124
131
|
self.generic_visit(node)
|
125
132
|
|
@@ -127,9 +134,15 @@ class SafeCodeChecker(ast.NodeVisitor):
|
|
127
134
|
"""Check from imports."""
|
128
135
|
if node.module:
|
129
136
|
module_name = node.module.split(".")[0]
|
137
|
+
self.imports_found.append(module_name)
|
130
138
|
if module_name not in ALLOWED_MODULES:
|
131
139
|
self.violations.append(
|
132
|
-
|
140
|
+
{
|
141
|
+
"type": "import_from",
|
142
|
+
"module": module_name,
|
143
|
+
"line": node.lineno,
|
144
|
+
"message": f"Import from module '{module_name}' is not allowed",
|
145
|
+
}
|
133
146
|
)
|
134
147
|
self.generic_visit(node)
|
135
148
|
|
@@ -139,12 +152,24 @@ class SafeCodeChecker(ast.NodeVisitor):
|
|
139
152
|
func_name = node.func.id
|
140
153
|
# Check for dangerous built-in functions
|
141
154
|
if func_name in {"eval", "exec", "compile"}:
|
142
|
-
self.violations.append(
|
155
|
+
self.violations.append(
|
156
|
+
{
|
157
|
+
"type": "function_call",
|
158
|
+
"function": func_name,
|
159
|
+
"line": node.lineno,
|
160
|
+
"message": f"Call to '{func_name}' is not allowed",
|
161
|
+
}
|
162
|
+
)
|
143
163
|
elif isinstance(node.func, ast.Attribute):
|
144
164
|
# Check for dangerous method calls
|
145
165
|
if node.func.attr in {"system", "popen"}:
|
146
166
|
self.violations.append(
|
147
|
-
|
167
|
+
{
|
168
|
+
"type": "method_call",
|
169
|
+
"method": node.func.attr,
|
170
|
+
"line": node.lineno,
|
171
|
+
"message": f"Call to method '{node.func.attr}' is not allowed",
|
172
|
+
}
|
148
173
|
)
|
149
174
|
self.generic_visit(node)
|
150
175
|
|
@@ -249,12 +274,15 @@ class CodeExecutor:
|
|
249
274
|
}
|
250
275
|
self._execution_namespace = {}
|
251
276
|
|
252
|
-
def check_code_safety(self, code: str) ->
|
277
|
+
def check_code_safety(self, code: str) -> tuple[bool, list[dict], list[str]]:
|
253
278
|
"""Check if code is safe to execute.
|
254
279
|
|
255
280
|
Args:
|
256
281
|
code: Python code to check
|
257
282
|
|
283
|
+
Returns:
|
284
|
+
Tuple of (is_safe, violations, imports_found)
|
285
|
+
|
258
286
|
Raises:
|
259
287
|
SafetyViolationError: If code contains unsafe operations
|
260
288
|
"""
|
@@ -264,11 +292,67 @@ class CodeExecutor:
|
|
264
292
|
checker.visit(tree)
|
265
293
|
|
266
294
|
if checker.violations:
|
267
|
-
|
268
|
-
|
269
|
-
|
295
|
+
# Create detailed error message with suggestions
|
296
|
+
error_parts = []
|
297
|
+
suggestions = []
|
298
|
+
|
299
|
+
for violation in checker.violations:
|
300
|
+
error_parts.append(
|
301
|
+
f"Line {violation['line']}: {violation['message']}"
|
302
|
+
)
|
303
|
+
|
304
|
+
# Add suggestions based on violation type
|
305
|
+
if violation["type"] in ["import", "import_from"]:
|
306
|
+
module = violation["module"]
|
307
|
+
suggestions.append(
|
308
|
+
f"Module '{module}' is not allowed. Available modules: {', '.join(sorted(ALLOWED_MODULES))}"
|
309
|
+
)
|
310
|
+
|
311
|
+
# Suggest alternatives for common cases
|
312
|
+
if module == "subprocess":
|
313
|
+
suggestions.append(
|
314
|
+
"For file operations, use 'os' or 'pathlib' modules instead"
|
315
|
+
)
|
316
|
+
elif module == "requests":
|
317
|
+
suggestions.append(
|
318
|
+
"For HTTP requests, use HTTPRequestNode instead of importing requests"
|
319
|
+
)
|
320
|
+
elif module == "sqlite3" or module == "psycopg2":
|
321
|
+
suggestions.append(
|
322
|
+
"For database operations, use SQLDatabaseNode instead"
|
323
|
+
)
|
324
|
+
elif module == "boto3":
|
325
|
+
suggestions.append(
|
326
|
+
"For AWS operations, create a custom node or use existing cloud nodes"
|
327
|
+
)
|
328
|
+
|
329
|
+
elif violation["type"] == "function_call":
|
330
|
+
func = violation["function"]
|
331
|
+
if func in ["eval", "exec"]:
|
332
|
+
suggestions.append(
|
333
|
+
f"'{func}' is dangerous. Write explicit code instead of dynamic execution"
|
334
|
+
)
|
335
|
+
elif func == "compile":
|
336
|
+
suggestions.append(
|
337
|
+
"'compile' is not allowed. Use standard Python code instead"
|
338
|
+
)
|
339
|
+
|
340
|
+
error_msg = "Code safety violations found:\n" + "\n".join(error_parts)
|
341
|
+
if suggestions:
|
342
|
+
error_msg += "\n\nSuggestions:\n" + "\n".join(
|
343
|
+
f"- {s}" for s in suggestions
|
344
|
+
)
|
345
|
+
|
346
|
+
raise SafetyViolationError(error_msg)
|
347
|
+
|
348
|
+
return True, checker.violations, checker.imports_found
|
349
|
+
|
270
350
|
except SyntaxError as e:
|
271
|
-
raise NodeExecutionError(
|
351
|
+
raise NodeExecutionError(
|
352
|
+
f"Invalid Python syntax at line {e.lineno}: {e.msg}\n"
|
353
|
+
f"Text: {e.text}\n"
|
354
|
+
f"Error position: {' ' * (e.offset - 1) if e.offset else ''}^"
|
355
|
+
)
|
272
356
|
|
273
357
|
def execute_code(self, code: str, inputs: dict[str, Any]) -> dict[str, Any]:
|
274
358
|
"""Execute Python code with given inputs.
|
@@ -286,7 +370,7 @@ class CodeExecutor:
|
|
286
370
|
MemoryLimitError: If memory usage exceeds limit
|
287
371
|
"""
|
288
372
|
# Check code safety first
|
289
|
-
self.check_code_safety(code)
|
373
|
+
is_safe, violations, imports_found = self.check_code_safety(code)
|
290
374
|
|
291
375
|
# Sanitize inputs
|
292
376
|
sanitized_inputs = validate_node_parameters(inputs, self.security_config)
|
@@ -451,9 +535,13 @@ class FunctionWrapper:
|
|
451
535
|
"""Execute the wrapped function."""
|
452
536
|
result = self.executor.execute_function(self.func, inputs)
|
453
537
|
|
454
|
-
#
|
538
|
+
# Always wrap results in "result" key for consistent validation
|
539
|
+
# This ensures both dict and non-dict returns have the same structure
|
455
540
|
if not isinstance(result, dict):
|
456
541
|
result = {"result": result}
|
542
|
+
else:
|
543
|
+
# For dict results, wrap the entire dict in "result" key
|
544
|
+
result = {"result": result}
|
457
545
|
|
458
546
|
return result
|
459
547
|
|
@@ -604,9 +692,13 @@ class ClassWrapper:
|
|
604
692
|
# Execute the method
|
605
693
|
result = self.executor.execute_function(method, inputs)
|
606
694
|
|
607
|
-
#
|
695
|
+
# Always wrap results in "result" key for consistent validation
|
696
|
+
# This ensures both dict and non-dict returns have the same structure
|
608
697
|
if not isinstance(result, dict):
|
609
698
|
result = {"result": result}
|
699
|
+
else:
|
700
|
+
# For dict results, wrap the entire dict in "result" key
|
701
|
+
result = {"result": result}
|
610
702
|
|
611
703
|
return result
|
612
704
|
|
@@ -717,6 +809,7 @@ class PythonCodeNode(Node):
|
|
717
809
|
input_schema: dict[str, "NodeParameter"] | None = None,
|
718
810
|
output_schema: dict[str, "NodeParameter"] | None = None,
|
719
811
|
description: str | None = None,
|
812
|
+
max_code_lines: int = 10,
|
720
813
|
**kwargs,
|
721
814
|
):
|
722
815
|
"""Initialize a Python code node.
|
@@ -732,6 +825,7 @@ class PythonCodeNode(Node):
|
|
732
825
|
input_schema: Explicit input parameter schema for validation
|
733
826
|
output_schema: Explicit output parameter schema for validation
|
734
827
|
description: Node description
|
828
|
+
max_code_lines: Maximum lines before warning (default: 10)
|
735
829
|
**kwargs: Additional node parameters
|
736
830
|
"""
|
737
831
|
# Validate inputs
|
@@ -753,6 +847,20 @@ class PythonCodeNode(Node):
|
|
753
847
|
self.output_type = output_type or Any
|
754
848
|
self._input_schema = input_schema
|
755
849
|
self._output_schema = output_schema
|
850
|
+
self.max_code_lines = max_code_lines
|
851
|
+
|
852
|
+
# Check code length and warn if exceeds threshold
|
853
|
+
if self.code and self.max_code_lines > 0:
|
854
|
+
code_lines = [
|
855
|
+
line for line in self.code.strip().split("\n") if line.strip()
|
856
|
+
]
|
857
|
+
if len(code_lines) > self.max_code_lines:
|
858
|
+
logger.warning(
|
859
|
+
f"PythonCodeNode '{name}' contains {len(code_lines)} lines of code, "
|
860
|
+
f"exceeding the recommended maximum of {self.max_code_lines} lines. "
|
861
|
+
"Consider using PythonCodeNode.from_function() or from_file() for better "
|
862
|
+
"code organization and IDE support."
|
863
|
+
)
|
756
864
|
|
757
865
|
# For class-based nodes, maintain instance
|
758
866
|
self.instance = None
|
@@ -916,6 +1024,32 @@ class PythonCodeNode(Node):
|
|
916
1024
|
|
917
1025
|
except NodeExecutionError:
|
918
1026
|
raise
|
1027
|
+
except ImportError as e:
|
1028
|
+
# Enhanced import error handling
|
1029
|
+
module_name = str(e).split("'")[1] if "'" in str(e) else "unknown"
|
1030
|
+
error_msg = f"Import error: {str(e)}\n\n"
|
1031
|
+
|
1032
|
+
# Check if module is in allowed list
|
1033
|
+
if module_name not in ALLOWED_MODULES:
|
1034
|
+
error_msg += f"Module '{module_name}' is not in the allowed list.\n"
|
1035
|
+
error_msg += (
|
1036
|
+
f"Allowed modules: {', '.join(sorted(ALLOWED_MODULES))}\n\n"
|
1037
|
+
)
|
1038
|
+
|
1039
|
+
# Suggest alternatives
|
1040
|
+
if module_name == "requests":
|
1041
|
+
error_msg += "Suggestion: Use HTTPRequestNode for HTTP requests instead of importing requests.\n"
|
1042
|
+
elif module_name in ["sqlite3", "psycopg2", "pymongo"]:
|
1043
|
+
error_msg += (
|
1044
|
+
"Suggestion: Use SQLDatabaseNode for database operations.\n"
|
1045
|
+
)
|
1046
|
+
elif module_name == "boto3":
|
1047
|
+
error_msg += "Suggestion: Create a custom node for AWS operations or use cloud-specific nodes.\n"
|
1048
|
+
else:
|
1049
|
+
error_msg += f"Module '{module_name}' is allowed but not installed.\n"
|
1050
|
+
error_msg += "Suggestion: Install the module using pip or check your environment.\n"
|
1051
|
+
|
1052
|
+
raise NodeExecutionError(error_msg)
|
919
1053
|
except Exception as e:
|
920
1054
|
logger.error(f"Python code execution failed: {e}")
|
921
1055
|
raise NodeExecutionError(f"Execution failed: {str(e)}")
|
@@ -1154,3 +1288,150 @@ class PythonCodeNode(Node):
|
|
1154
1288
|
config["process_method"] = self.process_method
|
1155
1289
|
|
1156
1290
|
return config
|
1291
|
+
|
1292
|
+
@staticmethod
|
1293
|
+
def list_allowed_modules() -> list[str]:
|
1294
|
+
"""List all allowed modules for import in PythonCodeNode.
|
1295
|
+
|
1296
|
+
Returns:
|
1297
|
+
Sorted list of allowed module names
|
1298
|
+
"""
|
1299
|
+
return sorted(ALLOWED_MODULES)
|
1300
|
+
|
1301
|
+
@staticmethod
|
1302
|
+
def check_module_availability(module_name: str) -> dict[str, Any]:
|
1303
|
+
"""Check if a module is allowed and available for import.
|
1304
|
+
|
1305
|
+
Args:
|
1306
|
+
module_name: Name of the module to check
|
1307
|
+
|
1308
|
+
Returns:
|
1309
|
+
Dictionary with status information
|
1310
|
+
"""
|
1311
|
+
result = {
|
1312
|
+
"module": module_name,
|
1313
|
+
"allowed": module_name in ALLOWED_MODULES,
|
1314
|
+
"installed": False,
|
1315
|
+
"importable": False,
|
1316
|
+
"error": None,
|
1317
|
+
"suggestions": [],
|
1318
|
+
}
|
1319
|
+
|
1320
|
+
if not result["allowed"]:
|
1321
|
+
result["suggestions"].append(
|
1322
|
+
f"Module '{module_name}' is not in the allowed list."
|
1323
|
+
)
|
1324
|
+
result["suggestions"].append(
|
1325
|
+
f"Allowed modules: {', '.join(sorted(ALLOWED_MODULES))}"
|
1326
|
+
)
|
1327
|
+
|
1328
|
+
# Add specific suggestions for common modules
|
1329
|
+
if module_name == "requests":
|
1330
|
+
result["suggestions"].append(
|
1331
|
+
"Use HTTPRequestNode for HTTP requests instead."
|
1332
|
+
)
|
1333
|
+
elif module_name in ["sqlite3", "psycopg2", "pymongo", "mysql"]:
|
1334
|
+
result["suggestions"].append(
|
1335
|
+
"Use SQLDatabaseNode for database operations."
|
1336
|
+
)
|
1337
|
+
elif module_name == "boto3":
|
1338
|
+
result["suggestions"].append(
|
1339
|
+
"Use cloud-specific nodes or create a custom node."
|
1340
|
+
)
|
1341
|
+
elif module_name == "subprocess":
|
1342
|
+
result["suggestions"].append(
|
1343
|
+
"For security reasons, subprocess is not allowed. Use os or pathlib for file operations."
|
1344
|
+
)
|
1345
|
+
else:
|
1346
|
+
# Check if module is installed
|
1347
|
+
try:
|
1348
|
+
spec = importlib.util.find_spec(module_name)
|
1349
|
+
result["installed"] = spec is not None
|
1350
|
+
|
1351
|
+
if result["installed"]:
|
1352
|
+
# Try to import it
|
1353
|
+
try:
|
1354
|
+
importlib.import_module(module_name)
|
1355
|
+
result["importable"] = True
|
1356
|
+
except Exception as e:
|
1357
|
+
result["error"] = str(e)
|
1358
|
+
result["suggestions"].append(
|
1359
|
+
f"Module is installed but cannot be imported: {e}"
|
1360
|
+
)
|
1361
|
+
else:
|
1362
|
+
result["suggestions"].append(
|
1363
|
+
f"Module '{module_name}' needs to be installed: pip install {module_name}"
|
1364
|
+
)
|
1365
|
+
except Exception as e:
|
1366
|
+
result["error"] = str(e)
|
1367
|
+
result["suggestions"].append(f"Error checking module: {e}")
|
1368
|
+
|
1369
|
+
return result
|
1370
|
+
|
1371
|
+
def validate_code(self, code: str) -> dict[str, Any]:
|
1372
|
+
"""Validate Python code and provide detailed feedback.
|
1373
|
+
|
1374
|
+
Args:
|
1375
|
+
code: Python code to validate
|
1376
|
+
|
1377
|
+
Returns:
|
1378
|
+
Dictionary with validation results
|
1379
|
+
"""
|
1380
|
+
result = {
|
1381
|
+
"valid": True,
|
1382
|
+
"syntax_errors": [],
|
1383
|
+
"safety_violations": [],
|
1384
|
+
"imports": [],
|
1385
|
+
"suggestions": [],
|
1386
|
+
"warnings": [],
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
# Check syntax
|
1390
|
+
try:
|
1391
|
+
ast.parse(code)
|
1392
|
+
except SyntaxError as e:
|
1393
|
+
result["valid"] = False
|
1394
|
+
result["syntax_errors"].append(
|
1395
|
+
{"line": e.lineno, "column": e.offset, "message": e.msg, "text": e.text}
|
1396
|
+
)
|
1397
|
+
result["suggestions"].append(
|
1398
|
+
f"Fix syntax error at line {e.lineno}: {e.msg}"
|
1399
|
+
)
|
1400
|
+
return result
|
1401
|
+
|
1402
|
+
# Check safety
|
1403
|
+
try:
|
1404
|
+
is_safe, violations, imports_found = self.executor.check_code_safety(code)
|
1405
|
+
result["imports"] = imports_found
|
1406
|
+
|
1407
|
+
if violations:
|
1408
|
+
result["valid"] = False
|
1409
|
+
result["safety_violations"] = violations
|
1410
|
+
|
1411
|
+
# Add suggestions from violations
|
1412
|
+
for violation in violations:
|
1413
|
+
if violation["type"] in ["import", "import_from"]:
|
1414
|
+
module_info = self.check_module_availability(
|
1415
|
+
violation["module"]
|
1416
|
+
)
|
1417
|
+
result["suggestions"].extend(module_info["suggestions"])
|
1418
|
+
|
1419
|
+
except Exception as e:
|
1420
|
+
result["warnings"].append(f"Could not complete safety check: {e}")
|
1421
|
+
|
1422
|
+
# Check for common issues
|
1423
|
+
if "print(" in code and "result" not in code:
|
1424
|
+
result["warnings"].append(
|
1425
|
+
"Code uses print() but doesn't set 'result'. Output might not be captured."
|
1426
|
+
)
|
1427
|
+
result["suggestions"].append(
|
1428
|
+
"Set 'result' variable to return values from the node."
|
1429
|
+
)
|
1430
|
+
|
1431
|
+
if "input(" in code:
|
1432
|
+
result["warnings"].append("Code uses input() which will block execution.")
|
1433
|
+
result["suggestions"].append(
|
1434
|
+
"Use node parameters instead of input() for user input."
|
1435
|
+
)
|
1436
|
+
|
1437
|
+
return result
|