Skip to main content

Creating Your First Task

In flyte-sdk, you define units of work by decorating Python functions with the @task decorator. This process transforms a standard function into an AsyncFunctionTaskTemplate, which encapsulates the function logic along with execution metadata like resource requirements, retry strategies, and caching policies.

Prerequisites​

To follow this guide, ensure you have flyte-sdk installed and can import the core components:

import flyte
from flyte import Resources, RetryStrategy

Step 1: Initialize a Task Environment​

Before defining tasks, you must create a TaskEnvironment. This environment acts as a container and configuration provider for your tasks, defining default settings like the Docker image and hardware resources.

# Define an environment for your data processing tasks
data_env = flyte.TaskEnvironment(
name="data_processing",
image="ghcr.io/flyteorg/flytekit:py3.11-latest",
resources=Resources(cpu="2", memory="4Gi")
)

The TaskEnvironment class (found in _task_environment.py) manages the lifecycle and registration of tasks within its scope.

Step 2: Define a Synchronous Task​

To create a task, apply the .task decorator from your environment instance to a Python function. For simple computations, a standard synchronous function is sufficient.

@data_env.task(cache="auto", retries=3)
def square(n: int) -> int:
return n * n

When you apply @data_env.task, flyte-sdk creates an AsyncFunctionTaskTemplate (defined in _task.py). Even though the function is synchronous, the SDK sets _call_as_synchronous = True internally to ensure it is executed correctly within the Flyte engine while still allowing it to be called normally in local scripts.

Step 3: Define an Asynchronous Task​

flyte-sdk natively supports async functions. These are particularly useful for I/O-bound operations or when you need to manage concurrency within a task.

import asyncio

@data_env.task(timeout=60)
async def fetch_data(url: str) -> str:
# Simulate an async network request
await asyncio.sleep(1)
return f"Data from {url}"

For asynchronous functions, the AsyncFunctionTaskTemplate uses await self.func(*args, **kwargs) during its execute method.

Note: If you use a reusable environment (where a Python process is kept alive for multiple tasks), flyte-sdk requires tasks to be async if you want to run them with a concurrency greater than 1. This is enforced in TaskEnvironment.task to prevent blocking the shared process.

Step 4: Overriding Task Configuration​

You can customize individual tasks by passing arguments to the decorator. These arguments override the defaults set in the TaskEnvironment.

@data_env.task(
short_name="heavy_task",
resources=Resources(cpu="4", memory="16Gi"),
retries=RetryStrategy(count=5)
)
def memory_intensive_op(data: list[int]) -> int:
return sum(data)

The TaskTemplate base class handles these overrides, ensuring that specific requirements like pod_template or env_vars are correctly associated with the task template before it is registered in the environment's _tasks dictionary.

Complete Example​

Here is how your task definitions look when combined in a single module:

import flyte
from flyte import Resources
import asyncio

# 1. Setup Environment
env = flyte.TaskEnvironment(name="tutorial_env")

# 2. Define Sync Task
@env.task
def add(a: int, b: int) -> int:
return a + b

# 3. Define Async Task
@env.task
async def process(x: int) -> int:
result = await asyncio.to_thread(lambda: x * 10)
return result

# Local execution works like standard Python
if __name__ == "__main__":
print(f"Sync result: {add(5, 10)}")
print(f"Async result: {asyncio.run(process(5))}")

When you run this code locally, the AsyncFunctionTaskTemplate.forward method is invoked, which simply calls your original function, allowing for easy testing and debugging outside of a Flyte cluster.