genxai-framework 0.1.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 (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,214 @@
1
+ """Resource limits and monitoring for tool execution."""
2
+
3
+ import time
4
+ import logging
5
+ from typing import Dict, Any, Optional
6
+ from dataclasses import dataclass
7
+ from collections import defaultdict
8
+ from threading import Lock
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class ResourceLimits:
15
+ """Resource limits for tool execution."""
16
+
17
+ max_execution_time: float = 30.0 # seconds
18
+ max_memory_mb: Optional[float] = None # MB (not enforced yet, placeholder)
19
+ max_cpu_percent: Optional[float] = None # % (not enforced yet, placeholder)
20
+
21
+ def __post_init__(self):
22
+ """Validate limits."""
23
+ if self.max_execution_time <= 0:
24
+ raise ValueError("max_execution_time must be positive")
25
+
26
+ if self.max_memory_mb is not None and self.max_memory_mb <= 0:
27
+ raise ValueError("max_memory_mb must be positive")
28
+
29
+ if self.max_cpu_percent is not None and (
30
+ self.max_cpu_percent <= 0 or self.max_cpu_percent > 100
31
+ ):
32
+ raise ValueError("max_cpu_percent must be between 0 and 100")
33
+
34
+
35
+ class ExecutionLimiter:
36
+ """Rate limiter and resource monitor for tool execution."""
37
+
38
+ def __init__(
39
+ self,
40
+ max_executions_per_minute: int = 60,
41
+ max_executions_per_hour: int = 1000,
42
+ resource_limits: Optional[ResourceLimits] = None
43
+ ):
44
+ """Initialize execution limiter.
45
+
46
+ Args:
47
+ max_executions_per_minute: Maximum executions per minute per tool
48
+ max_executions_per_hour: Maximum executions per hour per tool
49
+ resource_limits: Resource limits for execution
50
+ """
51
+ self.max_executions_per_minute = max_executions_per_minute
52
+ self.max_executions_per_hour = max_executions_per_hour
53
+ self.resource_limits = resource_limits or ResourceLimits()
54
+
55
+ # Track execution history
56
+ self._execution_history: Dict[str, list] = defaultdict(list)
57
+ self._lock = Lock()
58
+
59
+ logger.info(
60
+ f"ExecutionLimiter initialized: "
61
+ f"{max_executions_per_minute}/min, {max_executions_per_hour}/hour"
62
+ )
63
+
64
+ def _clean_old_executions(self, tool_name: str, current_time: float) -> None:
65
+ """Remove executions older than 1 hour.
66
+
67
+ Args:
68
+ tool_name: Name of the tool
69
+ current_time: Current timestamp
70
+ """
71
+ hour_ago = current_time - 3600
72
+ self._execution_history[tool_name] = [
73
+ ts for ts in self._execution_history[tool_name]
74
+ if ts > hour_ago
75
+ ]
76
+
77
+ def check_rate_limit(self, tool_name: str) -> tuple[bool, Optional[str]]:
78
+ """Check if execution is allowed based on rate limits.
79
+
80
+ Args:
81
+ tool_name: Name of the tool to execute
82
+
83
+ Returns:
84
+ Tuple of (allowed, error_message)
85
+ """
86
+ with self._lock:
87
+ current_time = time.time()
88
+
89
+ # Clean old executions
90
+ self._clean_old_executions(tool_name, current_time)
91
+
92
+ executions = self._execution_history[tool_name]
93
+
94
+ # Check per-minute limit
95
+ minute_ago = current_time - 60
96
+ recent_executions = sum(1 for ts in executions if ts > minute_ago)
97
+
98
+ if recent_executions >= self.max_executions_per_minute:
99
+ error_msg = (
100
+ f"Rate limit exceeded: {recent_executions} executions in the last minute. "
101
+ f"Maximum allowed: {self.max_executions_per_minute}/minute"
102
+ )
103
+ logger.warning(f"Rate limit exceeded for tool '{tool_name}': {error_msg}")
104
+ return False, error_msg
105
+
106
+ # Check per-hour limit
107
+ if len(executions) >= self.max_executions_per_hour:
108
+ error_msg = (
109
+ f"Rate limit exceeded: {len(executions)} executions in the last hour. "
110
+ f"Maximum allowed: {self.max_executions_per_hour}/hour"
111
+ )
112
+ logger.warning(f"Rate limit exceeded for tool '{tool_name}': {error_msg}")
113
+ return False, error_msg
114
+
115
+ return True, None
116
+
117
+ def record_execution(self, tool_name: str) -> None:
118
+ """Record a tool execution.
119
+
120
+ Args:
121
+ tool_name: Name of the tool executed
122
+ """
123
+ with self._lock:
124
+ current_time = time.time()
125
+ self._execution_history[tool_name].append(current_time)
126
+ logger.debug(f"Recorded execution for tool '{tool_name}'")
127
+
128
+ def get_execution_stats(self, tool_name: str) -> Dict[str, Any]:
129
+ """Get execution statistics for a tool.
130
+
131
+ Args:
132
+ tool_name: Name of the tool
133
+
134
+ Returns:
135
+ Dictionary with execution statistics
136
+ """
137
+ with self._lock:
138
+ current_time = time.time()
139
+ self._clean_old_executions(tool_name, current_time)
140
+
141
+ executions = self._execution_history[tool_name]
142
+ minute_ago = current_time - 60
143
+
144
+ recent_executions = sum(1 for ts in executions if ts > minute_ago)
145
+
146
+ return {
147
+ "tool_name": tool_name,
148
+ "executions_last_minute": recent_executions,
149
+ "executions_last_hour": len(executions),
150
+ "max_per_minute": self.max_executions_per_minute,
151
+ "max_per_hour": self.max_executions_per_hour,
152
+ "remaining_minute": max(0, self.max_executions_per_minute - recent_executions),
153
+ "remaining_hour": max(0, self.max_executions_per_hour - len(executions)),
154
+ }
155
+
156
+ def reset_limits(self, tool_name: Optional[str] = None) -> None:
157
+ """Reset execution limits.
158
+
159
+ Args:
160
+ tool_name: Name of tool to reset, or None to reset all
161
+ """
162
+ with self._lock:
163
+ if tool_name:
164
+ self._execution_history[tool_name] = []
165
+ logger.info(f"Reset execution limits for tool '{tool_name}'")
166
+ else:
167
+ self._execution_history.clear()
168
+ logger.info("Reset execution limits for all tools")
169
+
170
+ def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
171
+ """Get execution statistics for all tools.
172
+
173
+ Returns:
174
+ Dictionary mapping tool names to their statistics
175
+ """
176
+ with self._lock:
177
+ return {
178
+ tool_name: self.get_execution_stats(tool_name)
179
+ for tool_name in self._execution_history.keys()
180
+ }
181
+
182
+
183
+ # Global execution limiter instance
184
+ _global_limiter: Optional[ExecutionLimiter] = None
185
+ _limiter_lock = Lock()
186
+
187
+
188
+ def get_global_limiter() -> ExecutionLimiter:
189
+ """Get or create the global execution limiter.
190
+
191
+ Returns:
192
+ Global ExecutionLimiter instance
193
+ """
194
+ global _global_limiter
195
+
196
+ with _limiter_lock:
197
+ if _global_limiter is None:
198
+ _global_limiter = ExecutionLimiter()
199
+ logger.info("Created global ExecutionLimiter")
200
+
201
+ return _global_limiter
202
+
203
+
204
+ def set_global_limiter(limiter: ExecutionLimiter) -> None:
205
+ """Set the global execution limiter.
206
+
207
+ Args:
208
+ limiter: ExecutionLimiter instance to use globally
209
+ """
210
+ global _global_limiter
211
+
212
+ with _limiter_lock:
213
+ _global_limiter = limiter
214
+ logger.info("Updated global ExecutionLimiter")
@@ -0,0 +1,20 @@
1
+ """Tool execution policy enforcement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from genxai.config import get_settings
6
+
7
+
8
+ def is_tool_allowed(tool_name: str) -> tuple[bool, str | None]:
9
+ """Determine whether a tool can execute based on allow/deny lists."""
10
+ settings = get_settings()
11
+ allowlist = settings.allowlist_set()
12
+ denylist = settings.denylist_set()
13
+
14
+ if tool_name in denylist:
15
+ return False, f"Tool '{tool_name}' is denied by policy"
16
+
17
+ if allowlist and tool_name not in allowlist:
18
+ return False, f"Tool '{tool_name}' is not in allowlist"
19
+
20
+ return True, None
@@ -0,0 +1,248 @@
1
+ """Sandboxed execution environment for dynamic tools."""
2
+
3
+ import signal
4
+ import logging
5
+ from typing import Any, Dict, Optional
6
+ from contextlib import contextmanager
7
+
8
+ try:
9
+ from RestrictedPython import compile_restricted, safe_globals
10
+ from RestrictedPython.Guards import guarded_iter_unpack_sequence, safer_getattr
11
+ RESTRICTED_PYTHON_AVAILABLE = True
12
+ except ImportError:
13
+ RESTRICTED_PYTHON_AVAILABLE = False
14
+ safe_globals = {}
15
+ guarded_iter_unpack_sequence = None
16
+ safer_getattr = None
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ExecutionTimeout(Exception):
22
+ """Raised when code execution exceeds timeout limit."""
23
+ pass
24
+
25
+
26
+ class SafeExecutor:
27
+ """Secure executor for dynamic Python code using RestrictedPython."""
28
+
29
+ def __init__(self, timeout: int = 30):
30
+ """Initialize safe executor.
31
+
32
+ Args:
33
+ timeout: Maximum execution time in seconds (default: 30)
34
+ """
35
+ if not RESTRICTED_PYTHON_AVAILABLE:
36
+ logger.warning(
37
+ "RestrictedPython not available. Falling back to basic execution. "
38
+ "Install with: pip install RestrictedPython"
39
+ )
40
+
41
+ self.timeout = timeout
42
+ self._safe_builtins = self._create_safe_builtins()
43
+
44
+ def _create_safe_builtins(self) -> Dict[str, Any]:
45
+ """Create a safe set of built-in functions.
46
+
47
+ Returns:
48
+ Dictionary of safe built-in functions
49
+ """
50
+ if RESTRICTED_PYTHON_AVAILABLE:
51
+ # Use RestrictedPython's safe globals as base
52
+ safe_builtins = safe_globals.copy()
53
+
54
+ # Add additional safe functions
55
+ safe_builtins.update({
56
+ '_iter_unpack_sequence_': guarded_iter_unpack_sequence,
57
+ '_getattr_': safer_getattr,
58
+ # Safe built-ins
59
+ 'abs': abs,
60
+ 'all': all,
61
+ 'any': any,
62
+ 'bool': bool,
63
+ 'dict': dict,
64
+ 'enumerate': enumerate,
65
+ 'float': float,
66
+ 'int': int,
67
+ 'len': len,
68
+ 'list': list,
69
+ 'max': max,
70
+ 'min': min,
71
+ 'range': range,
72
+ 'round': round,
73
+ 'sorted': sorted,
74
+ 'str': str,
75
+ 'sum': sum,
76
+ 'tuple': tuple,
77
+ 'zip': zip,
78
+ 'isinstance': isinstance,
79
+ 'type': type,
80
+ 'hasattr': hasattr,
81
+ 'getattr': safer_getattr,
82
+ })
83
+ else:
84
+ # Fallback to basic safe builtins
85
+ safe_builtins = {
86
+ 'abs': abs,
87
+ 'all': all,
88
+ 'any': any,
89
+ 'bool': bool,
90
+ 'dict': dict,
91
+ 'enumerate': enumerate,
92
+ 'float': float,
93
+ 'int': int,
94
+ 'len': len,
95
+ 'list': list,
96
+ 'max': max,
97
+ 'min': min,
98
+ 'range': range,
99
+ 'round': round,
100
+ 'sorted': sorted,
101
+ 'str': str,
102
+ 'sum': sum,
103
+ 'tuple': tuple,
104
+ 'zip': zip,
105
+ 'isinstance': isinstance,
106
+ 'type': type,
107
+ }
108
+
109
+ return safe_builtins
110
+
111
+ @contextmanager
112
+ def _timeout_context(self):
113
+ """Context manager for execution timeout.
114
+
115
+ Raises:
116
+ ExecutionTimeout: If execution exceeds timeout
117
+ """
118
+ def timeout_handler(signum, frame):
119
+ raise ExecutionTimeout(f"Execution exceeded {self.timeout} seconds")
120
+
121
+ # Set the signal handler and alarm
122
+ old_handler = signal.signal(signal.SIGALRM, timeout_handler)
123
+ signal.alarm(self.timeout)
124
+
125
+ try:
126
+ yield
127
+ finally:
128
+ # Disable the alarm and restore old handler
129
+ signal.alarm(0)
130
+ signal.signal(signal.SIGALRM, old_handler)
131
+
132
+ def compile_code(self, code: str, filename: str = '<dynamic>') -> Any:
133
+ """Compile Python code with security restrictions.
134
+
135
+ Args:
136
+ code: Python source code to compile
137
+ filename: Filename for error messages
138
+
139
+ Returns:
140
+ Compiled code object
141
+
142
+ Raises:
143
+ SyntaxError: If code has syntax errors
144
+ ValueError: If code contains restricted operations
145
+ """
146
+ if RESTRICTED_PYTHON_AVAILABLE:
147
+ # Use RestrictedPython for compilation
148
+ byte_code = compile_restricted(code, filename, 'exec')
149
+
150
+ # Check for compilation errors
151
+ if byte_code.errors:
152
+ error_msg = "; ".join(byte_code.errors)
153
+ raise ValueError(f"Code contains restricted operations: {error_msg}")
154
+
155
+ return byte_code.code
156
+ else:
157
+ # Fallback to standard compilation
158
+ try:
159
+ return compile(code, filename, 'exec')
160
+ except SyntaxError as e:
161
+ raise SyntaxError(f"Invalid Python code: {e}")
162
+
163
+ def execute(
164
+ self,
165
+ compiled_code: Any,
166
+ parameters: Dict[str, Any],
167
+ enable_timeout: bool = True
168
+ ) -> Any:
169
+ """Execute compiled code in a sandboxed environment.
170
+
171
+ Args:
172
+ compiled_code: Compiled code object
173
+ parameters: Parameters to pass to the code
174
+ enable_timeout: Whether to enforce timeout (default: True)
175
+
176
+ Returns:
177
+ Execution result from 'result' variable
178
+
179
+ Raises:
180
+ ExecutionTimeout: If execution exceeds timeout
181
+ ValueError: If code doesn't set 'result' variable
182
+ RuntimeError: If execution fails
183
+ """
184
+ # Create execution namespace with safe builtins
185
+ namespace = {
186
+ '__builtins__': self._safe_builtins,
187
+ 'params': parameters,
188
+ }
189
+
190
+ try:
191
+ # Execute with timeout if enabled
192
+ if enable_timeout:
193
+ with self._timeout_context():
194
+ exec(compiled_code, namespace)
195
+ else:
196
+ exec(compiled_code, namespace)
197
+
198
+ # Check for result variable
199
+ if 'result' not in namespace:
200
+ raise ValueError(
201
+ "Tool code must set a 'result' variable with the output"
202
+ )
203
+
204
+ return namespace['result']
205
+
206
+ except ExecutionTimeout:
207
+ logger.error("Code execution timed out")
208
+ raise
209
+ except Exception as e:
210
+ logger.error(f"Code execution failed: {e}")
211
+ raise RuntimeError(f"Execution failed: {e}")
212
+
213
+ def execute_code(
214
+ self,
215
+ code: str,
216
+ parameters: Dict[str, Any],
217
+ enable_timeout: bool = True
218
+ ) -> Any:
219
+ """Compile and execute code in one step.
220
+
221
+ Args:
222
+ code: Python source code
223
+ parameters: Parameters to pass to the code
224
+ enable_timeout: Whether to enforce timeout (default: True)
225
+
226
+ Returns:
227
+ Execution result
228
+
229
+ Raises:
230
+ SyntaxError: If code has syntax errors
231
+ ValueError: If code contains restricted operations or missing result
232
+ ExecutionTimeout: If execution exceeds timeout
233
+ RuntimeError: If execution fails
234
+ """
235
+ compiled_code = self.compile_code(code)
236
+ return self.execute(compiled_code, parameters, enable_timeout)
237
+
238
+
239
+ def create_safe_executor(timeout: int = 30) -> SafeExecutor:
240
+ """Factory function to create a SafeExecutor instance.
241
+
242
+ Args:
243
+ timeout: Maximum execution time in seconds
244
+
245
+ Returns:
246
+ SafeExecutor instance
247
+ """
248
+ return SafeExecutor(timeout=timeout)