The capsule.files interface gives you direct access to the capsule’s filesystem. You can write source files before running them, read output files after a command finishes, inspect directory trees, and stream large files in and out without buffering them in memory.
Writing files
write() accepts either a string or bytes. When you pass a string, it is UTF-8 encoded automatically. Parent directories are created if they do not exist.
# Write a text file
capsule.files.write("/app/main.py", "print('hello from Wrenn')")
# Write bytes
capsule.files.write("/app/data.bin", b"\x00\x01\x02\x03")
Reading files
read() returns the file as a UTF-8 string. read_bytes() returns the raw bytes.
content = capsule.files.read("/app/main.py") # str
raw = capsule.files.read_bytes("/app/data.bin") # bytes
Checking existence
if capsule.files.exists("/app/main.py"):
print("file is there")
Listing directory contents
list() returns a list of FileEntry objects representing the immediate children of a directory. Pass depth to recurse further.
entries = capsule.files.list("/home/user", depth=1)
for entry in entries:
print(entry.name, entry.type, entry.size)
Each FileEntry has:
| Field | Type | Description |
|---|
name | str | None | Filename (not the full path). |
path | str | None | Full absolute path. |
type | str | None | "file", "directory", or "symlink". |
size | int | None | File size in bytes (None for directories). |
permissions | str | None | Human-readable permissions string (e.g. "-rwxr-xr-x"). |
owner | str | None | Owner username. |
modified_at | int | None | Last modified time as a Unix timestamp. |
Creating directories
make_dir() creates a directory and all required parent directories. It is idempotent — calling it on an existing path does not raise an error.
capsule.files.make_dir("/app/data/results")
Removing files and directories
remove() deletes a file or directory recursively.
capsule.files.remove("/app/old_data")
remove() is recursive. Passing a directory path deletes the directory and everything inside it.
Streaming large files
For files larger than a few megabytes, use the streaming methods to avoid loading the entire file into memory.
Streaming upload
upload_stream() accepts any iterator that yields bytes chunks:
def read_chunks(path: str, chunk_size: int = 65536):
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
yield chunk
capsule.files.upload_stream("/data/model.bin", read_chunks("/local/model.bin"))
Streaming download
download_stream() yields successive byte chunks:
with open("/local/output.bin", "wb") as f:
for chunk in capsule.files.download_stream("/data/output.bin"):
f.write(chunk)
Use upload_stream and download_stream for any file over a few MB. Buffering large files in memory can exhaust the host process’s heap and cause requests to time out.
Complete example
from wrenn import Capsule
with Capsule(template="minimal", wait=True) as capsule:
# Set up project structure
capsule.files.make_dir("/app/data")
# Write a script
capsule.files.write("/app/process.py", """
import json, pathlib
data = [{"x": i, "y": i ** 2} for i in range(100)]
pathlib.Path("/app/data/output.json").write_text(json.dumps(data))
print("done")
""")
# Run it
capsule.commands.run("python /app/process.py")
# Check what was produced
entries = capsule.files.list("/app/data", depth=1)
for e in entries:
print(e.name, e.size) # output.json <size>
# Read it back
content = capsule.files.read("/app/data/output.json")
print(content[:80])
# Stream a large result file back to disk
with open("/tmp/output.json", "wb") as f:
for chunk in capsule.files.download_stream("/app/data/output.json"):
f.write(chunk)