Skip to main content

Task authoring and execution

Flyte tasks are the fundamental building blocks of Flytekit. They represent a single unit of execution that is versioned, strongly typed, and independently executable. In Flytekit, tasks are typically defined using the @task decorator, which transforms a standard Python function into a PythonFunctionTask.

Declaring Tasks​

The most common way to declare a task is by decorating a Python function with @task. Flytekit uses Python type hints to automatically infer the task's interface (inputs and outputs).

from flytekit import task
import typing

@task
def greet(name: str) -> str:
return f"Hello, {name}!"

@task
def add_numbers(x: int, y: int) -> int:
return x + y

When you decorate a function, Flytekit creates an instance of flytekit.core.python_function_task.PythonFunctionTask. This class captures the function's signature and metadata, allowing it to be serialized and executed on the Flyte platform.

Task Metadata and Configuration​

You can configure task behavior by passing arguments to the @task decorator. These arguments are stored in a TaskMetadata object (defined in flytekit.core.base_task).

Common configuration options include:

  • Caching: Enable caching to avoid re-running tasks with the same inputs.
@task(cache=True, cache_version="1.0")
def expensive_computation(data: typing.List[int]) -> int:
return sum(data)
  • Retries: Specify how many times Flyte should retry the task on failure.
@task(retries=3)
def flaky_task(x: int) -> int:
return x + 1
  • Resources: Request specific CPU, memory, or GPU resources using flytekit.Resources.
from flytekit import Resources

@task(requests=Resources(cpu="2", mem="500Mi"), limits=Resources(cpu="4", mem="1Gi"))
def resource_intensive_task(x: int) -> int:
return x * 2
  • Timeouts: Set a maximum duration for task execution.
import datetime

@task(timeout=datetime.timedelta(minutes=5))
def time_limited_task(x: int) -> int:
return x

Core Task Abstractions​

Flytekit provides a hierarchy of classes to handle different task types and execution modes:

  1. Task (flytekit.core.base_task.Task): The base class for all tasks. It captures the Flyte IDL TaskTemplate information but lacks Python-native interfaces.
  2. PythonTask (flytekit.core.base_task.PythonTask): A subclass of Task that adds a Python-native Interface. It handles the translation between Python types and Flyte's internal literal system via the TypeEngine.
  3. PythonFunctionTask (flytekit.core.python_function_task.PythonFunctionTask): The standard class for tasks defined via a Python function. It implements execute() by calling the decorated function.
  4. AsyncPythonFunctionTask: Used when the decorated function is defined with async def.

The Execution Flow​

When a task is executed (either locally or on a cluster), Flytekit follows a structured dispatch process:

  1. local_execute: Used during local development. It translates native Python inputs into Flyte Literal objects, checks the local cache, and calls sandbox_execute.
  2. dispatch_execute: The entry point for both local and remote execution. It handles pre_execute hooks (e.g., setting up Spark sessions), converts LiteralMap inputs back to Python native types, and invokes the user's code.
  3. execute: The method that actually runs the user-defined logic. For PythonFunctionTask, this simply calls the wrapped function.

Task Resolvers​

When a task runs on a remote Flyte cluster, the container needs to know how to find and load the specific Python task object. This is handled by a TaskResolverMixin.

The default_task_resolver (in flytekit.core.python_auto_container) works by:

  1. Identifying the module and function name during serialization.
  2. Generating a command like pyflyte-execute --resolver flytekit.core.python_auto_container.default_task_resolver -- task-module my_module task-name my_task.
  3. Importing the module and retrieving the task object at runtime using load_task.

Special Task Types​

Dynamic Tasks​

A task can be marked as dynamic by using the @dynamic decorator (which sets execution_mode to DYNAMIC). Dynamic tasks allow you to generate a new workflow structure at runtime based on inputs.

from flytekit import dynamic

@dynamic
def dynamic_task(n: int) -> typing.List[int]:
return [add_numbers(x=i, y=i) for i in range(n)]

Internally, PythonFunctionTask.dynamic_execute compiles the returned entities into a DynamicJobSpec, which Flyte Propeller then executes as a sub-workflow.

Eager Tasks​

Eager tasks (declared with @eager) allow for more imperative-style execution where tasks are awaited and their results used immediately to decide the next step. These are implemented by EagerAsyncPythonFunctionTask.

Ignore Outputs​

In some scenarios, such as distributed training, a task might not want to return its results to the Flyte engine. You can raise flytekit.core.base_task.IgnoreOutputs to signal that the task's outputs should be ignored.

from flytekit.core.base_task import IgnoreOutputs

@task
def distributed_worker():
# ... perform work ...
raise IgnoreOutputs()