Skip to main content
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:
FieldTypeDescription
namestr | NoneFilename (not the full path).
pathstr | NoneFull absolute path.
typestr | None"file", "directory", or "symlink".
sizeint | NoneFile size in bytes (None for directories).
permissionsstr | NoneHuman-readable permissions string (e.g. "-rwxr-xr-x").
ownerstr | NoneOwner username.
modified_atint | NoneLast 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)