Skip to main content

Task Abstractions

Tasks are the fundamental building blocks of flytekit workflows. They represent a single unit of work that is versioned, strongly typed, and independently executable. In flytekit, tasks are implemented through a hierarchy of classes that bridge Python-native code with the Flyte backend.

The Task Hierarchy​

Flytekit uses a layered abstraction to handle different types of tasks, ranging from raw IDL-based tasks to Python-native functions.

Base Task​

The Task class in flytekit.core.base_task is the root of all tasks. It is designed to be closest to the FlyteIDL TaskTemplate. It captures metadata like retries, timeouts, and caching but does not have a Python-native interface. You typically won't instantiate this class directly; instead, it serves as the foundation for specialized task types.

Python Task​

The PythonTask class (also in flytekit.core.base_task) extends the base Task by adding a python_interface. This interface allows flytekit to map Python type hints (like int, str, or pandas.DataFrame) to Flyte's internal type system.

Python Function Task​

The PythonFunctionTask in flytekit.core.python_function_task is the most common task type. It is the underlying class created when you use the @task decorator. It automatically detects the interface of a Python function and handles the serialization required to run that function on the Flyte platform.

Creating Tasks with the @task Decorator​

The primary way to define a task in flytekit is using the @task decorator. This decorator transforms a standard Python function into a PythonFunctionTask.

from flytekit import task

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

When you call greet("World") locally, flytekit executes the function directly. When used inside a @workflow, flytekit uses the PythonFunctionTask metadata to compile a execution graph.

Task Configuration and Metadata​

The @task decorator accepts several arguments to control execution behavior. These are stored internally in the TaskMetadata class.

from datetime import timedelta
from flytekit import task, Resources

@task(
retries=3,
timeout=timedelta(minutes=60),
requests=Resources(cpu="2", mem="4Gi"),
limits=Resources(cpu="4", mem="8Gi"),
cache=True,
cache_version="1.0"
)
def heavy_computation(x: int) -> int:
return x ** 2

Common configuration options include:

  • retries: The number of times to retry the task on failure.
  • timeout: A datetime.timedelta or int (seconds) specifying the maximum runtime.
  • cache: Enables caching of results based on input values.
  • requests / limits: Defines the Resources (CPU, Memory, GPU) required for the task.

Advanced Task Types​

Dynamic Tasks​

A dynamic task is a task that can generate a new workflow at runtime based on its inputs. This is useful for scenarios like processing a variable number of files. You create a dynamic task by setting the execution_mode to DYNAMIC (or using the @dynamic decorator, which is a shortcut).

Internally, PythonFunctionTask.dispatch_execute detects the DYNAMIC execution mode and invokes compile_into_workflow to produce a DynamicJobSpec.

Python Instance Tasks​

For tasks that do not have a user-defined function body but instead have a platform-defined execute method, flytekit provides PythonInstanceTask. This is an abstract base class used by plugins to create tasks that behave like objects.

# Example of a conceptual Instance Task implementation
class MyCustomTask(PythonInstanceTask):
def __init__(self, name: str, **kwargs):
super().__init__(name=name, task_config=None, task_type="custom", **kwargs)

def execute(self, **kwargs) -> Any:
# Custom logic here
return "result"

Eager Tasks​

Eager tasks (or eager workflows) allow you to run Flyte entities using standard Python control flow (like if statements and for loops) while still executing each step as a Flyte task. These are implemented via EagerAsyncPythonFunctionTask. When an eager task is executed, it uses a Controller to manage a worker queue that communicates with the Flyte backend.

Internal Execution Flow​

When a task is executed, flytekit follows a specific lifecycle managed by the Task and PythonTask classes:

  1. pre_execute: Invoked before the task method. It can modify the ExecutionParameters to set up the environment (e.g., initializing a Spark session).
  2. dispatch_execute: The main entry point for execution. It handles:
    • Translating input LiteralMap (Flyte types) to Python native values using the TypeEngine.
    • Calling the user's execute method (the decorated function).
    • Handling different execution modes (Default, Dynamic, or Eager).
  3. post_execute: Invoked after the task completes. It can be used for cleanup or to alter the return values.
  4. Output Translation: The _output_to_literal_map method converts Python return values back into a Flyte LiteralMap.

Task Resolvers​

When a task runs in a container on a Flyte cluster, the system needs to know how to find and load the Python code. This is handled by the TaskResolverMixin. The default resolver (default_task_resolver) captures the module name and the function name. At runtime, the pyflyte-execute command uses these arguments to rehydrate the task object and run the code.