Task authoring and execution
Flyte tasks are the fundamental building blocks of workflows in flytekit. They represent a single unit of execution, characterized by a strong interface (typed inputs and outputs) and declarative configuration.
Declaring Tasks
The most common way to define a task is by using the @task decorator on a Python function. flytekit automatically detects the interface from the function's type hints and docstrings.
from flytekit import task
import typing
@task
def greet(name: str) -> str:
"""
A simple task that greets a user.
:param name: The name of the person to greet.
:return: A greeting string.
"""
return f"Hello, {name}!"
When you decorate a function with @task, flytekit creates an instance of PythonFunctionTask. This class handles the translation between Flyte's type system and Python native types, as well as the execution logic.
Task Configuration
The @task decorator accepts several parameters to control execution behavior, resource allocation, and metadata:
- Retries: Use
retriesto specify how many times the task should be retried on failure. - Caching: Enable caching with
cache=Trueand provide acache_version. flytekit uses theTaskMetadataclass to manage these settings. - Resources: Request specific compute resources using
requestsandlimitswith theResourcesclass. - Timeout: Set a maximum execution duration using
timeout(either an integer in seconds or adatetime.timedelta).
from datetime import timedelta
from flytekit import task, Resources
@task(
retries=3,
cache=True,
cache_version="1.0",
requests=Resources(cpu="1", mem="500Mi"),
timeout=timedelta(minutes=5)
)
def resource_intensive_task(data: typing.List[int]) -> int:
return sum(data)
Task Abstractions
Internally, flytekit uses a hierarchy of classes to represent different task types:
Task: The base class inflytekit.core.base_task. it captures information defined in the FlyteIDLTaskTemplate.PythonTask: Inherits fromTaskand adds support for Python native interfaces. It includes methods likeget_input_types()anddispatch_execute().PythonFunctionTask: The primary class for tasks defined via the@taskdecorator. It wraps a user-defined Python function and manages its execution.
Task Plugins
For tasks that require specialized backends (like Spark, SQL, or specialized Kubernetes Pods), flytekit uses a plugin system. You can pass a configuration object to the task_config parameter of the @task decorator.
# Example using a hypothetical Spark plugin
@task(task_config=Spark(spark_conf={"spark.executor.memory": "2g"}), retries=2)
def spark_task(x: int) -> str:
...
The TaskPlugins factory in flytekit.core.task manages these plugins, mapping configuration types to specific PythonFunctionTask implementations.
Data Artifacts
Tasks can produce and consume versioned, partitioned data artifacts. This allows for fine-grained data tracking and triggering. You use Annotated type hints and the Artifact class to declare these dependencies.
from typing import Annotated
from flytekit import task, Artifact
import pandas as pd
Pricing = Artifact(name="pricing", partition_keys=["region"])
@task
def get_pricing(region: str) -> Annotated[pd.DataFrame, Pricing]:
df = pd.DataFrame({"price": [10, 20]})
# Bind the partition value at runtime
return Pricing.create_from(df, region=region)
The create_from method on the Artifact class interacts with the FlyteContext's output_metadata_tracker to attach partition metadata to the task output.
Execution Modes
flytekit supports several specialized execution behaviors beyond standard synchronous tasks:
Dynamic Tasks
Dynamic tasks allow you to generate a sub-workflow at runtime based on task inputs. You use the @dynamic decorator (which sets execution_mode=ExecutionBehavior.DYNAMIC). Internally, PythonFunctionTask.compile_into_workflow generates a DynamicJobSpec that Flyte Propeller executes.
Async and Eager Tasks
AsyncPythonFunctionTask: Used when the decorated function is a coroutine (async def).EagerAsyncPythonFunctionTask: Implements "eager workflows" where Python code acts as the orchestrator, and each task call creates a new execution on the Flyte cluster. This is enabled by settingis_eager=TrueinTaskMetadata.
Local Execution
When you call a task locally (e.g., in a unit test), flytekit invokes local_execute. This method:
- Translates Python native inputs into Flyte literals using
translate_inputs_to_literals. - Checks the
LocalTaskCacheif caching is enabled. - Invokes
sandbox_execute, which eventually calls the user'sexecutemethod. - Wraps the results back into
Promiseobjects or native types.
# Local execution is as simple as calling the function
result = greet(name="Flyte User")
assert result == "Hello, Flyte User!"
Task Resolvers
When a task runs on a remote Flyte cluster, the container needs to know how to find and load the Python task object. This is handled by TaskResolverMixin. The default_task_resolver identifies tasks by their module and function name, allowing pyflyte-execute to rehydrate the task object at runtime.