From cbd8622c214f309b33b20af52a811a68c3a36f16 Mon Sep 17 00:00:00 2001 From: "jaegwon.seo" <162448493+wornjs@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:33:22 +0900 Subject: [PATCH] Configure Airflow tasks using dbt model meta (#1339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The various dbt models have unique characteristics, and some may require the use of custom pools, queues, or other specific configurations. To support such cases, this update introduces the ability to add necessary information in the meta section of the dbt model.yaml. This metadata is then passed as kwargs to the corresponding Airflow tasks, enabling model-specific customization and enhanced task configuration. **here is sample** DbtTaskGroup - default_args for all dbt models ```python dbt_task_group = DbtTaskGroup( project_config=, profile_config=ProfileConfig, default_args={'pool': dbt_pool} ) ``` ```yaml version: 2 models: - name: name description: description meta: owner: 'jaegwon.seo@toss.im' cosmos: pool: abcd ``` **result** general pool ![스크린샷 2024-11-25 오후 10 15 26](https://github.com/user-attachments/assets/ea492a19-488f-4dbb-b4b9-2be20dc191be) custom pool ![스크린샷 2024-11-25 오후 10 15 40](https://github.com/user-attachments/assets/f6055cc0-94f6-4789-9b05-4e02e34bcd5f) ## Related Issue(s) Closes: #881 Closes: #1325 --- cosmos/airflow/graph.py | 4 ++ cosmos/core/airflow.py | 3 ++ cosmos/core/graph/entities.py | 1 + cosmos/dbt/graph.py | 29 +++++++++++- docs/_static/custom_airflow_pool.png | Bin 0 -> 41559 bytes .../custom-airflow-properties.rst | 33 ++++++++++++++ tests/airflow/test_graph.py | 43 +++++++++++++++++- 7 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 docs/_static/custom_airflow_pool.png create mode 100644 docs/getting_started/custom-airflow-properties.rst diff --git a/cosmos/airflow/graph.py b/cosmos/airflow/graph.py index 6605bf20d..3e3103266 100644 --- a/cosmos/airflow/graph.py +++ b/cosmos/airflow/graph.py @@ -99,6 +99,7 @@ def create_test_task_metadata( extra_context = {} task_owner = "" + airflow_task_config = {} if test_indirect_selection != TestIndirectSelection.EAGER: task_args["indirect_selection"] = test_indirect_selection.value if node is not None: @@ -111,6 +112,7 @@ def create_test_task_metadata( extra_context = {"dbt_node_config": node.context_dict} task_owner = node.owner + airflow_task_config = node.airflow_task_config elif render_config is not None: # TestBehavior.AFTER_ALL task_args["select"] = render_config.select @@ -120,6 +122,7 @@ def create_test_task_metadata( return TaskMetadata( id=test_task_name, owner=task_owner, + airflow_task_config=airflow_task_config, operator_class=calculate_operator_class( execution_mode=execution_mode, dbt_class="DbtTest", @@ -214,6 +217,7 @@ def create_task_metadata( task_metadata = TaskMetadata( id=task_id, owner=node.owner, + airflow_task_config=node.airflow_task_config, operator_class=calculate_operator_class( execution_mode=execution_mode, dbt_class=dbt_resource_to_class[node.resource_type] ), diff --git a/cosmos/core/airflow.py b/cosmos/core/airflow.py index 6f1064649..e25404aed 100644 --- a/cosmos/core/airflow.py +++ b/cosmos/core/airflow.py @@ -32,6 +32,9 @@ def get_airflow_task(task: Task, dag: DAG, task_group: TaskGroup | None = None) if task.owner != "": task_kwargs["owner"] = task.owner + for k, v in task.airflow_task_config.items(): + task_kwargs[k] = v + airflow_task = Operator( task_id=task.id, dag=dag, diff --git a/cosmos/core/graph/entities.py b/cosmos/core/graph/entities.py index 6bf9ff046..cdd5485a6 100644 --- a/cosmos/core/graph/entities.py +++ b/cosmos/core/graph/entities.py @@ -58,6 +58,7 @@ class Task(CosmosEntity): """ owner: str = "" + airflow_task_config: Dict[str, Any] = field(default_factory=dict) operator_class: str = "airflow.operators.empty.EmptyOperator" arguments: Dict[str, Any] = field(default_factory=dict) extra_context: Dict[str, Any] = field(default_factory=dict) diff --git a/cosmos/dbt/graph.py b/cosmos/dbt/graph.py index 6c207f2ca..04a7425e7 100644 --- a/cosmos/dbt/graph.py +++ b/cosmos/dbt/graph.py @@ -13,7 +13,7 @@ from functools import cached_property from pathlib import Path from subprocess import PIPE, Popen -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional from airflow.models import Variable @@ -67,6 +67,33 @@ class DbtNode: has_freshness: bool = False has_test: bool = False + @property + def airflow_task_config(self) -> Dict[str, Any]: + """ + This method is designed to extend the dbt project's functionality by incorporating Airflow-related metadata into the dbt YAML configuration. + Since dbt projects are independent of Airflow, adding Airflow-specific information to the `meta` field within the dbt YAML allows Airflow tasks to + utilize this information during execution. + + Examples: pool, pool_slots, queue, ... + Returns: + Dict[str, Any]: A dictionary containing custom metadata configurations for integration with Airflow. + """ + + if "meta" in self.config: + meta = self.config["meta"] + if "cosmos" in meta: + cosmos = meta["cosmos"] + if isinstance(cosmos, dict): + if "operator_kwargs" in cosmos: + operator_kwargs = cosmos["operator_kwargs"] + if isinstance(operator_kwargs, dict): + return operator_kwargs + else: + logger.error(f"Invalid type: 'operator_kwargs' in meta.cosmos must be a dict.") + else: + logger.error(f"Invalid type: 'cosmos' in meta must be a dict.") + return {} + @property def resource_name(self) -> str: """ diff --git a/docs/_static/custom_airflow_pool.png b/docs/_static/custom_airflow_pool.png new file mode 100644 index 0000000000000000000000000000000000000000..4b4163e66f383944e7bbfde7f93fe507d08473ac GIT binary patch literal 41559 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 F{sVw7-VXo( literal 0 HcmV?d00001 diff --git a/docs/getting_started/custom-airflow-properties.rst b/docs/getting_started/custom-airflow-properties.rst new file mode 100644 index 000000000..90490a099 --- /dev/null +++ b/docs/getting_started/custom-airflow-properties.rst @@ -0,0 +1,33 @@ +.. _custom-airflow-properties: + +Airflow Configuration Overrides with Astronomer Cosmos +====================================================== + +**Astronomer Cosmos** allows you to override Airflow configurations for each dbt task (dbt operator) via the dbt YAML file. + +Sample dbt Model YAML +++++++++++++ + +.. code-block:: yaml + + version: 2 + models: + - name: name + description: description + meta: + cosmos: + operator_args: + pool: abcd + + + + +Explanation +++++++++++++ + +By adding Airflow configurations under **cosmos** in the **meta** field, you can set independent Airflow configurations for each task. +For example, in the YAML above, the **pool** setting is applied to the specific dbt task. +This approach allows for more granular control over Airflow settings per task within your dbt model definitions. + +.. image:: ../_static/custom_airflow_pool.png + :alt: Result of applying Custom Airflow Pool diff --git a/tests/airflow/test_graph.py b/tests/airflow/test_graph.py index d2f943bab..fc0070e8b 100644 --- a/tests/airflow/test_graph.py +++ b/tests/airflow/test_graph.py @@ -62,7 +62,7 @@ depends_on=[parent_node.unique_id], file_path=SAMPLE_PROJ_PATH / "gen3/models/child.sql", tags=["nightly"], - config={"materialized": "table"}, + config={"materialized": "table", "meta": {"cosmos": {"operator_kwargs": {"queue": "custom_queue"}}}}, ) child2_node = DbtNode( @@ -71,7 +71,7 @@ depends_on=[parent_node.unique_id], file_path=SAMPLE_PROJ_PATH / "gen3/models/child2_v2.sql", tags=["nightly"], - config={"materialized": "table"}, + config={"materialized": "table", "meta": {"cosmos": {"operator_kwargs": {"pool": "custom_pool"}}}}, ) sample_nodes_list = [parent_seed, parent_node, test_parent_node, child_node, child2_node] @@ -750,3 +750,42 @@ def test_owner(dbt_extra_config, expected_owner): assert len(output.leaves) == 1 assert output.leaves[0].owner == expected_owner + + +def test_custom_meta(): + with DAG("test-id", start_date=datetime(2022, 1, 1)) as dag: + task_args = { + "project_dir": SAMPLE_PROJ_PATH, + "conn_id": "fake_conn", + "profile_config": ProfileConfig( + profile_name="default", + target_name="default", + profile_mapping=PostgresUserPasswordProfileMapping( + conn_id="fake_conn", + profile_args={"schema": "public"}, + ), + ), + } + build_airflow_graph( + nodes=sample_nodes, + dag=dag, + execution_mode=ExecutionMode.LOCAL, + test_indirect_selection=TestIndirectSelection.EAGER, + task_args=task_args, + render_config=RenderConfig( + test_behavior=TestBehavior.AFTER_EACH, + source_rendering_behavior=SOURCE_RENDERING_BEHAVIOR, + ), + dbt_project_name="astro_shop", + ) + # test custom meta (queue, pool) + for task in dag.tasks: + if task.task_id == "child2_v2_run": + assert task.pool == "custom_pool" + else: + assert task.pool == "default_pool" + + if task.task_id == "child_run": + assert task.queue == "custom_queue" + else: + assert task.queue == "default"