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.
Files changed (146) hide show
  1. kailash/__init__.py +33 -1
  2. kailash/access_control/__init__.py +129 -0
  3. kailash/access_control/managers.py +461 -0
  4. kailash/access_control/rule_evaluators.py +467 -0
  5. kailash/access_control_abac.py +825 -0
  6. kailash/config/__init__.py +27 -0
  7. kailash/config/database_config.py +359 -0
  8. kailash/database/__init__.py +28 -0
  9. kailash/database/execution_pipeline.py +499 -0
  10. kailash/middleware/__init__.py +306 -0
  11. kailash/middleware/auth/__init__.py +33 -0
  12. kailash/middleware/auth/access_control.py +436 -0
  13. kailash/middleware/auth/auth_manager.py +422 -0
  14. kailash/middleware/auth/jwt_auth.py +477 -0
  15. kailash/middleware/auth/kailash_jwt_auth.py +616 -0
  16. kailash/middleware/communication/__init__.py +37 -0
  17. kailash/middleware/communication/ai_chat.py +989 -0
  18. kailash/middleware/communication/api_gateway.py +802 -0
  19. kailash/middleware/communication/events.py +470 -0
  20. kailash/middleware/communication/realtime.py +710 -0
  21. kailash/middleware/core/__init__.py +21 -0
  22. kailash/middleware/core/agent_ui.py +890 -0
  23. kailash/middleware/core/schema.py +643 -0
  24. kailash/middleware/core/workflows.py +396 -0
  25. kailash/middleware/database/__init__.py +63 -0
  26. kailash/middleware/database/base.py +113 -0
  27. kailash/middleware/database/base_models.py +525 -0
  28. kailash/middleware/database/enums.py +106 -0
  29. kailash/middleware/database/migrations.py +12 -0
  30. kailash/{api/database.py → middleware/database/models.py} +183 -291
  31. kailash/middleware/database/repositories.py +685 -0
  32. kailash/middleware/database/session_manager.py +19 -0
  33. kailash/middleware/mcp/__init__.py +38 -0
  34. kailash/middleware/mcp/client_integration.py +585 -0
  35. kailash/middleware/mcp/enhanced_server.py +576 -0
  36. kailash/nodes/__init__.py +25 -3
  37. kailash/nodes/admin/__init__.py +35 -0
  38. kailash/nodes/admin/audit_log.py +794 -0
  39. kailash/nodes/admin/permission_check.py +864 -0
  40. kailash/nodes/admin/role_management.py +823 -0
  41. kailash/nodes/admin/security_event.py +1519 -0
  42. kailash/nodes/admin/user_management.py +944 -0
  43. kailash/nodes/ai/a2a.py +24 -7
  44. kailash/nodes/ai/ai_providers.py +1 -0
  45. kailash/nodes/ai/embedding_generator.py +11 -11
  46. kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
  47. kailash/nodes/ai/llm_agent.py +407 -2
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/api/auth.py +287 -6
  50. kailash/nodes/api/rest.py +151 -0
  51. kailash/nodes/auth/__init__.py +17 -0
  52. kailash/nodes/auth/directory_integration.py +1228 -0
  53. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  54. kailash/nodes/auth/mfa.py +2338 -0
  55. kailash/nodes/auth/risk_assessment.py +872 -0
  56. kailash/nodes/auth/session_management.py +1093 -0
  57. kailash/nodes/auth/sso.py +1040 -0
  58. kailash/nodes/base.py +344 -13
  59. kailash/nodes/base_cycle_aware.py +4 -2
  60. kailash/nodes/base_with_acl.py +1 -1
  61. kailash/nodes/code/python.py +293 -12
  62. kailash/nodes/compliance/__init__.py +9 -0
  63. kailash/nodes/compliance/data_retention.py +1888 -0
  64. kailash/nodes/compliance/gdpr.py +2004 -0
  65. kailash/nodes/data/__init__.py +22 -2
  66. kailash/nodes/data/async_connection.py +469 -0
  67. kailash/nodes/data/async_sql.py +757 -0
  68. kailash/nodes/data/async_vector.py +598 -0
  69. kailash/nodes/data/readers.py +767 -0
  70. kailash/nodes/data/retrieval.py +360 -1
  71. kailash/nodes/data/sharepoint_graph.py +397 -21
  72. kailash/nodes/data/sql.py +94 -5
  73. kailash/nodes/data/streaming.py +68 -8
  74. kailash/nodes/data/vector_db.py +54 -4
  75. kailash/nodes/enterprise/__init__.py +13 -0
  76. kailash/nodes/enterprise/batch_processor.py +741 -0
  77. kailash/nodes/enterprise/data_lineage.py +497 -0
  78. kailash/nodes/logic/convergence.py +31 -9
  79. kailash/nodes/logic/operations.py +14 -3
  80. kailash/nodes/mixins/__init__.py +8 -0
  81. kailash/nodes/mixins/event_emitter.py +201 -0
  82. kailash/nodes/mixins/mcp.py +9 -4
  83. kailash/nodes/mixins/security.py +165 -0
  84. kailash/nodes/monitoring/__init__.py +7 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  86. kailash/nodes/rag/__init__.py +284 -0
  87. kailash/nodes/rag/advanced.py +1615 -0
  88. kailash/nodes/rag/agentic.py +773 -0
  89. kailash/nodes/rag/conversational.py +999 -0
  90. kailash/nodes/rag/evaluation.py +875 -0
  91. kailash/nodes/rag/federated.py +1188 -0
  92. kailash/nodes/rag/graph.py +721 -0
  93. kailash/nodes/rag/multimodal.py +671 -0
  94. kailash/nodes/rag/optimized.py +933 -0
  95. kailash/nodes/rag/privacy.py +1059 -0
  96. kailash/nodes/rag/query_processing.py +1335 -0
  97. kailash/nodes/rag/realtime.py +764 -0
  98. kailash/nodes/rag/registry.py +547 -0
  99. kailash/nodes/rag/router.py +837 -0
  100. kailash/nodes/rag/similarity.py +1854 -0
  101. kailash/nodes/rag/strategies.py +566 -0
  102. kailash/nodes/rag/workflows.py +575 -0
  103. kailash/nodes/security/__init__.py +19 -0
  104. kailash/nodes/security/abac_evaluator.py +1411 -0
  105. kailash/nodes/security/audit_log.py +91 -0
  106. kailash/nodes/security/behavior_analysis.py +1893 -0
  107. kailash/nodes/security/credential_manager.py +401 -0
  108. kailash/nodes/security/rotating_credentials.py +760 -0
  109. kailash/nodes/security/security_event.py +132 -0
  110. kailash/nodes/security/threat_detection.py +1103 -0
  111. kailash/nodes/testing/__init__.py +9 -0
  112. kailash/nodes/testing/credential_testing.py +499 -0
  113. kailash/nodes/transform/__init__.py +10 -2
  114. kailash/nodes/transform/chunkers.py +592 -1
  115. kailash/nodes/transform/processors.py +484 -14
  116. kailash/nodes/validation.py +321 -0
  117. kailash/runtime/access_controlled.py +1 -1
  118. kailash/runtime/async_local.py +41 -7
  119. kailash/runtime/docker.py +1 -1
  120. kailash/runtime/local.py +474 -55
  121. kailash/runtime/parallel.py +1 -1
  122. kailash/runtime/parallel_cyclic.py +1 -1
  123. kailash/runtime/testing.py +210 -2
  124. kailash/utils/migrations/__init__.py +25 -0
  125. kailash/utils/migrations/generator.py +433 -0
  126. kailash/utils/migrations/models.py +231 -0
  127. kailash/utils/migrations/runner.py +489 -0
  128. kailash/utils/secure_logging.py +342 -0
  129. kailash/workflow/__init__.py +16 -0
  130. kailash/workflow/cyclic_runner.py +3 -4
  131. kailash/workflow/graph.py +70 -2
  132. kailash/workflow/resilience.py +249 -0
  133. kailash/workflow/templates.py +726 -0
  134. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
  135. kailash-0.4.0.dist-info/RECORD +223 -0
  136. kailash/api/__init__.py +0 -17
  137. kailash/api/__main__.py +0 -6
  138. kailash/api/studio_secure.py +0 -893
  139. kailash/mcp/__main__.py +0 -13
  140. kailash/mcp/server_new.py +0 -336
  141. kailash/mcp/servers/__init__.py +0 -12
  142. kailash-0.3.1.dist-info/RECORD +0 -136
  143. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
  144. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
  145. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
  146. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -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
- f"Import of module '{module_name}' is not allowed"
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
- f"Import from module '{module_name}' is not allowed"
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(f"Call to '{func_name}' is not allowed")
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
- f"Call to method '{node.func.attr}' is not allowed"
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) -> None:
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
- raise SafetyViolationError(
268
- f"Code contains unsafe operations: {'; '.join(checker.violations)}"
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(f"Invalid Python syntax: {e}")
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
- # Wrap non-dict results in a dict
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
- # Wrap non-dict results in a dict
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
@@ -0,0 +1,9 @@
1
+ """Compliance-related nodes for the Kailash SDK."""
2
+
3
+ from .data_retention import DataRetentionPolicyNode
4
+ from .gdpr import GDPRComplianceNode
5
+
6
+ __all__ = [
7
+ "GDPRComplianceNode",
8
+ "DataRetentionPolicyNode",
9
+ ]