This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 890c20b6ecb Fix Cloud Monitoring list operators with explicit output
formats (#73434)
890c20b6ecb is described below
commit 890c20b6ecbd6061ba8dcab6c274c2c626e865ce
Author: ylv01 <[email protected]>
AuthorDate: Tue Sep 22 22:39:13 2026 +0800
Fix Cloud Monitoring list operators with explicit output formats (#73434)
---
.../google/cloud/operators/cloud_monitoring.py | 30 ++++----
.../cloud/operators/test_cloud_monitoring.py | 87 ++++++++++++++++++++++
2 files changed, 103 insertions(+), 14 deletions(-)
diff --git
a/providers/google/src/airflow/providers/google/cloud/operators/cloud_monitoring.py
b/providers/google/src/airflow/providers/google/cloud/operators/cloud_monitoring.py
index 29143ef4e5c..85e586913b6 100644
---
a/providers/google/src/airflow/providers/google/cloud/operators/cloud_monitoring.py
+++
b/providers/google/src/airflow/providers/google/cloud/operators/cloud_monitoring.py
@@ -41,17 +41,16 @@ class
CloudMonitoringListAlertPoliciesOperator(GoogleCloudBaseOperator):
"""
Fetches all the Alert Policies identified by the filter passed as filter
parameter.
- The desired return type can be specified by the format parameter, the
supported
- formats are "dict", "json" and None which returns python dictionary,
stringified
- JSON and protobuf respectively.
+ Returns a list of dictionaries by default, or JSON strings when
``format_="json"``.
.. seealso::
For more information on how to use this operator, take a look at the
guide:
:ref:`howto/operator:CloudMonitoringListAlertPoliciesOperator`
- :param format_: (Optional) Desired output format of the result. The
- supported formats are "dict", "json" and None which returns
- python dictionary, stringified JSON and protobuf respectively.
+ :param format_: (Optional) Desired output format. ``"dict"`` and
``"json"`` return
+ the hook's list of dictionaries or JSON strings unchanged,
respectively.
+ When ``None`` (the default) or any other value, protobuf objects are
converted to dictionaries
+ for XCom serialization.
:param filter_: If provided, this field specifies the criteria that must
be met by alert
policies to be included in the response.
For more details, see
https://cloud.google.com/monitoring/api/v3/sorting-and-filtering.
@@ -148,7 +147,9 @@ class
CloudMonitoringListAlertPoliciesOperator(GoogleCloudBaseOperator):
context=context,
project_id=self.project_id or self.hook.project_id,
)
- return [AlertPolicy.to_dict(policy) for policy in result]
+ if self.format_ not in ("dict", "json"):
+ return [AlertPolicy.to_dict(policy) for policy in result]
+ return result
class CloudMonitoringEnableAlertPoliciesOperator(GoogleCloudBaseOperator):
@@ -477,17 +478,16 @@ class
CloudMonitoringListNotificationChannelsOperator(GoogleCloudBaseOperator):
"""
Fetches all the Notification Channels identified by the filter passed as
filter parameter.
- The desired return type can be specified by the format parameter, the
- supported formats are "dict", "json" and None which returns python
- dictionary, stringified JSON and protobuf respectively.
+ Returns a list of dictionaries by default, or JSON strings when
``format_="json"``.
.. seealso::
For more information on how to use this operator, take a look at the
guide:
:ref:`howto/operator:CloudMonitoringListNotificationChannelsOperator`
- :param format_: (Optional) Desired output format of the result. The
- supported formats are "dict", "json" and None which returns
- python dictionary, stringified JSON and protobuf respectively.
+ :param format_: (Optional) Desired output format. ``"dict"`` and
``"json"`` return
+ the hook's list of dictionaries or JSON strings unchanged,
respectively.
+ When ``None`` (the default) or any other value, protobuf objects are
converted to dictionaries
+ for XCom serialization.
:param filter_: If provided, this field specifies the criteria that
must be met by notification channels to be included in the response.
For more details, see
https://cloud.google.com/monitoring/api/v3/sorting-and-filtering.
@@ -584,7 +584,9 @@ class
CloudMonitoringListNotificationChannelsOperator(GoogleCloudBaseOperator):
context=context,
project_id=self.project_id or self.hook.project_id,
)
- return [NotificationChannel.to_dict(channel) for channel in channels]
+ if self.format_ not in ("dict", "json"):
+ return [NotificationChannel.to_dict(channel) for channel in
channels]
+ return channels
class
CloudMonitoringEnableNotificationChannelsOperator(GoogleCloudBaseOperator):
diff --git
a/providers/google/tests/unit/google/cloud/operators/test_cloud_monitoring.py
b/providers/google/tests/unit/google/cloud/operators/test_cloud_monitoring.py
index 8ef3bf7ac08..660fd69af3b 100644
---
a/providers/google/tests/unit/google/cloud/operators/test_cloud_monitoring.py
+++
b/providers/google/tests/unit/google/cloud/operators/test_cloud_monitoring.py
@@ -20,6 +20,7 @@ from __future__ import annotations
import json
from unittest import mock
+import pytest
from google.api_core.gapic_v1.method import DEFAULT
from google.cloud.monitoring_v3 import AlertPolicy, NotificationChannel
@@ -89,7 +90,63 @@ TEST_NOTIFICATION_CHANNEL_2 = {
}
[email protected]("format_", ["Dict", "protobuf", ""])
[email protected](
+ ("operator_class", "hook_method", "resource"),
+ [
+ (CloudMonitoringListAlertPoliciesOperator, "list_alert_policies",
AlertPolicy(name="test-policy")),
+ (
+ CloudMonitoringListNotificationChannelsOperator,
+ "list_notification_channels",
+ NotificationChannel(name="test-channel"),
+ ),
+ ],
+)
[email protected]("airflow.providers.google.cloud.operators.cloud_monitoring.CloudMonitoringHook",
autospec=True)
+def test_list_operator_converts_unrecognized_format(
+ mock_hook, operator_class, hook_method, resource, format_
+):
+ operator = operator_class(task_id=TEST_TASK_ID, format_=format_)
+ list_resources = getattr(mock_hook.return_value, hook_method)
+ list_resources.return_value = iter([resource])
+
+ result = operator.execute(context=mock.MagicMock(spec=dict))
+
+ assert list_resources.call_args.kwargs["format_"] == format_
+ assert result == [type(resource).to_dict(resource)]
+
+
class TestCloudMonitoringListAlertPoliciesOperator:
+ @pytest.mark.parametrize(
+ ("format_", "policies"),
+ [
+ ("dict", [TEST_ALERT_POLICY_1, TEST_ALERT_POLICY_2]),
+ ("json", [json.dumps(TEST_ALERT_POLICY_1),
json.dumps(TEST_ALERT_POLICY_2)]),
+ ],
+ )
+ @mock.patch(
+
"airflow.providers.google.cloud.operators.cloud_monitoring.CloudMonitoringHook",
autospec=True
+ )
+ def test_execute_preserves_formatted_result(self, mock_hook, format_,
policies):
+ operator = CloudMonitoringListAlertPoliciesOperator(
+ task_id=TEST_TASK_ID, filter_=TEST_FILTER, format_=format_
+ )
+ mock_hook.return_value.list_alert_policies.return_value = policies
+
+ result = operator.execute(context=mock.MagicMock(spec=dict))
+
+ mock_hook.return_value.list_alert_policies.assert_called_once_with(
+ project_id=None,
+ filter_=TEST_FILTER,
+ format_=format_,
+ order_by=None,
+ page_size=None,
+ retry=DEFAULT,
+ timeout=None,
+ metadata=(),
+ )
+ assert result is policies
+
@mock.patch("airflow.providers.google.cloud.operators.cloud_monitoring.CloudMonitoringHook")
def test_execute(self, mock_hook):
operator =
CloudMonitoringListAlertPoliciesOperator(task_id=TEST_TASK_ID,
filter_=TEST_FILTER)
@@ -168,6 +225,36 @@ class TestCloudMonitoringDeleteAlertOperator:
class TestCloudMonitoringListNotificationChannelsOperator:
+ @pytest.mark.parametrize(
+ ("format_", "channels"),
+ [
+ ("dict", [TEST_NOTIFICATION_CHANNEL_1,
TEST_NOTIFICATION_CHANNEL_2]),
+ ("json", [json.dumps(TEST_NOTIFICATION_CHANNEL_1),
json.dumps(TEST_NOTIFICATION_CHANNEL_2)]),
+ ],
+ )
+ @mock.patch(
+
"airflow.providers.google.cloud.operators.cloud_monitoring.CloudMonitoringHook",
autospec=True
+ )
+ def test_execute_preserves_formatted_result(self, mock_hook, format_,
channels):
+ operator = CloudMonitoringListNotificationChannelsOperator(
+ task_id=TEST_TASK_ID, filter_=TEST_FILTER, format_=format_
+ )
+ mock_hook.return_value.list_notification_channels.return_value =
channels
+
+ result = operator.execute(context=mock.MagicMock(spec=dict))
+
+
mock_hook.return_value.list_notification_channels.assert_called_once_with(
+ project_id=None,
+ filter_=TEST_FILTER,
+ format_=format_,
+ order_by=None,
+ page_size=None,
+ retry=DEFAULT,
+ timeout=None,
+ metadata=(),
+ )
+ assert result is channels
+
@mock.patch("airflow.providers.google.cloud.operators.cloud_monitoring.CloudMonitoringHook")
def test_execute(self, mock_hook):
operator =
CloudMonitoringListNotificationChannelsOperator(task_id=TEST_TASK_ID,
filter_=TEST_FILTER)