Volume
Volumes: a persistent filesystem you mount into sandboxes and agents.
vol = noclick.Volume.from_name("datasets", create_if_missing=True)
vol.write("/raw/orders.csv", csv_bytes)
with noclick.Sandbox.create(volumes={"/data": vol}) as sb:
sb.exec("python train.py --data /data/raw")Paths start at the volume's root (/raw/). Folders are real folders,
made as files are written into them. What a sandbox or an agent's turn writes is
saved every few seconds while it runs, and all of it when it ends; two writers to one
file at once: the last one to finish wins.
class VolumeA persistent filesystem: files by path, in folders, that outlive the
sandbox or agent turn that wrote them. Mount it with
Sandbox.create(volumes={"/data": vol}) or Agent.from_name(..., volumes=...),
or read and write it from your own code.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| name | str | |
| size_bytes | int | None | |
| file_count | int | None | |
| mounted_by | list[dict[str, Any]] | The running sandboxes, the agents and the machines that mount it: |
| region | str | None | Always |
| user_id | str | None | |
| created_at | Any | |
| console_url | str | None | This volume in the developer console (None off the hosted API). |
createclassmethodasync
noclick.Volume.create(name, *, region=None, user=..., client=None) -> VolumeCreate an empty volume called name (1-64 letters, digits and
. _ -), unique to its user (a taken name raises noclick.Conflict
with existing_id).
Invite-only beta: region (noclick.regions()), where its
files are kept. The sandbox provider's volumes take no location yet, so
passing one raises noclick.NotAvailable; a sandbox or agent in any
region mounts a volume.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The new, empty volume.
An empty volume:
cache = noclick.Volume.create("build-cache")
print(cache.id, cache.size_bytes) # ... 0One per user, for what their sandboxes build:
with noclick.as_user("user_42"):
work = noclick.Volume.create("workbench")
print(work.user_id) # user_42from_nameclassmethod
noclick.Volume.from_name(
name, *, create_if_missing=False, region=None, user=..., client=None
) -> VolumeA lazy reference to the volume called name. Acting for a user, that
user's own volume is found first, then the account's (every user's
sandboxes and agents can mount those). With create_if_missing=True the
current (or given) user's is created on first use.
Invite-only beta: region (see create).
FalseWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A volume that loads (or is created) the first time it's used. Nothing is sent yet.
Your code is the definition; the first run creates it:
vol = noclick.Volume.from_name("datasets", create_if_missing=True)
vol.write("/raw/orders.csv", b"id,total\no_1,42\n")A volume per user, for what their sandboxes build:
with noclick.as_user("user_42"):
work = noclick.Volume.from_name("workbench", create_if_missing=True)
print(work.listdir("/"))from_idclassmethod
noclick.Volume.from_id(volume_id, *, client=None, user=...) -> VolumeA lazy reference to a volume by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A volume that loads the first time you read a field. Nothing is sent yet.
vol = noclick.Volume.from_id("4c8e1f0a-6b3d-4a92-9e57-1d2f8b0c7a36")
data = vol.read("/raw/orders.csv")getclassmethodasync
noclick.Volume.get(volume_id, *, user=..., client=None) -> VolumeLoad a volume by id now; an unknown id raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The volume, loaded: its size, how many files it holds, and who mounts it.
vol = noclick.Volume.get("4c8e1f0a-6b3d-4a92-9e57-1d2f8b0c7a36")
print(vol.name, vol.size_bytes, vol.file_count, vol.mounted_by)listclassmethodasync
noclick.Volume.list(
*, limit=None, user_filter=None, user=..., client=None
) -> list[Volume]The current user's volumes and the account's (or those user_filter covers).
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching volume, newest first, fetched page by page.
for vol in noclick.Volume.list():
print(vol.name, vol.size_bytes, vol.user_id) # user_id is None for the account's
everyone = noclick.Volume.list(user_filter=noclick.ALL_USERS) # every user's, plus your account'slist_pageclassmethodasync
noclick.Volume.list_page(
*, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> PageOne page of volumes; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the volumes and whose next_cursor is None on the last page.
page = noclick.Volume.list_page(limit=50)
while True:
for vol in page.data:
print(vol.name, vol.file_count)
if page.next_cursor is None:
break
page = noclick.Volume.list_page(limit=50, cursor=page.next_cursor)writeasync
vol.write(path, data) -> VolumeEntryWrite data (bytes, text as UTF-8, or a binary file) to path,
replacing the file there and making any folders it needs. It's saved at
once: a sandbox or agent that mounts the volume after this sees it. One
write carries up to 100 MB; make bigger files inside a sandbox that
mounts the volume.
The file as written, with its size.
Put a dataset where your sandboxes will read it:
vol = noclick.Volume.from_name("datasets", create_if_missing=True)
entry = vol.write("/raw/orders.csv", b"id,total\no_1,42\n")
print(entry.path, entry.size)Upload a file from disk:
vol = noclick.Volume.from_name("datasets")
with open("model.bin", "rb") as f:
vol.write("/models/model.bin", f)readasync
vol.read(path) -> bytesRead the file at path, as the volume last saved it, up to 100 MB; a
missing file raises noclick.NotFound, a folder or a bigger file
noclick.InvalidRequest.
The file's bytes, exactly as stored; decode text yourself.
vol = noclick.Volume.from_name("datasets")
report = vol.read("/out/report.csv").decode()
print(report.splitlines()[0])listdirasync
vol.listdir(path='/', *, recursive=False, limit=None) -> list[VolumeEntry]What's in the folder at path, in path order: its files and folders,
or with recursive=True everything below it. A path that doesn't exist
raises noclick.NotFound.
'/'FalseReturn at most this many.
None)Every entry (or the first limit), fetched page by page.
One folder, like ls:
vol = noclick.Volume.from_name("datasets")
for entry in vol.listdir("/raw"):
print(entry.path, entry.type, entry.size)Every file under a folder:
vol = noclick.Volume.from_name("datasets")
files = [e for e in vol.listdir("/", recursive=True) if e.type == "file"]
print(len(files), sum(e.size or 0 for e in files))listdir_pageasync
vol.listdir_page(path='/', *, recursive=False, cursor=None, limit=None) -> PageOne page of listdir; pass next_cursor back as cursor.
'/'FalseThe next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)A page whose data holds VolumeEntrys and whose next_cursor is None on the last page.
vol = noclick.Volume.from_name("datasets")
page = vol.listdir_page("/raw", limit=100)
while True:
for entry in page.data:
print(entry.path, entry.size)
if page.next_cursor is None:
break
page = vol.listdir_page("/raw", limit=100, cursor=page.next_cursor)removeasync
vol.remove(path, *, recursive=False) -> NoneDelete the file or folder at path. A folder that isn't empty needs
recursive=True (otherwise noclick.Conflict); a path that isn't
there does nothing.
Falsevol = noclick.Volume.from_name("datasets")
vol.remove("/raw/orders.csv")
vol.remove("/tmp", recursive=True)copyasync
vol.copy(src, dst) -> VolumeEntryCopy the file or folder at src to dst in this volume (a folder
with everything in it), replacing what dst held.
The copy at dst.
vol = noclick.Volume.from_name("datasets")
vol.copy("/raw/orders.csv", "/archive/2026-09/orders.csv")exportasync
vol.export() -> OperationExport every file, so the volume can move anywhere: a zip holding them
in their folders (under volumes/<name>/files/), with index.jsonl
listing each path, its size and when it last changed.
An operation whose wait() gives an ExportBundle: the zip at url, valid 7 days.
vol = noclick.Volume.from_name("datasets")
bundle = vol.export().wait()
print(bundle.url, bundle.expires_at)deleteasync
vol.delete() -> TrashItemDelete the volume and every file in it: it goes to the trash, restorable for 30 days (its files
stored, and billed, until it's purged). While a running sandbox, an
agent or a machine mounts it (mounted_by), this raises noclick.Conflict:
take it out of the agent's or machine's volumes and let the sandboxes end first. A user
can't delete the account's volumes (noclick.PermissionDenied).
noclick.Volume.from_name("build-cache").delete()VolumeEntry
class VolumeEntryA file or folder in a volume: its path from the volume's root, its
type (file or directory), a file's size in bytes (None for a
folder) and when it last changed (updated_at).
| path | str | |
| type | str | |
| size | int | None | |
| updated_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |