This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 9076e01351fef0b20b22870e76b2f54a33b56761
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Aug 21 14:16:03 2025 +0100

    [v3-0-test] Ensure that Connection extra can get masked without causing an 
error (#54769) (#54780)
    
    * Ensure that Connection extra can get masked without causing an error
    
    Fixes #54768
    
    This was caused by pydantic#9541 and improper testing on my part. Sorry
    folks. Thsi happens because `Iterable` is too open-ended a type
    
    Yes, this should absolutely have unit tests to go with the fix, but a fix is
    better than nothing, and I'm about to leave on a camping holiday.
    
    * fixup! Ensure that Connection extra can get masked without causing an 
error
    
    * fixup! fixup! Ensure that Connection extra can get masked without causing 
an error
    
    * fixup! fixup! fixup! Ensure that Connection extra can get masked without 
causing an error
    
    ---------
    (cherry picked from commit 5aec867d3c4606aba4e7816a8fb7cd86a406ba54)
    
    Co-authored-by: Ash Berlin-Taylor <[email protected]>
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 task-sdk/src/airflow/sdk/execution_time/comms.py   |  7 ++---
 .../airflow/sdk/execution_time/secrets_masker.py   |  8 ++++--
 .../task_sdk/definitions/test_secrets_masker.py    | 31 ++++++++++++++++++++++
 3 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/task-sdk/src/airflow/sdk/execution_time/comms.py 
b/task-sdk/src/airflow/sdk/execution_time/comms.py
index d0f0eae04c8..41fb1d99da0 100644
--- a/task-sdk/src/airflow/sdk/execution_time/comms.py
+++ b/task-sdk/src/airflow/sdk/execution_time/comms.py
@@ -49,7 +49,7 @@ Execution API server is because:
 from __future__ import annotations
 
 import itertools
-from collections.abc import Iterable, Iterator
+from collections.abc import Iterator
 from datetime import datetime
 from functools import cached_property
 from pathlib import Path
@@ -863,8 +863,9 @@ class MaskSecret(BaseModel):
 
     # This is needed since calls to `mask_secret` in the Task process will 
otherwise only add the mask value
     # to the child process, but the redaction happens in the parent.
-
-    value: str | dict | Iterable
+    # We cannot use `string | Iterable | dict here` (would be more intuitive) 
because bug in Pydantic
+    # https://github.com/pydantic/pydantic/issues/9541 turns iterable into a 
ValidatorIterator
+    value: JsonValue
     name: str | None = None
     type: Literal["MaskSecret"] = "MaskSecret"
 
diff --git a/task-sdk/src/airflow/sdk/execution_time/secrets_masker.py 
b/task-sdk/src/airflow/sdk/execution_time/secrets_masker.py
index 9d75c097bd2..391bf497012 100644
--- a/task-sdk/src/airflow/sdk/execution_time/secrets_masker.py
+++ b/task-sdk/src/airflow/sdk/execution_time/secrets_masker.py
@@ -37,6 +37,10 @@ from typing import (
     overload,
 )
 
+# We have to import this here, as it is used in the type annotations at 
runtime even if it seems it is
+# not used in the code. This is because Pydantic uses type at runtime to 
validate the types of the fields.
+from pydantic import JsonValue  # noqa: TC002
+
 from airflow import settings
 
 if TYPE_CHECKING:
@@ -105,7 +109,7 @@ def should_hide_value_for_key(name):
     return False
 
 
-def mask_secret(secret: str | dict | Iterable, name: str | None = None) -> 
None:
+def mask_secret(secret: JsonValue, name: str | None = None) -> None:
     """
     Mask a secret from appearing in the logs.
 
@@ -475,7 +479,7 @@ class SecretsMasker(logging.Filter):
             else:
                 yield secret_or_secrets
 
-    def add_mask(self, secret: str | dict | Iterable, name: str | None = None):
+    def add_mask(self, secret: JsonValue, name: str | None = None):
         """Add a new secret to be masked to this filter instance."""
         if isinstance(secret, dict):
             for k, v in secret.items():
diff --git a/task-sdk/tests/task_sdk/definitions/test_secrets_masker.py 
b/task-sdk/tests/task_sdk/definitions/test_secrets_masker.py
index e9599af4995..e03266bbf5f 100644
--- a/task-sdk/tests/task_sdk/definitions/test_secrets_masker.py
+++ b/task-sdk/tests/task_sdk/definitions/test_secrets_masker.py
@@ -30,6 +30,7 @@ from unittest.mock import patch
 import pytest
 
 from airflow.models import Connection
+from airflow.sdk.execution_time.comms import MaskSecret
 from airflow.sdk.execution_time.secrets_masker import (
     RedactedIO,
     SecretsMasker,
@@ -545,6 +546,36 @@ class TestMaskSecretAdapter:
         if should_be_masked:
             assert filt.replacer is not None
 
+    @pytest.mark.parametrize(
+        "object_to_mask",
+        [
+            {
+                "key_path": "/files/airflow-breeze-config/keys2/keys.json",
+                "scope": "https://www.googleapis.com/auth/cloud-platform";,
+                "project": "project_id",
+                "num_retries": 6,
+            },
+            ["iter1", "iter2", {"key": "value"}],
+            "string",
+            {
+                "key1": "value1",
+            },
+        ],
+    )
+    def test_mask_secret_with_objects(self, object_to_mask):
+        mask_secret_object = MaskSecret(value=object_to_mask, 
name="test_secret")
+        assert mask_secret_object.value == object_to_mask
+
+    def test_mask_secret_with_list(self):
+        example_dict = ["test"]
+        mask_secret_object = MaskSecret(value=example_dict, name="test_secret")
+        assert mask_secret_object.value == example_dict
+
+    def test_mask_secret_with_iterable(self):
+        example_dict = ["test"]
+        mask_secret_object = MaskSecret(value=example_dict, name="test_secret")
+        assert mask_secret_object.value == example_dict
+
 
 class TestStructuredVsUnstructuredMasking:
     def test_structured_sensitive_fields_always_masked(self):

Reply via email to