# Python exec (sandboxed)

Run a sandboxed Python snippet over records without network access.

## When to use it

Write a bounded Python transform using records as input and out as the output list. Network access and access to the host environment are unavailable. Imports are restricted; unavailable or unsupported execution is refused rather than running the code with broader access.

## Add and configure

1. Add **Python exec (sandboxed)** from the workflow step picker.
2. Give the step a recognisable name and connect its required inputs.
3. Set the options below. Nested names identify fields within an object or a list item.
4. Validate the recipe, inspect the test result, then run it on the intended data.

The step identifier used in the editor’s code view is `python_exec`.

## Settings

| Setting | Accepted value | Required | Default | What it controls |
| --- | --- | --- | --- | --- |
| Code (`code`) | string; minimum characters 1; maximum characters 64000 | No | "# `records` is the input list[dict]; append output records to `out`.\n# Default: pass records through unchanged.\nout.extend(records)\n" | Python source. Reads input from `records` (list[dict]); appends output records to `out` (list[dict]). Defaults to the passthrough transform (records out unchanged). |
| Imports (`imports`) | list of string | No | Not specified | Optional `import` statements prepended to the snippet. Restricted by the shared Tier-1 allowlist. |
| Memory Mb (`memory_mb`) | integer; minimum 32; maximum 2048 | No | 256 | Memory limit for the restricted interpreter. Execution fails when the interpreter exceeds its allocation budget. |
| Timeout Seconds (`timeout_seconds`) | number; minimum 1.0; maximum 300.0 | No | 30.0 | Time limit in seconds for the configured processing operation. Inspect its saved outcome before retrying after a timeout. |

## Inputs and outputs

| Direction | Port | Content | Required | Multiple records |
| --- | --- | --- | --- | --- |
| Inputs | `in` | records | Yes | Yes |
| Outputs | `out` | records | Yes | Yes |

## Effects, testing, and recovery

Transforms or passes records without a declared external side effect.

The declared preview behaviour is to execute this step. Reads, processing, or model usage can therefore be real. Read [dry runs](/docs/workflows/dry-runs) before using a preview with live connections.

No duplicate-prevention key is declared for this step. Do not assume that manually repeating the step is free of repeated effects.

## Example: Pass records through (the default snippet)

Step settings:

```json
{}
```

Input records:

```json
[
  {
    "id": 1
  },
  {
    "id": 2
  }
]
```

Expected output for this example:

```json
[
  {
    "id": 1
  },
  {
    "id": 2
  }
]
```

## Example: Add a computed field

Step settings:

```json
{
  "code": "for r in records:\n    out.append({**r, 'total': r['qty'] * r['price']})\n"
}
```

Input records:

```json
[
  {
    "price": 3,
    "qty": 2
  },
  {
    "price": 10,
    "qty": 1
  }
]
```

Expected output for this example:

```json
[
  {
    "price": 3,
    "qty": 2,
    "total": 6
  },
  {
    "price": 10,
    "qty": 1,
    "total": 10
  }
]
```

## Check the result

Inspect the selected run’s status, counts, errors, and output records. An empty collection can be a valid result, and a completed step can contain per-record failures where the step defines that behaviour. Check the next step’s expected record shape before publishing or delivering the result.
