Visual grouping — task_groups¶
Collapsible folders in the Airflow grid view, one per dbt layer. Independent of
runner routing — task_groups controls which UI folder a task lives in,
not which runner executes it.
dbt_project__all_runners_mix
├─ ▸ bronze (collapsible) -- every model carrying tag:bronze
├─ ▸ silver (collapsible) -- every model carrying tag:silver
├─ ▸ gold (collapsible) -- every model carrying tag:gold
└─ ▸ other (collapsible) -- everything else (fallback)
Python¶
from dbt_aws.common import TaskGroupConfig, TaskGroupingConfig
TASK_GROUPS = TaskGroupingConfig(
groups=(
TaskGroupConfig(name="bronze", tags=frozenset({"bronze"})),
TaskGroupConfig(name="silver", tags=frozenset({"silver"})),
TaskGroupConfig(name="gold", tags=frozenset({"gold"})),
TaskGroupConfig(name="dimensions", tags=frozenset({"dim", "scd"})),
),
ungrouped_group="other", # fallback name for unmatched nodes
# ungrouped_group=None puts unmatched at the DAG root
)
dag = DbtDag(
...,
task_groups=TASK_GROUPS,
)
YAML¶
task_groups:
- name: bronze
tags: [bronze]
- name: silver
tags: [silver]
- name: gold
tags: [gold]
- name: dimensions
tags: [dim, scd]
# Optional. Omit to leave unmatched models at the DAG root.
ungrouped_group: other
Validation¶
| Failure | Result |
|---|---|
| Same tag listed in two groups | RunnerConfigError at config load |
Group with empty tags |
Error at config load |
| Node tagged with two tags from different groups | ValueError at DAG-parse — node must belong to exactly one group |
| Duplicate group name | Error at config load |
Composing with tag_runners¶
The two features are completely independent and compose freely. The most common pattern is to keep the same tag set driving both:
# Tags drive both routing AND visual grouping.
TAG_RUNNERS = {
"bronze": "glue_spark",
"silver,gold": "session_warm",
}
TASK_GROUPS = TaskGroupingConfig(
groups=(
TaskGroupConfig(name="bronze", tags=frozenset({"bronze"})),
TaskGroupConfig(name="silver", tags=frozenset({"silver"})),
TaskGroupConfig(name="gold", tags=frozenset({"gold"})),
),
ungrouped_group="other",
)
dag = DbtDag(..., tag_runners=TAG_RUNNERS, task_groups=TASK_GROUPS)
A model tagged bronze will:
- Be routed to
glue_spark(becausetag_runners["bronze"]="glue_spark") - Be visually placed in the
bronzeTaskGroup (becausetask_groups.bronze.tagscontainsbronze)
But you don't have to align them — a model can route to glue_spark AND live in the
silver UI folder if you want.
Custom group hierarchy¶
TaskGroupingConfig produces a flat set of TaskGroups (one level under the DAG root).
For nested hierarchies (e.g. bronze/raw, bronze/transformed), use the lower-level
DbtTaskGroup + your own nesting:
with DAG(dag_id="hybrid", ...) as dag:
with TaskGroup(group_id="warehouse"):
with TaskGroup(group_id="bronze"):
DbtTaskGroup(group_id="raw", project=..., select=["tag:bronze,tag:raw"], ...)
DbtTaskGroup(group_id="transformed", project=..., select=["tag:bronze,tag:transformed"], ...)
with TaskGroup(group_id="silver"):
DbtTaskGroup(group_id="all", project=..., select=["tag:silver"], ...)
Each DbtTaskGroup parses the manifest, applies its own select=, and emits one task per
matching node. Edges between sibling TaskGroups are wired automatically.