Repository: astronomer/astronomer-cosmos
Pull request #1674: Add Airflow 3 compatibility for BiqQuery deferrable support (`ExecutionMode.AIRFLOW_ASYNC`)
Author: login: pankajkoti
Labels: name: area:dependencies
description: Related to dependencies, like Python packages, library versions, etc
name: area:execution
description: Related to the execution environment/mode, like Docker, Kubernetes, Local, VirtualEnv, etc
name: size:M
description: This PR changes 30-99 lines, ignoring generated files.
name: lgtm
description: This PR has been approved by a maintainer
Created: 2025-04-16T17:19:15Z | Merged: 2025-04-17T10:58:02Z

## Changes

- Remove SQLAlchemy session dependency and calls to DB ORM
- Fix base class inheritance for proper object initialisation wrt Airflow 3
- Add symlink in the airflow3 dags for the simple_dag_async.py example DAG
- Set environment variables in airflow3 env to not use setup task for the async DAG until we resolve the Virtualenv execution mode
- Add dbt-bigquery and Google provider to requirements.txt in Airflow 3 setup required for the example DAG


closes: #1634 


Diff:
diff --git a/cosmos/operators/_asynchronous/bigquery.py b/cosmos/operators/_asynchronous/bigquery.py
index 60c2ac6c0a..3b1c8d6fbd 100644
--- a/cosmos/operators/_asynchronous/bigquery.py
+++ b/cosmos/operators/_asynchronous/bigquery.py
@@ -15,7 +15,6 @@
     )
 
 from airflow.utils.context import Context
-from airflow.utils.session import NEW_SESSION, provide_session
 from packaging.version import Version
 
 from cosmos import settings
@@ -25,9 +24,6 @@
 from cosmos.operators.local import AbstractDbtLocalBase
 from cosmos.settings import remote_target_path, remote_target_path_conn_id
 
-if TYPE_CHECKING:  # pragma: no cover
-    from sqlalchemy.orm import Session
-
 AIRFLOW_VERSION = Version(airflow.__version__)
 
 
@@ -98,6 +94,13 @@ def __init__(
             kwargs["outlets"] = [
                 DatasetAlias(name=get_dataset_alias_name(dag_id, task_group_id, self.task_id))
             ]  # type: ignore
+
+        # This is a workaround for Airflow 3 compatibility. In Airflow 2, the super().__init__() call worked correctly,
+        # but in Airflow 3, it attempts to re-initialize AbstractDbtLocalBase with filtered kwargs that only include
+        # BigQueryInsertJobOperator parameters and hence fails to initialise the operator due to missing arguments.
+        # To fix this, we temporarily set the base class to only BigQueryInsertJobOperator during initialization,
+        # then restore the full inheritance chain afterward.
+        DbtRunAirflowAsyncBigqueryOperator.__bases__ = (BigQueryInsertJobOperator,)
         super().__init__(
             gcp_conn_id=self.gcp_conn_id,
             configuration=self.configuration,
@@ -111,6 +114,15 @@ def __init__(
         self.gcp_project = ""
         self.dataset = ""
 
+        # Restore the full inheritance chain after initialization that was temporarily set to BigQueryInsertJobOperator
+        # above for adding compatibility with Airflow 3 operator initialisation. This ensures that:
+        # 1. Subsequent class initializations have access to AbstractDbtLocalBase methods
+        # 2. Operator instances can properly access AbstractDbtLocalBase functionality during execution
+        DbtRunAirflowAsyncBigqueryOperator.__bases__ = (
+            BigQueryInsertJobOperator,
+            AbstractDbtLocalBase,
+        )
+
     @property
     def base_cmd(self) -> list[str]:
         return ["run"]
@@ -154,11 +166,7 @@ def execute(self, context: Context, **kwargs: Any) -> None:
             self.build_and_run_cmd(context=context, run_as_async=True, async_context=self.async_context)
         self._store_template_fields(context=context)
 
-    @provide_session
-    def _store_template_fields(self, context: Context, session: Session = NEW_SESSION) -> None:
-        from airflow.models.renderedtifields import RenderedTaskInstanceFields
-        from airflow.models.taskinstance import TaskInstance
-
+    def _store_template_fields(self, context: Context) -> None:
         if not settings.enable_setup_async_task:
             self.log.info("SQL cannot be made available, skipping registration of compiled_sql template field")
             return
@@ -175,25 +183,7 @@ def _store_template_fields(self, context: Context, session: Session = NEW_SESSIO
         self.gcp_project = profile["project"]
         self.dataset = profile["dataset"]
 
-        # need to refresh the rendered task field record in the db because Airflow only does this
-        # before executing the task, not after
-        ti = context["ti"]
-
-        if isinstance(ti, TaskInstance):  # verifies ti is a TaskInstance in order to access and use the "task" field
-            if TYPE_CHECKING:  # pragma: no cover
-                assert ti.task is not None
-            ti.task.template_fields = self.template_fields
-            rtif = RenderedTaskInstanceFields(ti, render_templates=False)
-
-            # delete the old records
-            session.query(RenderedTaskInstanceFields).filter(
-                RenderedTaskInstanceFields.dag_id == self.dag_id,  # type: ignore[attr-defined]
-                RenderedTaskInstanceFields.task_id == self.task_id,
-                RenderedTaskInstanceFields.run_id == ti.run_id,
-            ).delete()
-            session.add(rtif)
-        else:  # pragma: no cover
-            self.log.info("Warning: ti is of type TaskInstancePydantic. Cannot update template_fields.")
+        self._override_rtif(context=context)
 
     def execute_complete(self, context: Context, event: dict[str, Any]) -> Any:
         """
diff --git a/dev/dags/simple_dag_async.py b/dev/dags/simple_dag_async.py
index d9afba493d..8c9931f27f 100644
--- a/dev/dags/simple_dag_async.py
+++ b/dev/dags/simple_dag_async.py
@@ -6,7 +6,7 @@
 from cosmos.constants import TestBehavior
 from cosmos.profiles import GoogleCloudServiceAccountDictProfileMapping
 
-DEFAULT_DBT_ROOT_PATH = Path(__file__).parent / "dbt"
+DEFAULT_DBT_ROOT_PATH = Path(__file__).resolve().parent / "dbt"
 DBT_ROOT_PATH = Path(os.getenv("DBT_ROOT_PATH", DEFAULT_DBT_ROOT_PATH))
 
 DBT_ADAPTER_VERSION = os.getenv("DBT_ADAPTER_VERSION", "1.9")
@@ -33,7 +33,7 @@
     ),
     render_config=RenderConfig(select=["path:models"], test_behavior=TestBehavior.NONE),
     # normal dag parameters
-    schedule_interval=None,
+    schedule=None,
     start_date=datetime(2023, 1, 1),
     catchup=False,
     dag_id="simple_dag_async",
diff --git a/scripts/airflow3/dags/simple_dag_async.py b/scripts/airflow3/dags/simple_dag_async.py
new file mode 120000
index 0000000000..d3f5df7648
--- /dev/null
+++ b/scripts/airflow3/dags/simple_dag_async.py
@@ -0,0 +1 @@
+../../../dev/dags/simple_dag_async.py
\ No newline at end of file
diff --git a/scripts/airflow3/env.sh b/scripts/airflow3/env.sh
index 2f90f7faeb..ded745c13d 100644
--- a/scripts/airflow3/env.sh
+++ b/scripts/airflow3/env.sh
@@ -19,3 +19,6 @@ export AIRFLOW__CORE__DAGBAG_IMPORT_TIMEOUT=300
 # export AIRFLOW__LOGGING__LOGGING_LEVEL=DEBUG
 export AIRFLOW_REPO_DIR="$PWD/../airflow"
 export AIRFLOW_CONN_EXAMPLE_CONN=postgresql+psycopg2://postgres:postgres@localhost:5432/airflow_db
+# Until we fix the ExecutionMode.VIRTUALENV https://github.com/astronomer/astronomer-cosmos/issues/1633
+export AIRFLOW__COSMOS__ENABLE_SETUP_ASYNC_TASK=False
+export AIRFLOW__COSMOS__ENABLE_TEARDOWN_ASYNC_TASK=False
diff --git a/scripts/airflow3/requirements.txt b/scripts/airflow3/requirements.txt
index d9b73e2a36..21dbaf8e80 100644
--- a/scripts/airflow3/requirements.txt
+++ b/scripts/airflow3/requirements.txt
@@ -6,3 +6,5 @@ apache-airflow-providers-cncf-kubernetes
 psycopg2
 asyncpg
 dbt-postgres
+dbt-bigquery
+apache-airflow-providers-google
diff --git a/tests/operators/_asynchronous/test_bigquery.py b/tests/operators/_asynchronous/test_bigquery.py
index 783fa53e95..ab50e4fe9d 100644
--- a/tests/operators/_asynchronous/test_bigquery.py
+++ b/tests/operators/_asynchronous/test_bigquery.py
@@ -113,15 +113,12 @@ def test_configure_bigquery_async_op_args_missing_sql(async_operator_mock):
 
 
 @patch("cosmos.operators._asynchronous.bigquery.DbtRunAirflowAsyncBigqueryOperator.get_remote_sql")
-@patch("airflow.models.renderedtifields.RenderedTaskInstanceFields")
-def test_store_compiled_sql(mock_rendered_ti, mock_get_remote_sql, profile_config_mock):
+@patch("cosmos.operators._asynchronous.bigquery.DbtRunAirflowAsyncBigqueryOperator._override_rtif")
+def test_store_compiled_sql(mock_override_rtif, mock_get_remote_sql, profile_config_mock):
     from airflow.models.taskinstance import TaskInstance
-    from sqlalchemy.orm import Session
 
     mock_get_remote_sql.return_value = "SELECT * FROM test_table;"
 
-    mock_session = MagicMock(spec=Session)
-
     operator = DbtRunAirflowAsyncBigqueryOperator(
         task_id="test_task",
         project_dir="/path/to/project",
@@ -132,16 +129,13 @@ def test_store_compiled_sql(mock_rendered_ti, mock_get_remote_sql, profile_confi
     mock_task_instance.task = operator
     mock_context = {"ti": mock_task_instance}
 
-    operator._store_template_fields(mock_context, session=mock_session)
+    operator._store_template_fields(mock_context)
     # check if gcp_project and dataset are set after the tasks gets executed
 
     assert operator.compiled_sql == "SELECT * FROM test_table;"
     assert operator.dataset == "test_dataset"
     assert operator.gcp_project == "test_project"
-
-    mock_rendered_ti.assert_called_once()
-    mock_session.add.assert_called_once()
-    mock_session.query().filter().delete.assert_called_once()
+    mock_override_rtif.assert_called()
 
 
 @patch("cosmos.operators._asynchronous.bigquery.DbtRunAirflowAsyncBigqueryOperator._store_template_fields")


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds Airflow 3 compatibility for BigQuery deferrable support by removing the SQLAlchemy session dependency and updating the operator's inheritance, as well as adjusting the example DAG configuration.  
- Removed SQLAlchemy session dependency and related DB ORM calls  
- Updated base class initialization for DbtRunAirflowAsyncBigqueryOperator  
- Added a symlink and updated settings for the Airflow 3 asynchronous DAG example

### Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.

| File | Description |
| ---- | ----------- |
| tests/operators/_asynchronous/test_bigquery.py | Updated test to patch _override_rtif instead of session-dependent RenderedTaskInstanceFields |
| scripts/airflow3/dags/simple_dag_async.py | Added a symlink to the example DAG in the dev directory |
| dev/dags/simple_dag_async.py | Modified path resolution, dataset reference, and schedule parameter for Airflow 3 |
| cosmos/operators/_asynchronous/bigquery.py | Removed session dependency and restructured inheritance for Airflow 3 compatibility |

<details>
<summary>Files not reviewed (2)</summary>

* **scripts/airflow3/env.sh**: Language not supported
* **scripts/airflow3/requirements.txt**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**cosmos/operators/_asynchronous/bigquery.py:110**
* Reassigning __bases__ here overrides the previous assignment, which may introduce unintended behavior in the operator's inheritance. Consolidate the modifications into a single base class assignment for clarity.
```
DbtRunAirflowAsyncBigqueryOperator.__bases__ = (BigQueryInsertJobOperator, AbstractDbtLocalBase,)
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T08:21:03Z
user: login: tatiana
body: Excellent work, @pankajkoti !
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-17T09:24:53Z
user: login: pankajastro
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:17:28Z
user: login: pankajastro
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:17:44Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:24:54Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:25:17Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:27:49Z
user: login: pankajastro
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:34:27Z
user: login: pankajastro
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:36:31Z
user: login: pankajastro
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:38:24Z
user: login: tatiana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T10:39:05Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:44:55Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:55:44Z
user: login: pankajkoti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:58:08Z

Review comments:
diff_hunk: @@ -98,6 +94,7 @@ def __init__(
             kwargs["outlets"] = [
                 DatasetAlias(name=get_dataset_alias_name(dag_id, task_group_id, self.task_id))
             ]  # type: ignore
+        DbtRunAirflowAsyncBigqueryOperator.__bases__ = (BigQueryInsertJobOperator,)
path: cosmos/operators/_asynchronous/bigquery.py
user: login: Copilot
body: The base class assignment is modified twice in the __init__ method. It is recommended to merge these assignments into a single, clear statement to prevent potential conflicts.
created_at: 2025-04-17T08:21:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,6 +107,10 @@ def __init__(
         self.compiled_sql = ""
         self.gcp_project = ""
         self.dataset = ""
+        DbtRunAirflowAsyncBigqueryOperator.__bases__ = (
path: cosmos/operators/_asynchronous/bigquery.py
user: login: tatiana
body: It would be great if we added a comment on why we're redefining `__bases__` twice, potentially with some example of error message that lead to this solution
created_at: 2025-04-17T09:14:44Z
author_association: COLLABORATOR
diff_hunk: @@ -18,3 +18,8 @@ export AIRFLOW__CORE__DAGBAG_IMPORT_ERROR_TRACEBACK_DEPTH=10
 export AIRFLOW__CORE__DAGBAG_IMPORT_TIMEOUT=300
 # export AIRFLOW__LOGGING__LOGGING_LEVEL=DEBUG
 export AIRFLOW_REPO_DIR="$PWD/../airflow"
+
+
+# Until we fix the ExecutionMode.VIRTUALENV
+export AIRFLOW__COSMOS__ENABLE_SETUP_ASYNC_TASK=False
path: scripts/airflow3/env.sh
user: login: tatiana
body: It is probably worth to add a comment on https://github.com/astronomer/astronomer-cosmos/issues/1633 about this
created_at: 2025-04-17T09:22:14Z
author_association: COLLABORATOR
diff_hunk: @@ -175,25 +172,7 @@ def _store_template_fields(self, context: Context, session: Session = NEW_SESSIO
         self.gcp_project = profile["project"]
         self.dataset = profile["dataset"]
 
-        # need to refresh the rendered task field record in the db because Airflow only does this
-        # before executing the task, not after
-        ti = context["ti"]
-
-        if isinstance(ti, TaskInstance):  # verifies ti is a TaskInstance in order to access and use the "task" field
-            if TYPE_CHECKING:  # pragma: no cover
-                assert ti.task is not None
-            ti.task.template_fields = self.template_fields
-            rtif = RenderedTaskInstanceFields(ti, render_templates=False)
-
-            # delete the old records
-            session.query(RenderedTaskInstanceFields).filter(
-                RenderedTaskInstanceFields.dag_id == self.dag_id,  # type: ignore[attr-defined]
-                RenderedTaskInstanceFields.task_id == self.task_id,
-                RenderedTaskInstanceFields.run_id == ti.run_id,
-            ).delete()
-            session.add(rtif)
-        else:  # pragma: no cover
-            self.log.info("Warning: ti is of type TaskInstancePydantic. Cannot update template_fields.")
+        self._override_rtif(context=context)
path: cosmos/operators/_asynchronous/bigquery.py
user: login: tatiana
body: Really exciting to see this using #1661 - much cleaner and easier to maintain!
created_at: 2025-04-17T09:24:34Z
author_association: COLLABORATOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajastro
body: Curious, why are we changing the project?
created_at: 2025-04-17T10:17:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,6 +107,10 @@ def __init__(
         self.compiled_sql = ""
         self.gcp_project = ""
         self.dataset = ""
+        DbtRunAirflowAsyncBigqueryOperator.__bases__ = (
path: cosmos/operators/_asynchronous/bigquery.py
user: login: pankajkoti
body: yes, added comments to explain those. please check. 
created_at: 2025-04-17T10:24:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,3 +18,8 @@ export AIRFLOW__CORE__DAGBAG_IMPORT_ERROR_TRACEBACK_DEPTH=10
 export AIRFLOW__CORE__DAGBAG_IMPORT_TIMEOUT=300
 # export AIRFLOW__LOGGING__LOGGING_LEVEL=DEBUG
 export AIRFLOW_REPO_DIR="$PWD/../airflow"
+
+
+# Until we fix the ExecutionMode.VIRTUALENV
+export AIRFLOW__COSMOS__ENABLE_SETUP_ASYNC_TASK=False
path: scripts/airflow3/env.sh
user: login: pankajkoti
body: added a comment there https://github.com/astronomer/astronomer-cosmos/issues/1633#issuecomment-2812428496
created_at: 2025-04-17T10:25:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajkoti
body: I observed we have the same project across all our example DAGs, so altered it for consistency.
created_at: 2025-04-17T10:27:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajastro
body: Look like `jaffle_shop` does not have bigquery profile, which may require here? https://github.com/astronomer/astronomer-cosmos/blob/main/dev/dags/dbt/jaffle_shop/profiles.yml
created_at: 2025-04-17T10:34:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajastro
body: ohh it uses profile mapping
created_at: 2025-04-17T10:36:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: tatiana
body: @pankajastro  @pankajkoti Some of the example DAGs use the `original_jaffle_shop`, others use `jaffle_shop` and others `altered_jaffle_shop`, which was altered and has a different DAG topology than the one in our docs.

There were changes related to this in #1374.

I'm not sure oft he difference between `original_jaffle_shop` and `jaffle_shop` - but it feels out of the scope of this PR to try to normalise this.

 We need to clean this up and make it consistent, but I suggest you don't change the dbt project as part of this PR. Ideally, we'd have a single copy of the Jaffle Shop project, and have dedicated examples of dbt projects just with the other features we want to test (e.g. sources and others). Could we log a follow up ticket for this?
created_at: 2025-04-17T10:39:05Z
author_association: COLLABORATOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajkoti
body: yes, it uses profile_mapping and I reverified by running the example DAG with the profile not having an entry in the profiles.yml, so I believe we're safe. I believe we generate the profile at runtime using profilemapping, so we're safe
created_at: 2025-04-17T10:44:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajkoti
body: ```suggestion
        DBT_ROOT_PATH / "original_jaffle_shop",
```
created_at: 2025-04-17T10:55:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@
 simple_dag_async = DbtDag(
     # dbt/cosmos-specific parameters
     project_config=ProjectConfig(
-        DBT_ROOT_PATH / "original_jaffle_shop",
+        DBT_ROOT_PATH / "jaffle_shop",
path: dev/dags/simple_dag_async.py
user: login: pankajkoti
body: based on the discussions, I reverted the project to use `original_jaffle_shop`. We can work on it in a separate PR to get rid of original_jaffle_shop as suggested by @tatiana .
created_at: 2025-04-17T10:58:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-04-16T17:19:38Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *sunny-pastelito-5ecb04* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 143d902c9d9b6bdb16bc48b4e13e6f2c7c47f944 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/sunny-pastelito-5ecb04/deploys/67ffe69617f2a0000893df10 |
author_association: NONE
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-04-16T17:19:38Z
body: ## Deploying astronomer-cosmos with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>27a9681</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://d471e8ff.astronomer-cosmos.pages.dev'>https://d471e8ff.astronomer-cosmos.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://fix-async-bigquery-af3.astronomer-cosmos.pages.dev'>https://fix-async-bigquery-af3.astronomer-cosmos.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/799f6c6de390adc4994d0b9621414a76/pages/view/astronomer-cosmos/d471e8ff-ffc8-472e-9f50-a0cfda8a6e9a)

author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-16T17:20:16Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *sunny-pastelito-5ecb04* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 27a96814e803912cabdbe19700ea233ba02cb3db |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/sunny-pastelito-5ecb04/deploys/6800de3742c0e400084c497b |
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-16T18:08:34Z
body: ## [Codecov](https://app.codecov.io/gh/astronomer/astronomer-cosmos/pull/1674?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astronomer) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.08%. Comparing base [(`5ae181e`)](https://app.codecov.io/gh/astronomer/astronomer-cosmos/commit/5ae181e02e7696d82c51fb4f628abbdc25e41c8e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astronomer) to head [(`27a9681`)](https://app.codecov.io/gh/astronomer/astronomer-cosmos/commit/27a96814e803912cabdbe19700ea233ba02cb3db?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astronomer).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1674      +/-   ##
==========================================
- Coverage   97.09%   97.08%   -0.01%     
==========================================
  Files          80       80              
  Lines        5022     5015       -7     
==========================================
- Hits         4876     4869       -7     
  Misses        146      146              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/astronomer/astronomer-cosmos/pull/1674?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astronomer).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astronomer).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE