| """ |
| Python Executor - Persistent Python execution environment. |
| Similar to a Jupyter kernel: maintains state and tool namespace across calls. |
| """ |
|
|
| import io |
| import sys |
| from typing import Any, Dict |
|
|
| __all__ = ["PythonExecutor"] |
|
|
| |
| |
| |
| MAX_OUTPUT_CHARS = 4000 |
|
|
|
|
| class PythonExecutor: |
| """ |
| Persistent Python execution environment. |
| Maintains namespace (variables, functions) across multiple execute() calls. |
| """ |
|
|
| def __init__(self): |
| self.namespace = {} |
| self.reset_environment() |
|
|
| def reset_environment(self): |
| """Reset the execution environment to a clean state.""" |
| self.namespace = {"__builtins__": __builtins__} |
|
|
| try: |
| import pandas as pd |
| import numpy as np |
| import os |
| from pathlib import Path |
| self.namespace.update({"pd": pd, "np": np, "os": os, "Path": Path}) |
|
|
| def write_text_file(filepath, content): |
| """Write text content to a file.""" |
| with open(filepath, 'w') as f: |
| f.write(content) |
| return f"File written to {filepath}" |
|
|
| def write_dataframe_to_csv(df, filepath, **kwargs): |
| """Write pandas DataFrame to CSV file.""" |
| df.to_csv(filepath, **kwargs) |
| return f"DataFrame written to {filepath}" |
|
|
| def write_dataframe_to_tsv(df, filepath, **kwargs): |
| """Write pandas DataFrame to TSV file.""" |
| df.to_csv(filepath, sep='\t', **kwargs) |
| return f"DataFrame written to {filepath}" |
|
|
| def create_directory(dirpath): |
| """Create directory if it doesn't exist.""" |
| Path(dirpath).mkdir(parents=True, exist_ok=True) |
| return f"Directory created: {dirpath}" |
|
|
| self.namespace.update({ |
| "write_text_file": write_text_file, |
| "write_dataframe_to_csv": write_dataframe_to_csv, |
| "write_dataframe_to_tsv": write_dataframe_to_tsv, |
| "create_directory": create_directory, |
| }) |
|
|
| except ImportError as e: |
| print(f"Warning: Could not import library: {e}") |
|
|
| def send_functions(self, functions: Dict[str, Any]): |
| """Inject functions into the execution namespace.""" |
| self.namespace.update(functions) |
|
|
| def send_variables(self, variables: Dict[str, Any]): |
| """Inject variables into the execution namespace.""" |
| if variables: |
| self.namespace.update(variables) |
|
|
| def __call__(self, code: str) -> Any: |
| return self.execute(code) |
|
|
| def execute(self, code: str) -> Any: |
| """Execute Python code in the persistent namespace, returning stdout output.""" |
| try: |
| old_stdout = sys.stdout |
| sys.stdout = captured_output = io.StringIO() |
| try: |
| |
| |
| |
| exec(code, self.namespace) |
| result = captured_output.getvalue().strip() |
| if not result: |
| return "Code executed successfully" |
| if len(result) > MAX_OUTPUT_CHARS: |
| omitted = len(result) - MAX_OUTPUT_CHARS |
| result = ( |
| result[:MAX_OUTPUT_CHARS] |
| + f"\n...[output truncated, {omitted} more characters omitted. " |
| f"Print a smaller summary (e.g. .head(), .describe(), or specific columns) " |
| f"if you need to inspect this further.]" |
| ) |
| return result |
| finally: |
| sys.stdout = old_stdout |
| except Exception as e: |
| return f"Error: {str(e)}" |
|
|