Task authoring and execution
Flyte tasks are the fundamental building blocks of a workflow, representing a single unit of execution. In flytekit, tasks are strongly typed, versioned, and independently executable.
Declaring Tasks
The most common way to define a task is using the @task decorator from flytekit.core.task. This decorator transforms a standard Python function into a PythonFunctionTask.
from flytekit import task
@task
def add(a: int, b: int) -> int:
return a + b
When you decorate a function, flytekit automatically inspects the function signature to determine the task's interface (inputs and outputs). This is handled by transform_function_to_interface in flytekit.core.interface.
Task Configuration
The @task decorator accepts several parameters to control execution behavior:
- Retries: Use
retriesto specify how many times the task should be retried on failure. - Timeout: Use
timeout(as anintof seconds ordatetime.timedelta) to limit the execution duration. - Caching: Enable caching by setting
cache=Trueand providing acache_version. - Resources: Specify
requestsandlimitsfor CPU, memory, and other resources using theResourcesclass.
from datetime import timedelta
from flytekit import task, Resources
@task(
retries=3,
timeout=timedelta(minutes=5),
cache=True,
cache_version="1.0",
requests=Resources(cpu="1", mem="500Mi"),
limits=Resources(cpu="2", mem="1Gi")
)
def resource_intensive_task(data: list[int]) -> int:
return sum(data)
Internally, these configurations are stored in a TaskMetadata object (defined in flytekit.core.base_task), which is then converted to the Flyte IDL representation via to_taskmetadata_model.
Core Task Abstractions
Flytekit uses a class hierarchy to manage different task behaviors:
Task: The base class inflytekit.core.base_task. It captures the Flyte IDLTaskTemplateand does not have a Python-native interface.PythonTask: A subclass ofTaskthat introduces apython_interface(anInterfaceobject). It handles the mapping between Python types and Flyte literals.PythonFunctionTask: The primary class for tasks defined by Python functions. It manages the execution of the user's code and supports different execution modes likeDYNAMICandEAGER.
The Plugin System
Flytekit supports various task types (e.g., Spark, SQL, Pods) through TaskPlugins. When you provide a task_config to the @task decorator, flytekit uses TaskPlugins.find_pythontask_plugin to locate the appropriate subclass of PythonFunctionTask.
# Example of a task using a specific config (e.g., for a Spark plugin)
@task(task_config=Spark(spark_conf={"spark.driver.memory": "2g"}))
def my_spark_task(a: int) -> int:
...
Task Execution
Tasks can be executed in different environments, and flytekit handles the translation of data accordingly.
Local Execution
When you call a task directly in a Python script, flytekit triggers local_execute. This method:
- Translates Python native inputs into Flyte literals using
translate_inputs_to_literals. - Checks the local cache if caching is enabled.
- Invokes
sandbox_execute, which eventually callsdispatch_execute. - Translates the resulting literals back into Python native values or
Promiseobjects.
Remote Execution
On the Flyte platform, the container starts with a command generated by a TaskResolver. The default resolver (default_task_resolver) uses the module and function name to rehydrate the task object.
The entry point calls dispatch_execute, which:
- Converts input literals to Python native types via
_literal_map_to_python_input. - Calls the user's
executemethod (the decorated function). - Converts the return values back to literals using
_output_to_literal_map.
Dynamic and Eager Tasks
Flytekit supports advanced execution patterns:
- Dynamic Tasks: Declared with
@dynamic. These tasks return aDynamicJobSpecduring execution, which Flyte Propeller then uses to expand the workflow graph at runtime. This is implemented inPythonFunctionTask.dynamic_execute. - Eager Tasks: Declared by setting
is_eager=Truein metadata (or using the@eagerdecorator). These tasks allow for more flexible, imperative-style execution where tasks are awaited. They are implemented byEagerAsyncPythonFunctionTaskand use aControllerto manage sub-executions on the Flyte backend.
Important Constraints
- Nested Functions: The
default_task_resolvercannot handle tasks defined inside other functions. Tasks must be accessible at the module level so they can be imported and executed in the container. - Caching Requirements: If
cache=Trueis set, acache_versionmust also be provided, or aValueErrorwill be raised during task initialization inTaskMetadata.__post_init__. - Async Tasks: Tasks defined with
async defare instantiated asAsyncPythonFunctionTask. These tasks are executed using aloop_managerto handle the asynchronous lifecycle.