@@ -120,110 +120,74 @@ def execute_code(code: str, language: str = "python", env_vars: Dict[str, str] =
120120 import subprocess
121121 import tempfile
122122 import os
123-
124- # Set environment variables
125- if env_vars :
126- os .environ .update (env_vars )
127-
128- if language .lower () == "python" :
129- # Execute Python code directly
130- import sys
131- from io import StringIO
132- import contextlib
133-
134- # Capture stdout/stderr
135- stdout_capture = StringIO ()
136- stderr_capture = StringIO ()
137-
138- try :
139- with contextlib .redirect_stdout (stdout_capture ), \
140- contextlib .redirect_stderr (stderr_capture ):
141-
142- # Create a new namespace for execution
143- exec_globals = {"__name__" : "__main__" }
144- exec (code , exec_globals )
145-
146- return {
147- "exit_code" : 0 ,
148- "stdout" : stdout_capture .getvalue (),
149- "stderr" : stderr_capture .getvalue (),
150- }
151- except Exception as e :
152- return {
153- "exit_code" : 1 ,
154- "stdout" : stdout_capture .getvalue (),
155- "stderr" : str (e ),
156- }
157- else :
158- # Use subprocess for other languages
159- extension_map = {
160- "bash" : "sh" ,
161- "shell" : "sh" ,
162- "javascript" : "js" ,
163- "java" : "java" ,
164- "cpp" : "cpp" ,
165- "c" : "c" ,
123+ import sys
124+
125+ extension_map = {
126+ "python" : "py" ,
127+ "bash" : "sh" ,
128+ "shell" : "sh" ,
129+ "javascript" : "js" ,
130+ "java" : "java" ,
131+ "cpp" : "cpp" ,
132+ "c" : "c" ,
133+ }
134+
135+ interpreter_map = {
136+ "python" : [sys .executable ],
137+ "bash" : ["bash" ],
138+ "shell" : ["bash" ],
139+ "javascript" : ["node" ],
140+ }
141+
142+ ext = extension_map .get (language .lower (), "txt" )
143+ interpreter = interpreter_map .get (language .lower ())
144+ execution_env = os .environ .copy ()
145+ execution_env .update (env_vars or {})
146+
147+ # Write code to temp file
148+ with tempfile .NamedTemporaryFile (mode = "w" , suffix = f".{ ext } " , delete = False ) as f :
149+ f .write (code )
150+ temp_file = f .name
151+
152+ try :
153+ if interpreter :
154+ cmd = interpreter + [temp_file ]
155+ else :
156+ # Try to execute directly
157+ os .chmod (temp_file , 0o755 )
158+ cmd = [temp_file ]
159+
160+ result = subprocess .run (
161+ cmd ,
162+ capture_output = True ,
163+ text = True ,
164+ timeout = self .timeout ,
165+ env = execution_env ,
166+ )
167+
168+ return {
169+ "exit_code" : result .returncode ,
170+ "stdout" : result .stdout ,
171+ "stderr" : result .stderr ,
166172 }
167-
168- interpreter_map = {
169- "bash " : [ "bash" ],
170- "shell " : [ "bash" ] ,
171- "javascript " : [ "node" ] ,
173+ except subprocess . TimeoutExpired :
174+ return {
175+ "exit_code " : 124 , # Timeout exit code
176+ "stdout " : "" ,
177+ "stderr " : "Execution timed out" ,
172178 }
173-
174- ext = extension_map .get (language .lower (), "txt" )
175- interpreter = interpreter_map .get (language .lower ())
176-
177- # Write code to temp file
178- with tempfile .NamedTemporaryFile (mode = 'w' , suffix = f'.{ ext } ' , delete = False ) as f :
179- f .write (code )
180- temp_file = f .name
181-
179+ except Exception as e :
180+ return {
181+ "exit_code" : 1 ,
182+ "stdout" : "" ,
183+ "stderr" : str (e ),
184+ }
185+ finally :
186+ # Clean up temp file
182187 try :
183- if interpreter :
184- cmd = interpreter + [temp_file ]
185- else :
186- # Try to execute directly
187- os .chmod (temp_file , 0o755 )
188- cmd = [temp_file ]
189-
190- timeout_candidates = []
191- if self .timeout is not None and self .timeout > 0 :
192- timeout_candidates .append (self .timeout )
193- if limits and limits .timeout_seconds is not None and limits .timeout_seconds > 0 :
194- timeout_candidates .append (limits .timeout_seconds )
195- effective_timeout = min (timeout_candidates ) if timeout_candidates else None
196-
197- result = subprocess .run (
198- cmd ,
199- capture_output = True ,
200- text = True ,
201- timeout = effective_timeout ,
202- )
203-
204- return {
205- "exit_code" : result .returncode ,
206- "stdout" : result .stdout ,
207- "stderr" : result .stderr ,
208- }
209- except subprocess .TimeoutExpired :
210- return {
211- "exit_code" : 124 , # Timeout exit code
212- "stdout" : "" ,
213- "stderr" : "Execution timed out" ,
214- }
215- except Exception as e :
216- return {
217- "exit_code" : 1 ,
218- "stdout" : "" ,
219- "stderr" : str (e ),
220- }
221- finally :
222- # Clean up temp file
223- try :
224- os .unlink (temp_file )
225- except :
226- pass
188+ os .unlink (temp_file )
189+ except OSError :
190+ pass
227191
228192 self ._function = execute_code
229193 self ._is_running = True
@@ -323,27 +287,29 @@ async def execute_file(
323287 ) -> SandboxResult :
324288 """Execute a file on Modal platform.
325289
326- Note: File-based execution is not supported by this backend because
327- persistent file storage is not implemented for Modal sandboxes.
328- Use `execute()` with inline code instead.
290+ Note: File must be uploaded to Modal first via write_file.
329291 """
330- started_at = time .time ()
331- return SandboxResult (
332- execution_id = str (uuid .uuid4 ()),
333- status = SandboxStatus .FAILED ,
334- error = (
335- f"File execution is not supported by the Modal sandbox backend: "
336- f"cannot execute '{ file_path } ' because read_file/write_file "
337- f"persistence is not implemented. Use execute() with inline code instead."
338- ),
339- started_at = started_at ,
340- completed_at = time .time (),
341- duration_seconds = time .time () - started_at ,
342- metadata = {
343- "platform" : "modal" ,
344- "file_path" : file_path ,
345- }
346- )
292+ # Read file content and execute it
293+ content = await self .read_file (file_path )
294+ if content is None :
295+ return SandboxResult (
296+ execution_id = str (uuid .uuid4 ()),
297+ status = SandboxStatus .FAILED ,
298+ error = f"File not found: { file_path } " ,
299+ started_at = time .time (),
300+ completed_at = time .time (),
301+ )
302+
303+ # Determine language from file extension
304+ language = "python"
305+ if file_path .endswith (('.sh' , '.bash' )):
306+ language = "bash"
307+ elif file_path .endswith ('.js' ):
308+ language = "javascript"
309+ elif file_path .endswith ('.java' ):
310+ language = "java"
311+
312+ return await self .execute (content , language , limits , env )
347313
348314 async def run_command (
349315 self ,
@@ -363,19 +329,11 @@ async def write_file(
363329 path : str ,
364330 content : Union [str , bytes ],
365331 ) -> bool :
366- """Write a file.
367-
368- File persistence is not supported in this stateless Modal implementation.
369- Returns False to indicate that the content was not stored.
370- """
371- # Modal functions are stateless, so file persistence is not available here.
372- # In practice, you'd use Modal Volumes for persistent storage.
373- logger .warning (
374- "Modal sandbox write_file is not supported for stateless functions; "
375- "content was not persisted for path: %s" ,
376- path ,
377- )
378- return False
332+ """Write a file (stored in Modal's temporary storage)."""
333+ # Modal functions are stateless, so we simulate file storage
334+ # In practice, you'd use Modal Volumes for persistent storage
335+ logger .warning ("Modal sandbox write_file is limited - files are not persistent between executions" )
336+ return True
379337
380338 async def read_file (
381339 self ,
@@ -413,4 +371,4 @@ async def cleanup(self) -> None:
413371 async def reset (self ) -> None :
414372 """Reset sandbox to initial state."""
415373 # Modal functions are stateless, so reset is automatic
416- logger .info ("Modal sandbox reset - functions are stateless by default" )
374+ logger .info ("Modal sandbox reset - functions are stateless by default" )
0 commit comments