/docs/Development/Entry Point
back to app →

Entry point — main.py

Every INPUT_OUTPUT Python tool has a main.py at the root of its repo. This is the file the sandbox runs, and it must define a top-level function called process. On every run, the sandbox calls process, passes it the user's input and settings, and renders whatever it returns into the output panel.

This page applies to INPUT_OUTPUT tools. IFRAME tools have no process function — they render a web page instead. See artifuncs.json for the difference.

The process function

python
def process(input: dict, settings: dict) -> dict:
    return {}

That's the whole contract. The sandbox loads main.py, looks for process, and calls it once per run. If main.py has no process function, the tool fails to start with "Tool missing process() function".

process is synchronous — return the result directly, don't await it.

What it receives

process is called with two arguments, both plain dicts:

ArgumentWhere it comes fromKeyed by
inputThe per-run input form the user fills inYour fields.input names from artifuncs.json
settingsThe Settings panel — values that persist between runsYour settings names from artifuncs.json

Each key matches a field name you declared in artifuncs.json, and each value has the type of that field (see Fields). For example, given:

json
"fields": {
  "input": {
    "a": { "type": "number", "default": 0 },
    "b": { "type": "number", "default": 0 }
  }
},
"settings": {
  "precision": { "type": "number", "label": "Decimal places", "default": 2 }
}

process receives:

python
input    == { "a": 3, "b": 4 }
settings == { "precision": 2 }

What it returns

Return a dict whose keys match your fields.output declarations. The sandbox maps each key onto the matching output field and renders it. Keys that don't match an output field are ignored; missing keys leave that output empty.

python
def process(input, settings):
    total = input["a"] + input["b"]
    return { "sum": round(total, settings["precision"]) }

Returning an empty {} is valid — it just produces empty outputs.

If process raises, the run fails and the exception's type and message are streamed back to the UI instead of a result, so let errors propagate rather than swallowing them.

Logging

Anything you print() is captured and streamed to the run's log panel as a DEBUG line. For levelled logging, use the SDK's log object:

python
from artifuncs.sandbox_sdk import log

def process(input, settings):
    log.info("processing started")
    log.debug(f"a={input['a']} b={input['b']}")
    return { "sum": input["a"] + input["b"] }

log exposes debug, info, warn, and error. See Running tools for how logs reach the UI.

Working with files

When your tool has file inputs or outputs, read and write them through the SDK rather than touching the filesystem directly — it sandboxes paths to the tool's files directory:

python
from artifuncs.sandbox_sdk import get_file_content, save_file_content, list_files

def process(input, settings):
    data = get_file_content(input["upload"])
    save_file_content("result.txt", b"...")
    return { "files": list_files() }

Dependencies

Third-party packages go in requirements.txt next to main.py; the sandbox installs them when the tool is set up. Import them in main.py as usual. See Dependencies for the full story on both languages.

JavaScript tools

JavaScript tools use index.js instead of main.py, and export the same process contract. The function may be sync or async:

js
export async function process(input, settings) {
    return { sum: input.a + input.b }
}

Dependencies are declared in package.json. Everything else on this page — the arguments, the return shape, logging via console.log/print, and field-name mapping — works the same way.