> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Filesystem

> Read, write, list, rename, remove, and watch files inside a Declaw sandbox using sbx.files.

```python theme={null}
from declaw import Sandbox, EntryInfo, WriteInfo, WriteEntry, FileType, FilesystemEvent
```

`sbx.files` is the `Filesystem` sub-module available on every `Sandbox` instance. All paths must be absolute paths within the sandbox filesystem.

## `sbx.files.read()`

Read a file's content from the sandbox.

```python theme={null}
# Read as text (default)
content: str = sbx.files.read("/home/user/script.py")

# Read as bytes
raw: bytearray = sbx.files.read("/data/image.png", format="bytes")

# Read as a streaming iterator
stream = sbx.files.read("/data/large_file.bin", format="stream")
for chunk in stream:
    process(chunk)
```

<ParamField body="path" type="str" required>
  Absolute path inside the sandbox.
</ParamField>

<ParamField body="format" type="str" default="'text'">
  Output format. One of `"text"` (returns `str`), `"bytes"` (returns
  `bytearray`), or `"stream"` (returns `Iterator[bytes]`).
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context for the read operation.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `str | bytearray | Iterator[bytes]`

***

## `sbx.files.write()`

Write content to a file. Creates parent directories automatically.

```python theme={null}
info: WriteInfo = sbx.files.write(
    "/home/user/hello.py",
    "print('hello from sandbox')",
)
print(info.path, info.size)
```

### Binary writes

Pass `bytes` (or any file-like object containing binary data) and the SDK
routes the payload to the binary-safe `PUT /files/raw` endpoint
automatically — no manual base64 encoding required.

```python theme={null}
import base64, os

# Raw random bytes
sbx.files.write("/tmp/blob.bin", os.urandom(4096))

# PNG or any other binary format
with open("image.png", "rb") as f:
    sbx.files.write("/home/user/image.png", f.read())

# Base64-decoded payload (common LLM tool-use pattern)
decoded = base64.b64decode(some_b64_string)
sbx.files.write("/tmp/artifact.bin", decoded)

# Round-trip verification
got = sbx.files.read("/tmp/blob.bin", format="bytes")
assert bytes(got) == os.urandom  # byte-identical
```

`bytes` payloads are capped at 500 MiB per request. For larger uploads, use
[`sbx.upload_url()`](/sdks/python/sandbox#upload_url) to get a streaming URL.

<ParamField body="path" type="str" required>
  Absolute path inside the sandbox. Parent directories are created if they do
  not exist.
</ParamField>

<ParamField body="data" type="str | bytes | IO" required>
  Content to write. `str` is sent via the JSON `POST /files` endpoint (10 MiB
  cap). `bytes` or a file-like object is streamed to `PUT /files/raw`
  (500 MiB cap). The SDK dispatches based on payload type — callers do not
  need to pick the transport.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `WriteInfo`

***

## `sbx.files.write_files()`

Write multiple files in a single batch request. More efficient than calling
`write()` in a loop.

```python theme={null}
from declaw import WriteEntry
import os

results = sbx.files.write_files([
    WriteEntry(path="/home/user/main.py", data="import sys\nprint(sys.argv)"),
    WriteEntry(path="/home/user/data.json", data='{"key": "value"}'),
    WriteEntry(path="/home/user/blob.bin", data=os.urandom(1024)),  # bytes OK
])
```

`data` may be `str` or `bytes`. The SDK partitions entries internally —
string entries go through the JSON batch endpoint in a single request, bytes
entries are streamed individually to `PUT /files/raw` — and returns results
in the original input order.

<ParamField body="files" type="list[WriteEntry]" required>
  List of `WriteEntry` objects. Each has `path` (str) and `data` (str or
  bytes).
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context applied to all files.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `list[WriteInfo]`

***

## `sbx.files.list()`

List the contents of a directory.

```python theme={null}
entries: list[EntryInfo] = sbx.files.list("/home/user", depth=2)
for e in entries:
    print(e.type.value, e.path, e.size)
```

<ParamField body="path" type="str" required>
  Absolute path to the directory.
</ParamField>

<ParamField body="depth" type="int | None" default="1">
  Recursion depth. `1` lists only the immediate children of the directory.
  `None` or a larger value recurses deeper.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `list[EntryInfo]`

***

## `sbx.files.exists()`

Check whether a file or directory exists.

```python theme={null}
if sbx.files.exists("/home/user/output.csv"):
    content = sbx.files.read("/home/user/output.csv")
```

<ParamField body="path" type="str" required>
  Absolute path to check.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `bool`

***

## `sbx.files.get_info()`

Get metadata about a single file or directory entry.

```python theme={null}
info: EntryInfo = sbx.files.get_info("/home/user/script.py")
print(info.name, info.type, info.size)
```

<ParamField body="path" type="str" required>
  Absolute path to query.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `EntryInfo`

***

## `sbx.files.remove()`

Remove a file or directory.

```python theme={null}
sbx.files.remove("/home/user/temp_output.txt")
```

<ParamField body="path" type="str" required>
  Absolute path to remove.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `None`

***

## `sbx.files.rename()`

Rename or move a file or directory.

```python theme={null}
new_entry: EntryInfo = sbx.files.rename(
    "/home/user/draft.py",
    "/home/user/final.py",
)
```

<ParamField body="old_path" type="str" required>
  Current absolute path.
</ParamField>

<ParamField body="new_path" type="str" required>
  New absolute path. Can be a different directory (move semantics).
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `EntryInfo` for the renamed entry.

***

## `sbx.files.make_dir()`

Create a directory (including parent directories if needed).

```python theme={null}
created = sbx.files.make_dir("/home/user/output/results")
```

<ParamField body="path" type="str" required>
  Absolute path of the directory to create.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

**Returns** `bool` — `True` if the directory was created.

***

## `sbx.files.watch_dir()`

Watch a directory for filesystem events. Returns a `WatchHandle` that receives
events from the sandbox.

```python theme={null}
handle: WatchHandle = sbx.files.watch_dir(
    "/home/user/data",
    recursive=True,
)
```

<ParamField body="path" type="str" required>
  Absolute path of the directory to watch.
</ParamField>

<ParamField body="user" type="str" default="'user'">
  Unix user context.
</ParamField>

<ParamField body="request_timeout" type="float | None" default="None">
  Per-request HTTP timeout in seconds.
</ParamField>

<ParamField body="recursive" type="bool" default="False">
  Watch the directory and all subdirectories recursively.
</ParamField>

**Returns** `WatchHandle`

***

## Data models

### `EntryInfo`

```python theme={null}
@dataclass
class EntryInfo:
    name: str          # Filename or directory name
    path: str          # Full absolute path
    type: FileType     # FileType.FILE or FileType.DIR
    size: int = 0      # Size in bytes (0 for directories)
```

### `FileType`

```python theme={null}
class FileType(str, Enum):
    FILE = "file"
    DIR  = "dir"
```

### `WriteInfo`

```python theme={null}
@dataclass
class WriteInfo:
    path: str      # Absolute path of the written file
    size: int = 0  # Bytes written
```

### `WriteEntry`

```python theme={null}
@dataclass
class WriteEntry:
    path: str               # Absolute destination path
    data: str | bytes       # Content to write
```

### `FilesystemEvent`

```python theme={null}
@dataclass
class FilesystemEvent:
    type: FilesystemEventType
    path: str
    timestamp: float | None = None
```

### `FilesystemEventType`

```python theme={null}
class FilesystemEventType(str, Enum):
    CREATE = "create"
    WRITE  = "write"
    REMOVE = "remove"
    RENAME = "rename"
    CHMOD  = "chmod"
```

### `WatchHandle`

```python theme={null}
class WatchHandle:
    def stop(self) -> None: ...
    def get_new_events(self) -> list[FilesystemEvent]: ...
```

The handle uses a poll-and-drain model — call `get_new_events()` to pull
buffered events. There is no iterator protocol or callback subscription.

***

## Examples

### Upload and execute a script

```python theme={null}
sbx.files.write("/home/user/analyze.py", open("local_analyze.py").read())
result = sbx.commands.run("python3 /home/user/analyze.py")
print(result.stdout)
```

### Batch upload a dataset

```python theme={null}
from declaw import WriteEntry
import pathlib

entries = [
    WriteEntry(path=f"/data/{f.name}", data=f.read_bytes())
    for f in pathlib.Path("./dataset").iterdir()
    if f.is_file()
]
sbx.files.write_files(entries)
```

### Download generated output

```python theme={null}
sbx.commands.run("python3 -c \"open('/tmp/out.csv','w').write('a,b\\n1,2')\"")
csv_content = sbx.files.read("/tmp/out.csv")
with open("local_out.csv", "w") as f:
    f.write(csv_content)
```
