1+ """
2+ CodeRunner - Local code execution without API keys or cloud setup
3+
4+ Zero-configuration local code execution with seamless cloud migration.
5+
6+ Basic Usage:
7+ from coderunner import CodeRunner
8+
9+ runner = CodeRunner() # Auto-starts container
10+ result = runner.execute("print('Hello World!')")
11+ print(result['stdout']) # "Hello World!"
12+
13+ Cloud Migration:
14+ from coderunner.cloud import InstaVM as CodeRunner
15+
16+ runner = CodeRunner(api_key="your-key") # Same interface!
17+ result = runner.execute("print('Hello Cloud!')")
18+ """
19+
20+ import requests
21+ import logging
22+ from typing import Dict , Optional , Any
23+
24+ from .container_manager import ContainerManager
25+ from .core .exceptions import CodeRunnerError , SessionError , ExecutionError
26+
27+ __version__ = "1.0.0"
28+
29+ logger = logging .getLogger (__name__ )
30+
31+
32+ class CodeRunner :
33+ """Local code execution without API keys or cloud setup"""
34+
35+ def __init__ (self , auto_start : bool = True , base_url : str = None ):
36+ """
37+ Initialize CodeRunner client
38+
39+ Args:
40+ auto_start: Automatically start container if not running
41+ base_url: Custom REST API base URL (for testing)
42+ """
43+ self .base_url = base_url or "http://localhost:8223"
44+ self .session_id : Optional [str ] = None
45+ self ._session = requests .Session ()
46+
47+ # Set reasonable timeouts
48+ self ._session .timeout = 30
49+
50+ if auto_start :
51+ try :
52+ ContainerManager .ensure_running ()
53+ except Exception as e :
54+ raise CodeRunnerError (f"Failed to start CodeRunner: { e } " )
55+
56+ def execute (self , code : str , language : str = "python" , timeout : int = 30 ) -> Dict [str , Any ]:
57+ """
58+ Execute code and return results (InstaVM compatible interface)
59+
60+ Args:
61+ code: Code to execute
62+ language: Programming language ("python", "bash", "shell", "javascript")
63+ timeout: Execution timeout in seconds
64+
65+ Returns:
66+ Dictionary with stdout, stderr, execution_time, etc.
67+
68+ Raises:
69+ ExecutionError: If execution fails
70+ CodeRunnerError: If container communication fails
71+ """
72+ if not self .session_id :
73+ self .session_id = self ._create_session ()
74+
75+ try :
76+ response = self ._session .post (
77+ f"{ self .base_url } /execute" ,
78+ json = {
79+ "command" : code ,
80+ "language" : language ,
81+ "timeout" : timeout ,
82+ "session_id" : self .session_id
83+ },
84+ timeout = timeout + 10
85+ )
86+
87+ if response .status_code != 200 :
88+ error_detail = "Unknown error"
89+ try :
90+ error_data = response .json ()
91+ error_detail = error_data .get ("detail" , error_detail )
92+ except :
93+ error_detail = response .text
94+
95+ raise ExecutionError (f"Execution failed: { error_detail } " )
96+
97+ return response .json ()
98+
99+ except requests .exceptions .Timeout :
100+ raise ExecutionError (f"Execution timed out after { timeout } seconds" )
101+ except requests .exceptions .ConnectionError :
102+ raise CodeRunnerError (
103+ "Cannot connect to CodeRunner. Is the container running? "
104+ "Try CodeRunner().start_container()"
105+ )
106+ except ExecutionError :
107+ raise
108+ except Exception as e :
109+ raise CodeRunnerError (f"Unexpected error: { e } " )
110+
111+ def execute_async (self , code : str , language : str = "python" , timeout : int = 30 ) -> str :
112+ """
113+ Execute code asynchronously, return task_id (InstaVM compatible)
114+
115+ Args:
116+ code: Code to execute
117+ language: Programming language
118+ timeout: Execution timeout in seconds
119+
120+ Returns:
121+ task_id string for checking execution status
122+
123+ Raises:
124+ ExecutionError: If async execution setup fails
125+ """
126+ if not self .session_id :
127+ self .session_id = self ._create_session ()
128+
129+ try :
130+ response = self ._session .post (
131+ f"{ self .base_url } /execute_async" ,
132+ json = {
133+ "command" : code ,
134+ "language" : language ,
135+ "timeout" : timeout ,
136+ "session_id" : self .session_id
137+ }
138+ )
139+
140+ if response .status_code != 200 :
141+ error_detail = "Unknown error"
142+ try :
143+ error_data = response .json ()
144+ error_detail = error_data .get ("detail" , error_detail )
145+ except :
146+ error_detail = response .text
147+
148+ raise ExecutionError (f"Async execution failed: { error_detail } " )
149+
150+ result = response .json ()
151+ return result ["task_id" ]
152+
153+ except requests .exceptions .ConnectionError :
154+ raise CodeRunnerError ("Cannot connect to CodeRunner" )
155+ except ExecutionError :
156+ raise
157+ except Exception as e :
158+ raise CodeRunnerError (f"Unexpected error: { e } " )
159+
160+ def get_task_status (self , task_id : str ) -> Dict [str , Any ]:
161+ """
162+ Get status of async task (InstaVM compatible)
163+
164+ Args:
165+ task_id: Task identifier from execute_async
166+
167+ Returns:
168+ Task status dictionary
169+ """
170+ try :
171+ response = self ._session .get (f"{ self .base_url } /tasks/{ task_id } " )
172+
173+ if response .status_code == 404 :
174+ raise ExecutionError ("Task not found" )
175+ elif response .status_code != 200 :
176+ raise ExecutionError (f"Failed to get task status: { response .text } " )
177+
178+ return response .json ()
179+
180+ except requests .exceptions .ConnectionError :
181+ raise CodeRunnerError ("Cannot connect to CodeRunner" )
182+ except ExecutionError :
183+ raise
184+ except Exception as e :
185+ raise CodeRunnerError (f"Unexpected error: { e } " )
186+
187+ def start_session (self ) -> str :
188+ """
189+ Start a new execution session (InstaVM compatible)
190+
191+ Returns:
192+ session_id string
193+
194+ Raises:
195+ SessionError: If session creation fails
196+ """
197+ self .session_id = self ._create_session ()
198+ return self .session_id
199+
200+ def close_session (self ):
201+ """Close current session (InstaVM compatible)"""
202+ if self .session_id :
203+ try :
204+ self ._session .delete (f"{ self .base_url } /sessions/{ self .session_id } " )
205+ except Exception as e :
206+ logger .warning (f"Error closing session: { e } " )
207+ finally :
208+ self .session_id = None
209+
210+ def is_session_active (self ) -> bool :
211+ """
212+ Check if current session is active (InstaVM compatible)
213+
214+ Returns:
215+ True if session is active, False otherwise
216+ """
217+ if not self .session_id :
218+ return False
219+
220+ try :
221+ response = self ._session .get (f"{ self .base_url } /sessions/{ self .session_id } " )
222+ return response .status_code == 200
223+ except :
224+ return False
225+
226+ def list_sessions (self ) -> Dict [str , Any ]:
227+ """
228+ List all active sessions
229+
230+ Returns:
231+ Dictionary with session information
232+ """
233+ try :
234+ response = self ._session .get (f"{ self .base_url } /sessions" )
235+
236+ if response .status_code != 200 :
237+ raise CodeRunnerError (f"Failed to list sessions: { response .text } " )
238+
239+ return response .json ()
240+
241+ except requests .exceptions .ConnectionError :
242+ raise CodeRunnerError ("Cannot connect to CodeRunner" )
243+ except Exception as e :
244+ raise CodeRunnerError (f"Unexpected error: { e } " )
245+
246+ def get_health (self ) -> Dict [str , Any ]:
247+ """
248+ Get CodeRunner health status
249+
250+ Returns:
251+ Health status dictionary
252+ """
253+ try :
254+ response = self ._session .get (f"{ self .base_url } /health" )
255+
256+ if response .status_code != 200 :
257+ raise CodeRunnerError (f"Health check failed: { response .text } " )
258+
259+ return response .json ()
260+
261+ except requests .exceptions .ConnectionError :
262+ raise CodeRunnerError ("Cannot connect to CodeRunner" )
263+ except Exception as e :
264+ raise CodeRunnerError (f"Unexpected error: { e } " )
265+
266+ def get_supported_languages (self ) -> Dict [str , Any ]:
267+ """
268+ Get list of supported programming languages
269+
270+ Returns:
271+ Dictionary with supported languages
272+ """
273+ try :
274+ response = self ._session .get (f"{ self .base_url } /languages" )
275+
276+ if response .status_code != 200 :
277+ raise CodeRunnerError (f"Failed to get languages: { response .text } " )
278+
279+ return response .json ()
280+
281+ except requests .exceptions .ConnectionError :
282+ raise CodeRunnerError ("Cannot connect to CodeRunner" )
283+ except Exception as e :
284+ raise CodeRunnerError (f"Unexpected error: { e } " )
285+
286+ def _create_session (self ) -> str :
287+ """
288+ Internal method to create new session
289+
290+ Returns:
291+ Session ID string
292+
293+ Raises:
294+ SessionError: If session creation fails
295+ """
296+ try :
297+ response = self ._session .post (f"{ self .base_url } /sessions" )
298+
299+ if response .status_code != 200 :
300+ error_detail = "Unknown error"
301+ try :
302+ error_data = response .json ()
303+ error_detail = error_data .get ("detail" , error_detail )
304+ except :
305+ error_detail = response .text
306+
307+ raise SessionError (f"Failed to create session: { error_detail } " )
308+
309+ result = response .json ()
310+ return result ["session_id" ]
311+
312+ except requests .exceptions .ConnectionError :
313+ raise CodeRunnerError (
314+ "Cannot connect to CodeRunner. Is the container running? "
315+ "Try CodeRunner().start_container()"
316+ )
317+ except SessionError :
318+ raise
319+ except Exception as e :
320+ raise SessionError (f"Unexpected error creating session: { e } " )
321+
322+ # Container management convenience methods
323+
324+ def start_container (self ) -> bool :
325+ """Start CodeRunner container manually"""
326+ return ContainerManager .ensure_running ()
327+
328+ def stop_container (self ) -> bool :
329+ """Stop CodeRunner container"""
330+ return ContainerManager .stop_container ()
331+
332+ def get_container_status (self ) -> Dict [str , Any ]:
333+ """Get container status information"""
334+ return ContainerManager .get_container_status ()
335+
336+ # Context manager support (InstaVM compatible)
337+ def __enter__ (self ):
338+ return self
339+
340+ def __exit__ (self , exc_type , exc_val , exc_tb ):
341+ self .close_session ()
342+
343+
344+ # Import cloud migration support
345+ try :
346+ from . import cloud
347+ InstaVM = cloud .InstaVM
348+ except ImportError :
349+ # Create placeholder for better error messages
350+ class InstaVM :
351+ def __init__ (self , * args , ** kwargs ):
352+ raise ImportError (
353+ "Cloud provider not available. Install with:\n "
354+ " pip install coderunner[cloud]\n \n "
355+ "Or use local execution:\n "
356+ " from coderunner import CodeRunner"
357+ )
358+
359+ __all__ = ["CodeRunner" , "InstaVM" ]
0 commit comments