This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit d98fca04d3f05aa611d5a6e14c804d401ac7aae2
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Nov 3 16:17:50 2026 +0000
[v3-4-test] Inline the masked Variable merge and drop unused merge depth
args (#76306) (#76378)
* Simplify the masked Variable merge and drop unused merge depth arguments
The restore runs from a single place, so keeping it inline mirrors the
Connection update it is modelled on. Merging now walks every depth like
redaction does, so the depth arguments threaded through _merge no longer
bound anything and only suggested a cutoff that does not exist.
* Read the stored Variable value once and clarify the merge max_depth
docstring
(cherry picked from commit 9a6ef0a4217c23cfe360fd1d26a6ecf6366166b0)
Co-authored-by: Pierre Jeambrun <[email protected]>
---
.../core_api/services/public/variables.py | 33 ++++++++--------------
.../secrets_masker/secrets_masker.py | 11 ++------
2 files changed, 15 insertions(+), 29 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/services/public/variables.py
b/airflow-core/src/airflow/api_fastapi/core_api/services/public/variables.py
index 7b9e91a70fb..790aa1c1125 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/public/variables.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/public/variables.py
@@ -42,24 +42,6 @@ from airflow.api_fastapi.core_api.services.public.common
import BulkService
from airflow.models.variable import Variable
-def _restore_redacted_value(new_value: str, old_value: str | None, key: str)
-> str:
- """
- Put the stored secret back wherever ``new_value`` still holds the mask
``VariableResponse`` sent out.
-
- The UI edit form is filled from the redacted response, so saving it
unchanged would otherwise
- write ``***`` over every sensitive field.
- """
- if old_value is None:
- return new_value
- try:
- new_obj, old_obj = json.loads(new_value), json.loads(old_value)
- except json.JSONDecodeError:
- return merge(new_value, old_value, key)
- merged = merge(new_obj, old_obj, key)
- # Keep the caller's formatting when there was nothing to restore. indent=2
matches VariableBody.
- return new_value if merged == new_obj else json.dumps(merged, indent=2)
-
-
def update_orm_from_pydantic(
old_variable: Variable, patch_body: VariableBody, update_mask: list[str] |
None
) -> Variable:
@@ -85,14 +67,23 @@ def update_orm_from_pydantic(
raise RequestValidationError(errors=e.errors())
non_update_fields = {"key"}
+ # The UI edit form is filled from the redacted response, so saving it
unchanged would otherwise
+ # write ``***`` over every sensitive field.
if (
"value" in patch_body.model_fields_set
and (not update_mask or "value" in update_mask)
and isinstance(patch_body.value, str)
+ and (old_value := old_variable.val) is not None
):
- patch_body = patch_body.model_copy(
- update={"value": _restore_redacted_value(patch_body.value,
old_variable.val, old_variable.key)}
- )
+ try:
+ new_obj, old_obj = json.loads(patch_body.value),
json.loads(old_value)
+ except json.JSONDecodeError:
+ merged_value = merge(patch_body.value, old_value, old_variable.key)
+ else:
+ merged = merge(new_obj, old_obj, old_variable.key)
+ # Keep the caller's formatting when there was nothing to restore.
indent=2 matches VariableBody.
+ merged_value = patch_body.value if merged == new_obj else
json.dumps(merged, indent=2)
+ patch_body = patch_body.model_copy(update={"value": merged_value})
# Apply patch via utility
return cast(
diff --git
a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
index 497e8f1ebcf..d802ea6bcb6 100644
--- a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
+++ b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
@@ -446,8 +446,6 @@ class SecretsMasker(logging.Filter):
old_item: Redactable,
*,
name: str | None,
- depth: int,
- max_depth: int,
force_sensitive: bool = False,
replacement: str,
) -> Redacted:
@@ -477,8 +475,6 @@ class SecretsMasker(logging.Filter):
new_item[key],
old_item[key],
name=child_name,
- depth=depth + 1,
- max_depth=max_depth,
force_sensitive=is_sensitive,
replacement=replacement,
)
@@ -499,8 +495,6 @@ class SecretsMasker(logging.Filter):
new_item[i],
old_item[i],
name=None,
- depth=depth + 1,
- max_depth=max_depth,
force_sensitive=is_sensitive,
replacement=replacement,
)
@@ -559,13 +553,14 @@ class SecretsMasker(logging.Filter):
Takes a user-modified redacted item and merges it with the original
unredacted item.
For sensitive fields that still contain "***" (or whatever the
``replacement`` is specified as), the
original value is restored. For fields that have been updated, the new
value is preserved.
+
+ ``max_depth`` is ignored and kept for backward compatibility.
Containers are walked at any
+ depth; a sensitive subtree that ``redact`` collapsed at its
``max_depth`` is restored whole.
"""
return self._merge(
new_item,
old_item,
name=name,
- depth=0,
- max_depth=max_depth or self.MAX_RECURSION_DEPTH,
force_sensitive=False,
replacement=replacement,
)