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:
- Extracts Source: During task initialization, the template uses
extract_sourceto pull the raw source code from the decorated Python function. - Analyzes References: It uses
_discover_external_refsto identify if the function calls other Flyte tasks or durable operations. - 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
Montyclass for maximum performance. - Orchestration Path: If the function calls other tasks, it uses an
ExternalFunctionBridgeto 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.
| Parameter | Default | Description |
|---|---|---|
max_memory | 50 MB | Maximum memory consumption allowed for the sandbox. |
max_stack_depth | 256 | Maximum recursion or call stack depth. |
timeout_ms | 30,000 | Execution timeout in milliseconds (30 seconds). |
type_check | True | Whether 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
importstatement. All logic must be self-contained or passed in via task orchestration. - Classes: You cannot define or instantiate new classes.
- Exception Handling:
try/except/finallyblocks are not permitted. - Context Managers: The
withstatement is not supported. - Augmented Assignment: Statements like
x += 1are not allowed; usex = x + 1instead.
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.