|
27 | 27 | from airflow.models.xcom import XComModel |
28 | 28 | from airflow.providers.standard.operators.empty import EmptyOperator |
29 | 29 | from airflow.providers.standard.operators.python import BranchPythonOperator |
| 30 | +from airflow.sdk import task, task_group |
30 | 31 | from airflow.sdk.bases.xcom import BaseXCom |
31 | 32 | from airflow.ti_deps.dep_context import DepContext |
32 | 33 | from airflow.ti_deps.deps.not_previously_skipped_dep import ( |
33 | 34 | XCOM_SKIPMIXIN_FOLLOWED, |
34 | 35 | XCOM_SKIPMIXIN_KEY, |
| 36 | + XCOM_SKIPMIXIN_SKIPPED, |
35 | 37 | NotPreviouslySkippedDep, |
36 | 38 | ) |
37 | 39 | from airflow.utils.state import State |
@@ -219,6 +221,48 @@ def test_unmapped_parent_skip_mapped_downstream(session, dag_maker): |
219 | 221 | assert tis["op2"].state == State.SKIPPED |
220 | 222 |
|
221 | 223 |
|
| 224 | +def test_parent_in_mapped_task_group_skips_same_map_index(session, dag_maker): |
| 225 | + """ |
| 226 | + A SkipMixin parent inside a mapped task group writes XCom per map index, so |
| 227 | + each child TI in the group must read the decision for its own map index. |
| 228 | + """ |
| 229 | + with dag_maker("test_mapped_group_skip_dag", schedule=None, session=session): |
| 230 | + |
| 231 | + @task.short_circuit(task_id="gate") |
| 232 | + def gate(value): |
| 233 | + return value |
| 234 | + |
| 235 | + @task_group |
| 236 | + def group(value): |
| 237 | + gate(value) >> EmptyOperator(task_id="child") |
| 238 | + |
| 239 | + group.expand(value=[True, False]) |
| 240 | + |
| 241 | + dr = dag_maker.create_dagrun(run_type=DagRunType.MANUAL, state=State.RUNNING) |
| 242 | + tis = {(ti.task_id, ti.map_index): ti for ti in dr.task_instances} |
| 243 | + for map_index in (0, 1): |
| 244 | + tis[("group.gate", map_index)].state = State.SUCCESS |
| 245 | + session.merge(tis[("group.gate", map_index)]) |
| 246 | + # Only the map index 1 gate short-circuited, as SkipMixin.skip records it. |
| 247 | + XComModel.set( |
| 248 | + key=XCOM_SKIPMIXIN_KEY, |
| 249 | + value={XCOM_SKIPMIXIN_SKIPPED: ["group.child"]}, |
| 250 | + dag_id=dr.dag_id, |
| 251 | + task_id="group.gate", |
| 252 | + run_id=dr.run_id, |
| 253 | + map_index=1, |
| 254 | + session=session, |
| 255 | + ) |
| 256 | + session.flush() |
| 257 | + |
| 258 | + dep = NotPreviouslySkippedDep() |
| 259 | + |
| 260 | + assert not dep.is_met(tis[("group.child", 1)], session=session) |
| 261 | + assert tis[("group.child", 1)].state == State.SKIPPED |
| 262 | + assert dep.is_met(tis[("group.child", 0)], session=session) |
| 263 | + assert tis[("group.child", 0)].state != State.SKIPPED |
| 264 | + |
| 265 | + |
222 | 266 | def test_branch_skip_decision_bypasses_custom_xcom_backend(session, dag_maker): |
223 | 267 | """ |
224 | 268 | A value-externalizing custom XCom backend must not break branch-skip of |
|
0 commit comments