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

bugraoz93 pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this 
push:
     new a8c64b87516 [airflow-ctl/v0-1-test] Fix CLI variables import with 
structured falsy values (#67060) (#68247)
a8c64b87516 is described below

commit a8c64b87516ec5117c141a0eb6a72fbcf605f819
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Jun 10 18:59:10 2026 +0200

    [airflow-ctl/v0-1-test] Fix CLI variables import with structured falsy 
values (#67060) (#68247)
    
    * Fix variables import with structured falsy values
    
    * Add newsfragment for variables import fix
    
    * Fix airflowctl variable import error path wrapping
    
    * Remove unnecessary variables import newsfragment
    (cherry picked from commit 837bfdee0312c59016910166bab94a6deb28e992)
    
    Co-authored-by: leon.jeon <[email protected]>
---
 .../src/airflow/cli/commands/variable_command.py   |  2 +-
 .../unit/cli/commands/test_variable_command.py     | 31 ++++++++++++++++++
 .../airflowctl/ctl/commands/variable_command.py    | 13 ++++++--
 .../ctl/commands/test_variable_command.py          | 38 +++++++++++++++++-----
 4 files changed, 73 insertions(+), 11 deletions(-)

diff --git a/airflow-core/src/airflow/cli/commands/variable_command.py 
b/airflow-core/src/airflow/cli/commands/variable_command.py
index 85166cb6e79..ba1cf4c6ee4 100644
--- a/airflow-core/src/airflow/cli/commands/variable_command.py
+++ b/airflow-core/src/airflow/cli/commands/variable_command.py
@@ -151,7 +151,7 @@ def variables_import(args, session):
         try:
             value = v
             description = None
-            if isinstance(v, dict) and v.get("value"):  # verify that var 
configuration has value
+            if isinstance(v, dict) and "value" in v:  # verify that var 
configuration has value
                 value, description = v["value"], v.get("description")
             Variable.set(k, value, description, serialize_json=not 
isinstance(value, str))
         except Exception as e:
diff --git a/airflow-core/tests/unit/cli/commands/test_variable_command.py 
b/airflow-core/tests/unit/cli/commands/test_variable_command.py
index 21d2fb66822..c7890d9c1a9 100644
--- a/airflow-core/tests/unit/cli/commands/test_variable_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_variable_command.py
@@ -474,6 +474,37 @@ class TestCliVariables:
             )
             assert 
session.scalar(select(Variable.description).where(Variable.key == "var3")) is 
None
 
+    @pytest.mark.parametrize(
+        ("value", "deserialize_json"),
+        [
+            ("", False),
+            (0, True),
+            (False, True),
+            (None, True),
+        ],
+        ids=["empty_string", "zero", "false", "null"],
+    )
+    def test_variables_import_with_structured_falsy_values(
+        self, create_variable_file, value, deserialize_json
+    ):
+        """Test variables_import preserves structured falsy values and 
descriptions."""
+        file = create_variable_file(
+            {"falsy_key": {"value": value, "description": "Falsy value 
description"}},
+            format="json",
+        )
+
+        with create_session() as session:
+            variable_command.variables_import(
+                self.parser.parse_args(["variables", "import", 
os.fspath(file)]), session=session
+            )
+
+        assert Variable.get("falsy_key", deserialize_json=deserialize_json) == 
value
+        with create_session() as session:
+            assert (
+                session.scalar(select(Variable.description).where(Variable.key 
== "falsy_key"))
+                == "Falsy value description"
+            )
+
     def test_variables_import_env(self, create_variable_file, 
env_variable_data):
         """Test variables_import with ENV format"""
         env_file = create_variable_file(env_variable_data, format="env")
diff --git a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py 
b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
index 88bf33a0f01..19321002e44 100644
--- a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
+++ b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
@@ -21,6 +21,7 @@ import os
 import sys
 
 import rich
+from rich.console import Console
 
 from airflowctl.api.client import NEW_API_CLIENT, ClientKind, 
provide_api_client
 from airflowctl.api.datamodels.generated import (
@@ -31,6 +32,10 @@ from airflowctl.api.datamodels.generated import (
 )
 
 
+def _print_file_error(message: str, file_path: str) -> None:
+    Console().print(f"[red]{message}: {file_path}", soft_wrap=True)
+
+
 @provide_api_client(kind=ClientKind.CLI)
 def import_(args, api_client=NEW_API_CLIENT) -> list[str]:
     """Import variables from a given file."""
@@ -38,15 +43,19 @@ def import_(args, api_client=NEW_API_CLIENT) -> list[str]:
     errors_message = "[red]Import failed! errors: {errors}[/red]"
 
     if not os.path.exists(args.file):
-        rich.print(f"[red]Missing variable file: {args.file}")
+        _print_file_error("Missing variable file", args.file)
         sys.exit(1)
     with open(args.file) as var_file:
         try:
             var_json = json.load(var_file)
         except json.JSONDecodeError:
-            rich.print(f"[red]Invalid variable file: {args.file}")
+            _print_file_error("Invalid variable file", args.file)
             sys.exit(1)
 
+    if not isinstance(var_json, dict):
+        _print_file_error("Invalid variable file", args.file)
+        sys.exit(1)
+
     action_on_existence = BulkActionOnExistence(args.action_on_existing_key)
     vars_to_update = []
     for k, v in var_json.items():
diff --git 
a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py 
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
index a0598d03459..f573585935f 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import json
+from types import SimpleNamespace
 
 import pytest
 
@@ -89,17 +90,20 @@ class TestCliVariableCommands:
             "",
             0,
             False,
+            None,
         ],
-        ids=["empty_string", "zero", "false"],
+        ids=["empty_string", "zero", "false", "null"],
     )
-    def test_import_falsy_values(self, api_client_maker, tmp_path, 
monkeypatch, falsy_value):
+    def test_import_falsy_values(self, tmp_path, monkeypatch, falsy_value):
         """Test that falsy values (empty string, 0, False) are correctly 
imported."""
-        api_client = api_client_maker(
-            path="/api/v2/variables",
-            response_json=self.bulk_response_success.model_dump(),
-            expected_http_status_code=200,
-            kind=ClientKind.CLI,
-        )
+        captured_variables = None
+
+        def bulk(variables):
+            nonlocal captured_variables
+            captured_variables = variables
+            return self.bulk_response_success
+
+        api_client = SimpleNamespace(variables=SimpleNamespace(bulk=bulk))
 
         monkeypatch.chdir(tmp_path)
         expected_json_path = tmp_path / self.export_file_name
@@ -113,6 +117,24 @@ class TestCliVariableCommands:
             api_client=api_client,
         )
         assert response == [self.key]
+        entity = captured_variables.actions[0].entities[0]
+        assert entity.value.root == falsy_value
+        assert entity.description == "test falsy value"
+
+    def test_import_rejects_non_object_json(self, tmp_path, monkeypatch, 
capsys):
+        monkeypatch.chdir(tmp_path)
+        expected_json_path = tmp_path / self.export_file_name
+        expected_json_path.write_text(json.dumps([self.key]))
+
+        with pytest.raises(SystemExit) as exit_info:
+            variable_command.import_(
+                self.parser.parse_args(["variables", "import", 
expected_json_path.as_posix()]),
+            )
+
+        assert exit_info.value.code == 1
+        output = capsys.readouterr().out
+        assert "Invalid variable file:" in output
+        assert expected_json_path.as_posix() in output
 
     def test_import_error(self, api_client_maker, tmp_path, monkeypatch):
         api_client = api_client_maker(

Reply via email to