kaxil commented on code in PR #75874:
URL: https://github.com/apache/airflow/pull/75874#discussion_r4516917568


##########
providers/standard/src/airflow/providers/standard/sensors/external_task.py:
##########
@@ -680,6 +688,9 @@ class ExternalTaskMarker(EmptyOperator):
 
     ui_color = "#4db7db"
     operator_extra_links = [ExternalDagLink()]
+    # Where Airflow can follow a recorded clear target, the marker runs on a 
worker so that its templates
+    # render there and the resolved target gets recorded, instead of being 
marked successful unrun.
+    inherits_from_empty_operator = ExternalClearTarget is None

Review Comment:
   Done in 397243fa606. A marker whose IDs have no Jinja, and whose 
`logical_date` is unset or a literal, stays an empty operator, and its target 
is stored with the Dag version. Core reads it from there and prefers it over a 
reported one, so only templated markers run on a worker now. The check uses the 
Dag's own Jinja env, so custom delimiters count, and anything that isn't a 
string (a callable, `literal()`) still goes to the worker.



##########
airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py:
##########
@@ -593,6 +603,232 @@ def _patch_task_instance_note(
                 ti.task_instance_note.user_id = user.get_id()
 
 
+EXTERNAL_CLEAR_TARGET_XCOM_KEY = "_external_clear_target"
+"""XCom key the Task SDK's task runner records 
``ExternalClearTarget.serialize()`` under."""
+
+
+class ExternalClearDepthExceeded(AirflowException):
+    """A clear followed more levels of cross-Dag clear targets than the 
starting task allows."""
+
+
+class _RecordedClearTarget(BaseModel):
+    """A clear target as a worker recorded it. Validated, since any task can 
write the XCom key."""
+
+    model_config = ConfigDict(frozen=True)
+
+    dag_id: str
+    task_id: str
+    logical_date: AwareDatetime
+    recursion_depth: PositiveInt
+
+
+@dataclass(frozen=True)
+class DependentClear:
+    """What clearing pulls in from one run of another Dag."""
+
+    dag: SerializedDAG
+    run_id: str
+    task_ids: frozenset[str]
+    exclude_task_ids: frozenset[tuple[str, int]]
+    task_instances: list[TI]
+
+
+def _get_recorded_targets(ti_ids: Iterable[UUID], *, session: Session) -> 
list[_RecordedClearTarget]:
+    read = build_xcom_read_query(
+        producer_ids=select(TI.id).where(TI.id.in_(ti_ids)), 
key=EXTERNAL_CLEAR_TARGET_XCOM_KEY

Review Comment:
   Done. If the live attempt hasn't succeeded yet, core uses the latest report 
from an earlier attempt of the same execution. Once it succeeds, only its own 
report counts, so a rerun that reports nothing doesn't keep the old target. 
`test_second_clear_before_the_marker_reruns_still_clears_dependent_dag_tasks` 
covers it through the route. Untemplated markers don't hit this at all now, 
since their target lives in the Dag version.



##########
task-sdk/src/airflow/sdk/execution_time/task_runner.py:
##########
@@ -2485,6 +2487,17 @@ def _render_map_index(context: Context, ti: 
RuntimeTaskInstance, log: Logger) ->
     return rendered_map_index
 
 
+def _record_external_clear_target(context: Context, ti: RuntimeTaskInstance, 
log: Logger) -> None:
+    """Record the task's resolved cross-Dag clear target, so the API server 
can follow it on clear."""
+    if not isinstance(ti.task, SupportsExternalClearTarget):
+        return
+    if (target := ti.task.get_external_clear_target(context)) is None:
+        return
+    log.debug("Recording external clear target", target=target)
+    # Straight to the metadata DB, like operator extra links: the API server 
reads it there.
+    _xcom_push_to_db(ti, key=EXTERNAL_CLEAR_TARGET_XCOM_KEY, 
value=target.serialize())

Review Comment:
   Done. The runner now sends the target with the success report 
(`external_clear_target` on `TISuccessStatePayload`, added in the 2027-02-28 
version), and the API server validates it and writes the record itself. Both 
the Execution API and the public XCom API reject the key, and 
`exclude_internal_keys` hides it.



##########
airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py:
##########
@@ -593,6 +603,232 @@ def _patch_task_instance_note(
                 ti.task_instance_note.user_id = user.get_id()
 
 
+EXTERNAL_CLEAR_TARGET_XCOM_KEY = "_external_clear_target"
+"""XCom key the Task SDK's task runner records 
``ExternalClearTarget.serialize()`` under."""

Review Comment:
   Gone with the change above, only core knows the key now 
(`airflow.models.xcom`).



-- 
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]

Reply via email to