Migration Guide: Sandbox Filesystem APIs
We have introduced new APIs for interacting with the contents of a Sandbox’s filesystem. The new APIs are backed by a new implementation that is more reliable and performant.
We strongly encourage migrating all usage to the new APIs. The legacy APIs are deprecated, and support will be discontinued on an accelerated timeline.
Overview
The legacy APIs are exposed via a stateful, descriptor-oriented API. Calling sb.open(path) returns a file-like object with methods for reading or writing chunks of data. Additionally, basic operations like creating directories, listing directory contents, and removing files are exposed via methods on the Sandbox instance itself.
The new APIs are exposed via methods in the filesystem namespace on a Sandbox instance. Reads and writes are now path-oriented, operating on an entire file with a single method call. Basic operations continue to be supported with more verbose method names.
Python SDK
The new APIs were added between versions 1.4.0 and 1.5.0, with performance optimizations made in 1.5.3. The legacy API will be removed in 1.6.0.
Replace all sb.open() calls and interactions on the returned FileIO object with calls to one of the following methods:
sb.filesystem.copy_from_local(local_path, remote_path)sb.filesystem.copy_to_local(remote_path, local_path)sb.filesystem.read_bytes(remote_path)sb.filesystem.read_text(remote_path)sb.filesystem.write_bytes(data, remote_path)sb.filesystem.write_text(data, remote_path)
Examples:
Write data into the Sandbox:
# Deprecated usage
with sb.open("test.txt", "w") as f:
f.write("Hello World!\n")
# Supported usage
sb.filesystem.write_text("Hello world!\n", "/test.txt")Consider using objects from Python’s io module to migrate code that makes deeper use of the FileIO interface:
# Deprecated usage
with sb.open(path) as f:
for line in f.readlines():
...
# Supported usage
data = io.StringIO(sb.filesystem.read_text(path))
for line in data.readlines():
...Notes:
- Unlike the stateful legacy
FileIOmethods, each call to one of the new methods performs a complete operation. - Writing to a path in a nonexistent directory will create that directory and any parents.
- Writing to a path with an existing file will overwrite its data. There is no equivalent method for writing to a file that was opened in append mode. Consider a read-append-write operation for small files, or write chunks as separate files and combine via
sb.exec(). - There is also no exclusive (
x) mode; check existence withsb.filesystem.stat(path)and catchmodal.exception.SandboxFilesystemNotFoundError
For migrating basic file operations, use the following name substitutions:
sb.mkdir(path)->sb.filesystem.make_directory(path)sb.rm(path)->sb.filesystem.remove(path)sb.ls(path)->sb.filesystem.list_files(path)
Notes:
- The new
sb.filesystem.make_directory(path)hascreate_parents=Trueby default and is idempotent;sb.mkdirhasparents=Falseby default and errors if the target exists. - The new
sb.filesystem.remove(dir_path)will removedir_pathif the directory is empty;sb.rm(dir_path)would error in this case. - The new
sb.filesystem.remove(path, recursive=True)will raiseInvalidErrorifpathis on a mount type that does not support recursive removals. - Both
recursiveandcreate_parentsmust be passed as keyword arguments in the new APIs.
For listing files, note that the new API returns a list of FileInfo dataclasses rather than a list of file names:
# Deprecated usage
for file_name in sb.ls(path):
print(file_name)
# Supported usage
for file_info in sb.filesystem.list_files(path):
print(file_info.name)For migrating file watching, substitute sb.watch(path) -> sb.filesystem.watch(path). The filter, recursive, and timeout parameters have the same semantics, but they must be passed as keyword arguments. If code that processes the events references the FileWatchEventType enum, update its import path to modal.types, as modal.file_io will be removed.
General notes:
The legacy APIs accept relative remote path arguments with undefined semantics. The new APIs consistently require remote_path values to be absolute, rejecting relative paths with modal.exception.InvalidError.
The legacy APIs raise most remote exceptions with built-in types (FileNotFoundError, NotADirectoryError, etc.), falling back to modal.exception.FilesystemExecutionError on undefined error codes. For remote errors, the new APIs raise Modal exceptions with a fine-grained type system inheriting from modal.exception.SandboxFilesystemError. These exceptions do not inherit the builtin types, so exception handling code must be migrated. Local exceptions still use built-in types. See method docstrings for details on specific error cases.
See more details in the reference documentation: https://modal.com/docs/sdk/py/latest/Sandbox
JS SDK
The new APIs were added in version 0.7.6; legacy APIs were removed in 0.8.0.
Legacy filesystem operations were limited to sandbox.open() and the SandboxFile object it returned. Replace all such calls with one of the following methods:
sb.filesystem.copyFromLocal(localPath, remotePath)sb.filesystem.copyToLocal(remotePath, localPath)sb.filesystem.readBytes(remotePath)sb.filesystem.readText(remotePath)sb.filesystem.writeBytes(data, remotePath)sb.filesystem.writeText(data, remotePath)
Note that the new APIs require remotePath to be absolute, rejecting relative paths with InvalidError.
Examples:
Write data into the Sandbox:
// Deprecated usage
const handle = await sb.open("/tmp/test.txt", "w");
await handle.write(new TextEncoder().encode("Hello World!\n"));
await handle.close();
// Supported usage
await sb.filesystem.writeText("Hello World!\n", "/tmp/test.txt");Read data out of the Sandbox:
// Deprecated usage
const handle = await sb.open("/tmp/test.txt", "r");
const contents = new TextDecoder().decode(await handle.read());
await handle.close();
// Supported usage
const contents = await sb.filesystem.readText("/tmp/test.txt");Notes:
- Each call to the new methods performs a complete operation, so there are no handles to
close()and noflush()step. - Writing to a path in a nonexistent directory will create that directory and any parents.
- Writing to a path with an existing file will overwrite its data. There is no equivalent method for writing to a file in append mode. Consider a read-append-write operation for small files, or write chunks as separate files and combinine via
sb.exec(). - The new
readTextandwriteTextmethods handle UTF-8 encoding, replacing manualTextDecoderandTextEncoderuse. - The
writeBytesmethod acceptsUint8Array,ArrayBuffer, orBuffer;SandboxFile.writerequired aUint8Array.
Exception handling:
The legacy API threw SandboxFilesystemError for every remote filesystem error. The new APIs throw fine-grained subclasses of SandboxFilesystemError, or InvalidError in some cases; all of them are exported from the package root. Unlike in the Python SDK, code that matches on instanceof SandboxFilesystemError continues to work, but code that matches on specific error messages must be migrated.
The remaining methods in the filesystem namespace — listFiles, makeDirectory, remove, stat, and watch — have no counterpart in the legacy JS API, so there is nothing to migrate for those. Note that watch requires version 0.9.0 or later.
See more details in the reference documentation: https://modal.com/docs/sdk/js/latest/Sandbox