| import gradio as gr |
| import sys |
| import io |
| import traceback |
| import ast |
| import time |
| import json |
| import os |
| from contextlib import redirect_stdout, redirect_stderr |
| from typing import Dict, Any |
| import threading |
| import signal |
|
|
| |
| def signal_handler(signum, frame): |
| print(f"Received signal {signum}, shutting down gracefully...") |
| sys.exit(0) |
|
|
| signal.signal(signal.SIGTERM, signal_handler) |
| signal.signal(signal.SIGINT, signal_handler) |
|
|
| class PythonSyntaxFSM: |
| """Finite State Machine for Python syntax validation""" |
| |
| def __init__(self): |
| self.reset() |
| |
| def reset(self): |
| self.state = 'normal' |
| self.bracket_stack = [] |
| self.in_string = False |
| self.string_delimiter = None |
| self.line_number = 0 |
| self.errors = [] |
| self.warnings = [] |
| |
| def validate_code(self, code: str) -> Dict[str, Any]: |
| """Validate Python code using FSM approach""" |
| self.reset() |
| |
| if not code or not code.strip(): |
| return { |
| 'valid': False, |
| 'errors': [{'line': 0, 'message': 'Empty code input', 'type': 'input_error'}], |
| 'warnings': [], |
| 'total_issues': 1 |
| } |
| |
| lines = code.split('\n') |
| |
| for i, line in enumerate(lines): |
| self.line_number = i + 1 |
| self.validate_line(line) |
| |
| |
| if self.bracket_stack: |
| self.errors.append({ |
| 'line': self.line_number, |
| 'type': 'syntax_error', |
| 'message': f'Unclosed bracket: {self.bracket_stack[-1]}', |
| 'severity': 'error' |
| }) |
| |
| return { |
| 'valid': len(self.errors) == 0, |
| 'errors': self.errors, |
| 'warnings': self.warnings, |
| 'total_issues': len(self.errors) + len(self.warnings) |
| } |
| |
| def validate_line(self, line: str): |
| """Validate a single line using FSM logic""" |
| if not line.strip(): |
| return |
| |
| |
| leading_spaces = len(line) - len(line.lstrip()) |
| if leading_spaces % 4 != 0 and line.strip(): |
| self.warnings.append({ |
| 'line': self.line_number, |
| 'type': 'style_warning', |
| 'message': 'Inconsistent indentation (PEP 8 recommends 4 spaces)', |
| 'severity': 'warning' |
| }) |
| |
| |
| i = 0 |
| while i < len(line): |
| char = line[i] |
| |
| |
| if self.in_string: |
| if char == self.string_delimiter and (i == 0 or line[i-1] != '\\'): |
| self.in_string = False |
| self.string_delimiter = None |
| i += 1 |
| continue |
| |
| |
| if char == '#': |
| break |
| |
| |
| if char in ['"', "'"]: |
| |
| if i + 2 < len(line) and line[i:i+3] == char * 3: |
| self.string_delimiter = char * 3 |
| i += 3 |
| else: |
| self.string_delimiter = char |
| i += 1 |
| self.in_string = True |
| continue |
| |
| |
| if char in '([{': |
| self.bracket_stack.append(char) |
| elif char in ')]}': |
| if not self.bracket_stack: |
| self.errors.append({ |
| 'line': self.line_number, |
| 'type': 'syntax_error', |
| 'message': f'Unmatched closing bracket: {char}', |
| 'severity': 'error' |
| }) |
| else: |
| expected = {'(': ')', '[': ']', '{': '}'} |
| last_open = self.bracket_stack[-1] |
| if expected[last_open] != char: |
| self.errors.append({ |
| 'line': self.line_number, |
| 'type': 'syntax_error', |
| 'message': f'Mismatched brackets: expected {expected[last_open]}, got {char}', |
| 'severity': 'error' |
| }) |
| else: |
| self.bracket_stack.pop() |
| |
| i += 1 |
| |
| |
| stripped = line.strip() |
| control_keywords = ['def ', 'class ', 'if ', 'elif ', 'for ', 'while ', 'try:', 'except'] |
| |
| for keyword in control_keywords: |
| if stripped.startswith(keyword): |
| if not stripped.endswith(':') and keyword != 'except': |
| self.errors.append({ |
| 'line': self.line_number, |
| 'type': 'syntax_error', |
| 'message': f'{keyword.strip()} statement must end with colon', |
| 'severity': 'error' |
| }) |
| break |
|
|
|
|
| class SecurePythonExecutor: |
| """Secure Python code executor with sandboxing and timeout""" |
| |
| def __init__(self, timeout: int = 10): |
| self.timeout = timeout |
| self.restricted_modules = { |
| 'os', 'sys', 'subprocess', 'socket', 'urllib', 'requests', |
| 'shutil', 'glob', 'pickle', 'marshal', 'shelve', 'dbm', |
| 'sqlite3', 'threading', 'multiprocessing', 'ctypes', 'importlib', |
| 'builtins', '__builtin__', 'imp', 'zipimport' |
| } |
| |
| |
| self.safe_builtins = { |
| 'print': print, |
| 'len': len, |
| 'range': range, |
| 'str': str, |
| 'int': int, |
| 'float': float, |
| 'bool': bool, |
| 'list': list, |
| 'dict': dict, |
| 'tuple': tuple, |
| 'set': set, |
| 'frozenset': frozenset, |
| 'abs': abs, |
| 'max': max, |
| 'min': min, |
| 'sum': sum, |
| 'sorted': sorted, |
| 'reversed': reversed, |
| 'enumerate': enumerate, |
| 'zip': zip, |
| 'map': map, |
| 'filter': filter, |
| 'all': all, |
| 'any': any, |
| 'type': type, |
| 'isinstance': isinstance, |
| 'hasattr': hasattr, |
| 'getattr': getattr, |
| 'setattr': setattr, |
| 'delattr': delattr, |
| 'round': round, |
| 'pow': pow, |
| 'divmod': divmod, |
| 'chr': chr, |
| 'ord': ord, |
| 'hex': hex, |
| 'oct': oct, |
| 'bin': bin, |
| 'format': format, |
| 'repr': repr, |
| 'ascii': ascii, |
| 'iter': iter, |
| 'next': next, |
| 'slice': slice, |
| 'callable': callable, |
| 'id': id, |
| 'hash': hash, |
| 'vars': vars, |
| 'dir': dir, |
| 'help': help, |
| 'Exception': Exception, |
| 'ValueError': ValueError, |
| 'TypeError': TypeError, |
| 'IndexError': IndexError, |
| 'KeyError': KeyError, |
| 'AttributeError': AttributeError, |
| 'NameError': NameError, |
| 'ZeroDivisionError': ZeroDivisionError, |
| } |
| |
| def execute_code(self, code: str) -> Dict[str, Any]: |
| """Execute Python code in a secure environment""" |
| start_time = time.time() |
| |
| |
| if not code or not code.strip(): |
| return { |
| 'success': False, |
| 'output': '', |
| 'error': 'No code provided', |
| 'execution_time': 0, |
| 'validation': { |
| 'valid': False, |
| 'errors': [{'message': 'Empty code input', 'line': 0}], |
| 'warnings': [], |
| 'total_issues': 1 |
| } |
| } |
| |
| |
| fsm = PythonSyntaxFSM() |
| validation_result = fsm.validate_code(code) |
| |
| |
| restricted_check = self.check_restricted_imports(code) |
| if not restricted_check['allowed']: |
| return { |
| 'success': False, |
| 'output': '', |
| 'error': f"Security violation: Restricted module '{restricted_check['module']}' is not allowed", |
| 'execution_time': 0, |
| 'validation': validation_result |
| } |
| |
| |
| result = self.run_with_timeout(code) |
| execution_time = time.time() - start_time |
| |
| return { |
| 'success': result['success'], |
| 'output': result['output'], |
| 'error': result['error'], |
| 'execution_time': round(execution_time, 3), |
| 'validation': validation_result |
| } |
| |
| def check_restricted_imports(self, code: str) -> Dict[str, Any]: |
| """Check for restricted module imports""" |
| try: |
| tree = ast.parse(code) |
| for node in ast.walk(tree): |
| if isinstance(node, ast.Import): |
| for alias in node.names: |
| if alias.name.split('.')[0] in self.restricted_modules: |
| return {'allowed': False, 'module': alias.name} |
| elif isinstance(node, ast.ImportFrom): |
| if node.module and node.module.split('.')[0] in self.restricted_modules: |
| return {'allowed': False, 'module': node.module} |
| return {'allowed': True, 'module': None} |
| except Exception: |
| return {'allowed': True, 'module': None} |
| |
| def run_with_timeout(self, code: str) -> Dict[str, Any]: |
| """Run code with timeout protection""" |
| output_buffer = io.StringIO() |
| error_buffer = io.StringIO() |
| result = {'success': False, 'output': '', 'error': ''} |
| |
| def target(): |
| try: |
| with redirect_stdout(output_buffer), redirect_stderr(error_buffer): |
| |
| restricted_globals = { |
| '__builtins__': self.safe_builtins, |
| '__name__': '__main__', |
| '__doc__': None, |
| } |
| |
| |
| exec(code, restricted_globals) |
| result['success'] = True |
| |
| except Exception as e: |
| error_msg = f"{type(e).__name__}: {str(e)}" |
| error_buffer.write(error_msg) |
| result['success'] = False |
| |
| |
| thread = threading.Thread(target=target) |
| thread.daemon = True |
| thread.start() |
| thread.join(timeout=self.timeout) |
| |
| if thread.is_alive(): |
| result['error'] = f"Code execution timed out after {self.timeout} seconds" |
| result['success'] = False |
| else: |
| result['output'] = output_buffer.getvalue() |
| if error_buffer.getvalue(): |
| result['error'] = error_buffer.getvalue() |
| result['success'] = False |
| else: |
| result['success'] = True |
| |
| return result |
|
|
|
|
| |
| executor = SecurePythonExecutor(timeout=10) |
|
|
| def execute_python_code_api(code: str) -> Dict[str, Any]: |
| """ |
| API function to execute Python code. |
| This function is directly exposed as an API endpoint. |
| """ |
| try: |
| result = executor.execute_code(code) |
| return result |
| except Exception as e: |
| return { |
| 'success': False, |
| 'output': '', |
| 'error': f"Execution engine error: {str(e)}", |
| 'execution_time': 0, |
| 'validation': { |
| 'valid': False, |
| 'errors': [{'message': str(e), 'line': 0, 'type': 'engine_error'}], |
| 'warnings': [], |
| 'total_issues': 1 |
| } |
| } |
|
|
| def health_check_api() -> Dict[str, Any]: |
| """ |
| Health check API endpoint. |
| """ |
| try: |
| test_result = execute_python_code_api("print('Health check OK')") |
| return { |
| 'status': 'healthy' if test_result['success'] else 'unhealthy', |
| 'timestamp': time.time(), |
| 'test_execution_success': test_result['success'] |
| } |
| except Exception as e: |
| return { |
| 'status': 'unhealthy', |
| 'timestamp': time.time(), |
| 'error': str(e), |
| 'test_execution_success': False |
| } |
|
|
| |
| |
| |
|
|
| |
| api_code_executor = gr.Interface( |
| fn=execute_python_code_api, |
| inputs=[ |
| gr.Code(label="Python Code", language="python", lines=10, value="print('Hello from API!')") |
| ], |
| outputs=[ |
| gr.JSON(label="Execution Result") |
| ], |
| |
| api_name="predict", |
| title="Python Code Executor API (Main)", |
| description="Submits Python code for secure execution and returns the result." |
| ) |
|
|
| |
| api_health_check = gr.Interface( |
| fn=health_check_api, |
| inputs=[], |
| outputs=[ |
| gr.JSON(label="Health Status") |
| ], |
| |
| api_name="health", |
| title="Service Health Check", |
| description="Checks the health and basic functionality of the API service." |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| print("Starting Python Code Executor API...") |
| print(f"Python version: {sys.version}") |
| print(f"Working directory: {os.getcwd()}") |
| |
| server_name = os.getenv('GRADIO_SERVER_NAME', '0.0.0.0') |
| server_port = int(os.getenv('GRADIO_SERVER_PORT', 7860)) |
| |
| print(f"Starting server on {server_name}:{server_port}") |
| |
| try: |
| |
| |
| |
| api_code_executor.launch( |
| server_name=server_name, |
| server_port=server_port, |
| share=False, |
| show_error=True, |
| quiet=False, |
| |
| ) |
| |
| |
| |
| |
| except Exception as e: |
| print(f"Failed to start server: {e}") |