Task authoring and execution
Flyte tasks are the fundamental building blocks of a Flyte workflow. They represent a discrete unit of work, encapsulated with a strong interface, versioning, and specific execution requirements. In flytekit, tasks are typically defined by decorating a Python function, which the framework then transforms into a structured task object capable of running locally or on a remote Flyte cluster.
Declaring Tasks
The primary way to define a task in flytekit is using the @task decorator found in flytekit/core/task.py. This decorator transforms a standard Python function into a PythonFunctionTask.
from flytekit import task
import typing
@task
def greet(name: str) -> str:
return f"Hello, {name}!"
When you apply @task, flytekit uses transform_function_to_interface to inspect the function's type hints and docstrings. This creates a TypedInterface that Flyte uses to validate data flow between tasks.
Task Metadata and Configuration
The @task decorator accepts several parameters to control execution behavior, which are stored in the TaskMetadata class (defined in flytekit/core/base_task.py).
- Retries: Use
retriesto specify how many times Flyte should attempt to re-run the task on failure. - Caching: Enable
cache=Trueand provide acache_versionto avoid redundant computations. - Timeouts: Use
timeout(as an integer ordatetime.timedelta) to limit the maximum execution time. - Resources: Specify
requestsandlimitsusing theResourcesclass to request specific CPU, memory, or GPU allocations.
from datetime import timedelta
from flytekit import task, Resources
@task(
retries=3,
cache=True,
cache_version="1.0",
timeout=timedelta(minutes=5),
requests=Resources(cpu="2", mem="500Mi"),
limits=Resources(cpu="4", mem="1Gi"),
)
def resource_intensive_task(x: int) -> int:
return x * x
Core Task Abstractions
Flytekit uses a hierarchy of classes to manage different task types:
Task: The base class inflytekit/core/base_task.py. it captures theFlyteIDLTaskTemplate information but lacks a Python-native interface.PythonTask: A subclass ofTaskthat introduces apython_interface. It handles the translation between Flyte's internalLiteraltypes and Python native types.PythonFunctionTask: The most common implementation, which wraps a user-defined Python function. It manages the execution logic via itsexecutemethod.
The Execution Lifecycle
When a task is executed, flytekit follows a structured dispatch process implemented in PythonTask.dispatch_execute:
pre_execute: Prepares the execution environment (e.g., setting up a Spark session or modifyingExecutionParameters).- Input Translation: Converts Flyte
LiteralMapinputs into Python native values using_literal_map_to_python_input. execute: Invokes the actual user function with the translated inputs.post_execute: Performs cleanup or output modification.- Output Translation: Converts the Python return values back into a Flyte
LiteralMapvia_output_to_literal_map.
Specialized Task Behaviors
Dynamic Tasks
A task can be marked as dynamic by using the @dynamic decorator (which sets execution_mode to ExecutionBehavior.DYNAMIC). Dynamic tasks allow you to generate new workflow nodes at runtime based on the task's inputs. Internally, PythonFunctionTask.compile_into_workflow is called during execution to produce a DynamicJobSpec.
Eager Tasks
Eager tasks (implemented via EagerAsyncPythonFunctionTask) allow for a more imperative, "Pythonic" style of workflow where tasks are awaited. Unlike standard tasks that return Promise objects during workflow compilation, eager tasks can execute and return actual values by interacting with a Controller that manages sub-executions on the Flyte backend.
Async Tasks
If you decorate an async def function with @task, flytekit instantiates an AsyncPythonFunctionTask. These tasks are executed within an asynchronous loop managed by flytekit's loop_manager.
Task Resolvers
When a task runs on a remote cluster, the container needs to know how to find and load the specific Python function. This is handled by a TaskResolverMixin. The default_task_resolver in flytekit/core/python_auto_container.py works by:
- Capturing the module and function name during serialization.
- Generating
loader_args(e.g.,task-module my_module task-name my_task). - Using
importlibto re-import the task in the execution container.
Constraints and Requirements
- Module Level: Tasks must be defined at the module level so they can be imported by the task resolver. Nested or local functions will raise a
ValueErrorunless they are in a test module (files starting withtest_). - Type Hints: Every input and output must have a type hint. Flytekit uses these to build the
TypedInterface. - Decks: Flyte Decks provide visibility into task execution. While
disable_deckis supported for backward compatibility, you should useenable_deck=Trueto generate HTML reports for inputs, outputs, and system metadata.