Sandbox
Sandboxes: a short-lived Linux box to run code in.
with noclick.Sandbox.create(image="python", timeout_s=3600) as sb:
sb.files.write("/tmp/in.csv", csv_bytes)
print(sb.exec("python -c 'print(1 + 1)'").stdout)
build = sb.exec("make all", background=True, timeout_s=1800) # an Operation
print(build.wait().exit_code)
# terminated on exitclass SandboxA running sandbox. with noclick.Sandbox.create() as sb: terminates it on exit;
otherwise it stops at expires_at. Billed per second while it runs, at its
provider's price for the cpu and memory_mb it reserves.
- Like every live object, it also has hydrate(), refresh() and raw.
- Works as a context manager:
withorasync with.
| id | str | |
| status | str | None | |
| image | str | None | |
| timeout_s | int | None | |
| cpu | float | None | |
| memory_mb | int | None | |
| region | str | None | The region it runs in ( |
| user_id | str | None | |
| expires_at | Any | |
| created_at | Any | |
| volumes | dict[str, str] | None | The volumes it mounts, by mount path: |
| secret_ids | list[str] | None | The secrets in its environment, in order (a later one's key wins). |
| network | Any | What it may reach: |
| files | SandboxFiles |
createclassmethodasync
noclick.Sandbox.create(
image=None, *, timeout_s=None, cpu=None, memory_mb=None, network=None, region=None,
volumes=None, secrets=None, user=..., client=None
) -> SandboxStart a sandbox (image "python" or "node") that lives
at most timeout_s seconds. network=True allows outbound
internet access, which is off by default. A list of up to 50 hosts allows
only those, over HTTPS on port 443: "pypi.org", or "*.example.com" for a
domain and its subdomains (a scheme, path, port or IP address raises
noclick.InvalidRequest).
region is where it runs (noclick.regions()): region=agent.region
puts it beside an agent. Without one it runs in your account's default
region, else wherever NoClick has capacity. Pinned to a region it costs what the
provider charges there: 1.15x in eu and ap, 1.75x in us-east and us-west.
volumes mounts Volumes (or their ids or names) read-write at
absolute paths, {"/data": vol}, up to 5: the sandbox sees each as it
was when it started, and what it writes there is saved every few seconds
while it runs, and all of it when it ends. A path can't be /,
/workspace itself or a system folder.
secrets (Secrets, ids or names; up to 20) become environment
variables for every command, a later secret's key winning a clash; the
sandbox keeps the values it started with. A volume's or secret's name is
the sandbox's user's own, else the account's (a user session's, its own only).
NoneNoneNoneNoneNoneNoneNoneNoneWho 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 sandbox, running and ready for commands. It stops on its own at expires_at.
Use it as a context manager, so it stops when the block ends:
with noclick.Sandbox.create(image="python", timeout_s=600) as sb:
print(sb.exec("python -c 'print(21 * 2)'").stdout) # 42Without with, stop it yourself or let it run out:
sb = noclick.Sandbox.create(image="node", timeout_s=300, network=True)
print(sb.id, sb.expires_at)
sb.terminate()Run it in Europe, beside an agent that works there:
with noclick.Sandbox.create(region="eu") as sb:
print(sb.region) # euReach only the package index:
with noclick.Sandbox.create(image="python", network=["pypi.org", "files.pythonhosted.org"]) as sb:
sb.exec("pip install requests")Mount a volume and pass a secret, so the job reads its data, calls its API and leaves its output for the next run:
data = noclick.Volume.from_name("datasets", create_if_missing=True)
openai = noclick.Secret.from_name("openai")
with noclick.Sandbox.create(volumes={"/data": data}, secrets=[openai], network=True) as sb:
sb.exec("python summarize.py --in /data/raw --out /data/summaries")from_idclassmethod
noclick.Sandbox.from_id(sandbox_id, *, client=None, user=...) -> SandboxA lazy reference to a sandbox by id, for one you started earlier.
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 sandbox that loads the first time you read a field. Nothing is sent yet.
sb = noclick.Sandbox.from_id("5e0c7a1d-2b9f-4d3e-8a61-7f2c9b4e1d08")
print(sb.exec("ls -la").stdout)getclassmethodasync
noclick.Sandbox.get(sandbox_id, *, user=..., client=None) -> SandboxLoad a sandbox 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 sandbox, loaded. Its status is starting, running, terminated or failed.
sb = noclick.Sandbox.get("5e0c7a1d-2b9f-4d3e-8a61-7f2c9b4e1d08")
print(sb.status, sb.expires_at)listclassmethodasync
noclick.Sandbox.list(
*, status=None, limit=None, user_filter=None, user=..., client=None
) -> list[Sandbox]List the current user's sandboxes (the account's own outside as_user, or
those user_filter covers); status="live" lists those starting or running.
NoneReturn 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 sandbox, newest first, fetched page by page.
The sandboxes still starting or running:
for sb in noclick.Sandbox.list(status="live"):
print(sb.id, sb.image, sb.expires_at)Every user's, plus your account's own:
sandboxes = noclick.Sandbox.list(user_filter=noclick.ALL_USERS, limit=50)list_pageclassmethodasync
noclick.Sandbox.list_page(
*, cursor=None, limit=None, status=None, user_filter=None, user=..., client=None
) -> PageOne page of sandboxes; 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)NoneWhich 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 sandboxes and whose next_cursor is None on the last page.
page = noclick.Sandbox.list_page(status="terminated", limit=50)
while True:
for sb in page.data:
print(sb.id, sb.created_at)
if page.next_cursor is None:
break
page = noclick.Sandbox.list_page(status="terminated", limit=50, cursor=page.next_cursor)execasync
sandbox.exec(command, *, background=False, timeout_s=None) -> ExecResult | OperationRun a shell command (a string, or an argv list that gets quoted)
and wait for it: timeout_s is 60 by default, at most 90. A
non-zero exit is a result, not an exception.
background=True is for commands longer than one request allows
(timeout_s up to 3600, within the sandbox's own lifetime).
FalseNoneWhat the command printed and how it exited: stdout and stderr (the last 64 KiB of each)
and exit_code. With background=True, an Operation at once, whose wait() gives that result.
Commands run with bash in /workspace. Check ok or exit_code, since a failing command
doesn't raise:
with noclick.Sandbox.create() as sb:
result = sb.exec("python -c 'print(21 * 2)'")
print(result.ok, result.stdout) # True 42
sb.exec(["python", "script.py", "--input", "data.csv"]) # an argument list is quoted for youRun anything longer in the background, in a sandbox that lives long enough to finish it:
with noclick.Sandbox.create(timeout_s=3600) as sb:
build = sb.exec("make all", background=True, timeout_s=1800)
print(build.wait().exit_code)terminateasync
sandbox.terminate() -> NoneStop the sandbox now (billing stops with it).
sb = noclick.Sandbox.create()
try:
print(sb.exec("uname -a").stdout)
finally:
sb.terminate()SandboxFiles
class SandboxFilessb.files: read and write files inside the sandbox.
writeasync
sb.files.write(path, data) -> NoneWrite data (bytes, or text as UTF-8) to path, replacing it.
with noclick.Sandbox.create(image="python") as sb:
sb.files.write("/workspace/main.py", "print('hello from the sandbox')")
print(sb.exec("python main.py").stdout) # hello from the sandboxreadasync
sb.files.read(path) -> bytesRead the file at path; a missing file raises noclick.NotFound.
The file's bytes, exactly as stored; decode text yourself.
with noclick.Sandbox.create() as sb:
sb.exec("echo done > /workspace/out.txt")
print(sb.files.read("/workspace/out.txt").decode()) # doneExecResult
class ExecResultWhat a command printed and how it exited (a non-zero exit_code is a result, not an error).
| stdout | str | |
| stderr | str | |
| exit_code | int | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
| ok | bool | Whether the command exited 0. |