TaskFlow automatically suffixes pretty close to this out of the box — I think 
without the override we’d end up with my_task, my_task__1, my_task__2, 
my_task__3 etc. 
https://github.com/apache/airflow/blob/376cecdb9f258fdb6f81f264c48f281c1cd2aeb5/task-sdk/src/airflow/sdk/bases/decorator.py#L111-L150

-a

> On 16 Jun 2026, at 10:59, Tzu-ping Chung via dev <[email protected]> 
> wrote:
> 
> The loop would not work as-is (since it’d create multiple tasks with the same 
> id). But as currently designed, you CAN set multiple result tasks on a dag. 
> The result is always a dict keyed by tsk_id. So this slightly modified example
> 
> @dag
> def my_dag():
>    @task
>     def t(x):
>         return x
>    @result
>    @task
>     def my_task(num):
>         return num*2 
>    for i in range(4):
>        my_task.override(task_id=f"my_task_{i}")(t(i))
> 
> Would have the dag result
> 
>    {
>      "my_task_0": 0,
>      "my_task_1": 2,
>      "my_task_2": 4,
>      "my_task_3": 6,
>    }
> 
> 
>> On 16 Jun 2026, at 17:34, Ephraim Anierobi <[email protected]> 
>> wrote:
>> 
>> Hi TP, 
>> 
>> Thanks for bringing up this discussion. 
>> 
>> I feel like `@result @task` is clean, however, it won't be clear what the 
>> Dag's result is if the task is invoked multiple times in a dag. 
>> Take for example:
>> 
>> @dag
>> def my_dag():
>>    @task
>>     def t(x):
>>         return x
>>    @result
>>    @task
>>     def my_task(num):
>>         return num*2 
>>    for i in range(4):
>>        my_task(t(i))
>> 
>> Unless I'm not understanding the @result well, but I feel like this means, 
>> every invocation of `my_task` is a result of the dag.
>> 
>> If result is intended to be singular, I will prefer value inference from the 
>> dag:
>> 
>> @dag
>> def my_dag():
>>    @task
>>        def my_task():
>>            return 1
>>    return my_task()
>> 
>> AND
>> 
>> with DAG(...) as dag:
>>    output = f()
>>    dag.add_result(output)
>> 
>> Thanks
>> - Ephraim
>> 
>> On Tue, 16 Jun 2026 at 08:37, Tzu-ping Chung via dev 
>> <[email protected]> wrote:
>> Hi all,
>> 
>> I’m currently working on the [Synchronous Dag Execution] feature and trying 
>> to gather opinions on how the Taskflow API should work when we want to mark 
>> a task as the dag’s “result task” (i.e. “the return value is a final output 
>> of the dag, not an intermediate value”).
>> 
>> [Synchronous Dag Execution]: https://github.com/apache/airflow/issues/51711
>> 
>> ## Prior art (kind of)
>> 
>> We currently have the setup/teardown Taskflow API like this:
>> 
>>    @setup
>>    def f1(): ...
>> 
>>    @task
>>    def f2(): ...
>> 
>>    setup1 = f1()  # This is a setup task.
>> 
>>    t2 = f2()  # This is a normal task.
>>    setup2 = t2.as_setup()  # This is a setup task.
>> 
>> A teardown variant also exists for both cases.
>> 
>> ## The decorator syntax
>> 
>> The most straightforward syntax would be to have a @result decorator on a 
>> plain Python function. However, I don’t like this since a result task still 
>> has all the same arguments as a non-result task. Setup and teardown tasks 
>> don’t accept most task arguments. If @result needs to work on a plain 
>> function, it would need to duplicate and forward all the arguments on @task. 
>> I feel we can avoid this redundancy by requiring @result to be used ON TOP 
>> OF @task instead:
>> 
>>    @result
>>    @task(put your arguments here...)
>>    def f(): ...
>> 
>> We COULD also make using @result without @task a shorthand to argument-less 
>> calls (which is probably common?)
>> 
>>    # This...
>>    @result
>>    def f(): ...
>> 
>>    # Is equivalent to...
>>    @result
>>    @task
>>    def f(): ...
>> 
>> Alternatively, we could use a fluent interface:
>> 
>>    @task(arguments here...).result
>>    def f(): ...
>> 
>> Pro: avoids needing a top-level name. Con: Not a common pattern in Airflow.
>> 
>> ## The method syntax
>> 
>> I don’t think adding a method similar to as_setup/teardown makes sense here. 
>> It makes sense for setup/teardown because it allows the same body of code to 
>> be BOTH a setup/teardown task AND a normal task at the same time, as shown 
>> above. This does not make sense for a result task—a task either returns the 
>> result, or it doesn’t. If we want a method-based syntax, it makes more sense 
>> to have a method on the dag:
>> 
>>    with DAG(...) as dag:
>>        @task
>>        def f():
>> 
>>        t = f()
>>        dag.add_result(t)
>> 
>> ## For @dag decorator
>> 
>> One more syntax that only makes sense here is we can automatically detect 
>> the return value of an @dag-decorated function:
>> 
>>    @dag
>>    def my_dag():
>>        @task
>>        def f1(): ...
>> 
>>        @task
>>        def f2(v): ...
>> 
>>        result = f2(f1())
>> 
>>        return result  # Marks f2 as the result task!
>> 
>> ---------------
>> 
>> Looking forward to hearing thoughts on the above, and more ideas on possible 
>> syntaxes.
>> 
>> TP
>> 
>> 
>> 
>> 
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [email protected]
>> For additional commands, e-mail: [email protected]
>> 
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
> 

Reply via email to