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 exit
class Sandbox

A 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.

Attributes
idstr
statusstr | None
imagestr | None
timeout_sint | None
cpufloat | None
memory_mbint | None
regionstr | None

The region it runs in (noclick.regions()), or None: wherever NoClick had capacity.

user_idstr | None
expires_atAny
created_atAny
volumesdict[str, str] | None

The volumes it mounts, by mount path: {path: volume id}.

secret_idslist[str] | None

The secrets in its environment, in order (a later one's key wins).

networkAny

What it may reach: False (nothing), True (anywhere) or the hosts it may reach, as sent.

filesSandboxFiles

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
) -> Sandbox

Start 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).

Parameters
imagestr | Nonedefault None
timeout_sint | Nonedefault None
cpufloat | Nonedefault None
memory_mbint | Nonedefault None
networkbool | Sequence[str] | Nonedefault None
regionstr | Nonedefault None
volumesMapping[str, Any] | Nonedefault None
secretsSequence[Any] | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Sandbox

The sandbox, running and ready for commands. It stops on its own at expires_at.

Usage

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)   # 42

Without 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)   # eu

Reach 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=...) -> Sandbox

A lazy reference to a sandbox by id, for one you started earlier.

Parameters
sandbox_idstrrequired
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
Returns
Sandbox

A sandbox that loads the first time you read a field. Nothing is sent yet.

Usage
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) -> Sandbox

Load a sandbox by id now; an unknown id raises noclick.NotFound.

Parameters
sandbox_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Sandbox

The sandbox, loaded. Its status is starting, running, terminated or failed.

Usage
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.

Parameters
statusstr | Nonedefault None
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[Sandbox]

Every matching sandbox, newest first, fetched page by page.

Usage

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
) -> Page

One page of sandboxes; pass next_cursor back as cursor.

Parameters
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
statusstr | Nonedefault None
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Page

A page whose data holds the sandboxes and whose next_cursor is None on the last page.

Usage
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 | Operation

Run 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).

Parameters
commandstr | Sequence[str]required
backgroundbooldefault False
timeout_sint | Nonedefault None
Returns
ExecResult | Operation

What 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.

Usage

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 you

Run 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() -> None

Stop the sandbox now (billing stops with it).

Usage
sb = noclick.Sandbox.create()
try:
    print(sb.exec("uname -a").stdout)
finally:
    sb.terminate()

SandboxFiles

class SandboxFiles

sb.files: read and write files inside the sandbox.

writeasync

sb.files.write(path, data) -> None

Write data (bytes, or text as UTF-8) to path, replacing it.

Parameters
pathstrrequired
databytes | strrequired
Usage
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 sandbox

readasync

sb.files.read(path) -> bytes

Read the file at path; a missing file raises noclick.NotFound.

Parameters
pathstrrequired
Returns
bytes

The file's bytes, exactly as stored; decode text yourself.

Usage
with noclick.Sandbox.create() as sb:
    sb.exec("echo done > /workspace/out.txt")
    print(sb.files.read("/workspace/out.txt").decode())   # done

ExecResult

class ExecResult

What a command printed and how it exited (a non-zero exit_code is a result, not an error).

Attributes
stdoutstr
stderrstr
exit_codeint | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

okbool

Whether the command exited 0.