fredthomsen opened a new pull request, #69426:
URL: https://github.com/apache/airflow/pull/69426
> This PR replaces #62079, which was closed and could not be re-opened. The
branch has been rebased onto the latest `main`.
Previously, adding extra links to a TaskFlow task required creating
a custom BaseOperator subclass with operator_extra_links and a
companion BaseOperatorLink class. This is heavyweight for what is
often just a URL set at runtime.
This change lets @task authors declare link names in the decorator
and set URLs directly during execution:
```python
@task(extra_links=["Docs"])
def my_task(extra_links):
extra_links["Docs"] = "https://example.com"
```
**What's included:**
- `TaskFlowExtraLink` — a concrete `BaseOperatorLink` that stores
URLs via in-memory, created at parse time from declared link names
- `_build_extra_links()` merges TaskFlow links with existing
class-level links, warning on name conflicts
- `ExtraLinksAccessor` — dict-like context object injected as
`context["extra_links"]` for setting URLs during execution
- Mapped operators retain extra links through `unmap()`
- Serialization/deserialization works via existing `operator_extra_links`
machinery (no new XCom keys or API changes needed)
closes: #54527
Simple DAGs used to verify this solution:
```
from airflow.decorators import dag, task
@dag
def taskflow_extra_links():
"""
Vanilla test.
"""
@task(extra_links=["Docs", "Home"])
def process_data(extra_links):
extra_links["Docs"] = "https://airflow.apache.org/docs/"
extra_links["Home"] = "https://airflow.apache.org/"
process_data()
taskflow_extra_links()
@dag
def taskflow_extra_links_mapped():
"""
Task expansion test.
"""
@task(extra_links=["Map"])
def process_data(num, extra_links, ti):
extra_links["Map"] = f"https://example.com/{ti.map_index}/"
process_data.expand(num=[1,2,3])
taskflow_extra_links_mapped()
@dag
def taskflow_extra_links_undeclared():
"""
This should blow up.
"""
@task
def process_data(extra_links):
extra_links["Home"] = "invalid"
process_data()
taskflow_extra_links_undeclared()
@dag
def taskflow_extra_links_virtualenv():
@task.virtualenv(extra_links=["Venv"])
def process_data(extra_links):
extra_links["Venv"] = "https://example.com/virtualenv/"
process_data()
taskflow_extra_links_virtualenv()
```
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Claude Opus 4.6)
Generated-by: Claude Code following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
---
* Read the **[Pull Request
Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)**
for more information. Note: commit author/co-author name and email in commits
become permanently public when merged.
* For fundamental code changes, an Airflow Improvement Proposal
([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals))
is needed.
* When adding dependency, check compliance with the [ASF 3rd Party License
Policy](https://www.apache.org/legal/resolved.html#category-x).
* For significant user-facing changes create newsfragment:
`{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in
[airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]