Skip to main content

Sandboxed Python Tasks

Sandboxed Python Tasks allow you to execute Python functions within a restricted Monty runtime. This implementation provides strong security isolation and microsecond-level startup times by executing a subset of Python logic without the overhead of a full container boot or a standard Python interpreter.

In this codebase, sandboxed tasks are primarily managed through the SandboxedTaskTemplate and configured via SandboxedConfig.

Core Concepts​

The Monty Runtime​

The SandboxedTaskTemplate (found in src/flyte/sandbox/_task.py) does not execute code using the standard eval() or exec() functions. Instead, it uses Monty, a specialized runtime that:

  1. Extracts Source: During task initialization, the template uses extract_source to pull the raw source code from the decorated Python function.
  2. Analyzes References: It uses _discover_external_refs to identify if the function calls other Flyte tasks or durable operations.
  3. Executes in Isolation: The code is run in a restricted environment where memory, stack depth, and execution time are strictly enforced.

SandboxedTaskTemplate​

This class is the engine for sandboxed execution. It handles two distinct execution paths:

  • Pure Python Path: If the function contains only standard Python logic (no external task calls), it runs directly via the Monty class for maximum performance.
  • Orchestration Path: If the function calls other tasks, it uses an ExternalFunctionBridge to handle asynchronous dispatch and state management between the sandbox and the Flyte engine.
# From src/flyte/sandbox/_task.py
async def _run_sandboxed(self, *args, **kwargs) -> Any:
Monty = _lazy_import_monty()
inputs = self._build_inputs(*args, **kwargs)

if not self._has_external_refs:
# Fast path: pure Python, no external calls
monty = Monty(self._source_code, inputs=self._input_names)
return monty.run(inputs=inputs)
else:
# External calls: use bridge for async dispatch
from ._bridge import ExternalFunctionBridge
bridge = ExternalFunctionBridge(**self._external_refs)
return await bridge.execute_monty(Monty, self._source_code, self._input_names, inputs)

Defining Sandboxed Tasks​

Sandboxed tasks are created using the sandbox.orchestrator property of a TaskEnvironment.

Using the Decorator​

The most common way to define a sandboxed task is by decorating a function. This automatically captures the function's source code and interface.

from flyte import TaskEnvironment

env = TaskEnvironment(name="production-env")

@env.sandbox.orchestrator
def calculate_total(price: float, quantity: int) -> float:
return price * quantity

Dynamic Code Strings​

You can also create tasks dynamically by passing a Python code string. This is useful for generating logic at runtime.

# Example based on tests/flyte/sandbox/test_code_task.py
t = env.sandbox.orchestrator(
"x + y",
inputs={"x": int, "y": int},
output=int,
name="dynamic_add"
)

Configuration and Resource Limits​

The SandboxedConfig class (in src/flyte/sandbox/_config.py) defines the constraints applied to the Monty runtime. You can override these defaults when using the decorator factory.

ParameterDefaultDescription
max_memory50 MBMaximum memory consumption allowed for the sandbox.
max_stack_depth256Maximum recursion or call stack depth.
timeout_ms30,000Execution timeout in milliseconds (30 seconds).
type_checkTrueWhether to validate input/output types against the interface.

Example with custom configuration:

@env.sandbox.orchestrator(
timeout_ms=5000,
max_memory=10 * 1024 * 1024 # 10 MB
)
def lightweight_task(x: int) -> int:
return x + 1

Sandbox Restrictions​

Because the Monty runtime is designed for security and performance, it enforces several strict limitations on the Python code it can execute.

Syntax Restrictions​

The following Python features are not supported inside a sandboxed task:

  • Imports: You cannot use the import statement. All logic must be self-contained or passed in via task orchestration.
  • Classes: You cannot define or instantiate new classes.
  • Exception Handling: try/except/finally blocks are not permitted.
  • Context Managers: The with statement is not supported.
  • Augmented Assignment: Statements like x += 1 are not allowed; use x = x + 1 instead.

Type Restrictions​

The sandbox only supports a subset of Python and Flyte types:

  • Primitives: int, float, str, bool, bytes, None.
  • Collections: list, dict, tuple, set.
  • Flyte IO: File, Dir, DataFrame.
  • Prohibited: Custom Pydantic models or arbitrary Python objects cannot be used as inputs or outputs.

Immutable-like Behavior​

Inside the sandbox, you cannot perform subscript assignment on dictionaries. For example, my_dict['key'] = value will fail. Dictionaries must be defined as literals or returned as new objects.