@@ -77,59 +77,22 @@ def copy_dir_to_sandbox(sandbox, local_dir: str, remote_dir: str) -> None:
7777
7878
7979def copy_from_sandbox (sandbox , remote_path : str , local_path : str ) -> None :
80- """Copy a file or directory from the sandbox to local filesystem using tar ."""
80+ """Copy a file from the sandbox to local filesystem."""
8181 logger .info ("Downloading %s to %s..." , remote_path , local_path )
8282
83- # Use tar on the sandbox to create an archive of the remote path
84- # This handles both files and directories uniformly
85- tar_remote_path = "/tmp/.download_transfer.tar"
83+ # Read file content directly from sandbox
84+ with sandbox . open ( remote_path , "rb" ) as f :
85+ data = f . read ()
8686
87- # Create tar archive on sandbox
88- # Use -C to change to parent directory and archive just the basename
89- # This preserves the directory structure correctly
90- remote_parent = os .path .dirname (remote_path .rstrip ("/" )) or "/"
91- remote_basename = os .path .basename (remote_path .rstrip ("/" ))
92-
93- logger .info ("Creating tar archive on sandbox..." )
94- process = sandbox .exec (
95- "tar" , "-cf" , tar_remote_path , "-C" , remote_parent , remote_basename
96- )
97- process .wait ()
98- if process .returncode != 0 :
99- stderr = process .stderr .read ()
100- raise click .ClickException (f"Failed to create tar archive on sandbox: { stderr } " )
101-
102- # Read the tar archive from sandbox
103- logger .info ("Transferring tar archive from sandbox..." )
104- with sandbox .open (tar_remote_path , "rb" ) as f :
105- tar_data = f .read ()
106-
107- logger .info ("Received tar archive (%d bytes)" , len (tar_data ))
108-
109- # Clean up tar file on sandbox
110- sandbox .exec ("rm" , "-f" , tar_remote_path ).wait ()
111-
112- # Extract tar archive locally
113- tar_buffer = io .BytesIO (tar_data )
87+ logger .info ("Received %d bytes" , len (data ))
11488
11589 # Create parent directory if needed
11690 local_parent = os .path .dirname (local_path .rstrip ("/" )) or "."
11791 os .makedirs (local_parent , exist_ok = True )
11892
119- logger .info ("Extracting tar archive to %s..." , local_parent )
120- with tarfile .open (fileobj = tar_buffer , mode = "r" ) as tar :
121- tar .extractall (path = local_parent )
122-
123- # If local_path differs from the extracted name, rename
124- extracted_path = os .path .join (local_parent , remote_basename )
125- if extracted_path != local_path and os .path .exists (extracted_path ):
126- if os .path .exists (local_path ):
127- # Remove existing target to allow rename
128- if os .path .isdir (local_path ):
129- shutil .rmtree (local_path )
130- else :
131- os .remove (local_path )
132- os .rename (extracted_path , local_path )
93+ # Write to local file
94+ with open (local_path , "wb" ) as f :
95+ f .write (data )
13396
13497 logger .info ("Download complete: %s -> %s" , remote_path , local_path )
13598
@@ -353,12 +316,11 @@ def download(sandbox_id: str, paths: tuple[str, ...]):
353316 SANDBOX_ID is the Modal sandbox ID to download from.
354317
355318 PATHS are one or more path specifications in the format "remote_path:local_path".
356- Each specification downloads the remote path to the local path.
357- Both files and directories are supported.
319+ Each specification downloads the remote file to the local path.
358320
359321 Examples:
360322
361- modal_sandbox.py download sb-abc123 "/app/results:/tmp/ results"
323+ modal_sandbox.py download sb-abc123 "/tmp/junit.xml:./ results/junit.xml "
362324
363325 modal_sandbox.py download sb-abc123 "/app/out:./out" "/app/logs:./logs"
364326 """
0 commit comments