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.