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

potiuk 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 b5547c4babc Fix airflow plugins and dags pause printing prose with 
--output json (#73270)
b5547c4babc is described below

commit b5547c4babc8244a287d3439dfc2edc793a5863e
Author: Jyun-An Chen <[email protected]>
AuthorDate: Wed Sep 23 03:31:49 2026 +0800

    Fix airflow plugins and dags pause printing prose with --output json 
(#73270)
    
    When nothing matched, both commands printed a hard-coded message before 
reaching AirflowConsole().print_as, so --output json/yaml callers got a prose 
line that jq and YAML parsers cannot read. Other list-style commands render an 
empty result as [] for structured formats, as documented in usage-cli.rst, and 
teams list was aligned with that in #72945. The human-readable message is kept 
for table and plain output, where it is more specific than the generic "No data 
found".
---
 airflow-core/src/airflow/cli/commands/dag_command.py       |  5 ++++-
 airflow-core/src/airflow/cli/commands/plugins_command.py   |  5 ++++-
 airflow-core/tests/unit/cli/commands/test_dag_command.py   | 14 ++++++++++++++
 .../tests/unit/cli/commands/test_plugins_command.py        | 14 ++++++++++++--
 4 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/cli/commands/dag_command.py 
b/airflow-core/src/airflow/cli/commands/dag_command.py
index 7ff7e1e5e15..9ab41b426c3 100644
--- a/airflow-core/src/airflow/cli/commands/dag_command.py
+++ b/airflow-core/src/airflow/cli/commands/dag_command.py
@@ -274,7 +274,10 @@ def set_is_paused(is_paused: bool, args, dag: DAG | None = 
None, *, session: Ses
 
     matched_dags = list(session.scalars(query).all())
     if not matched_dags:
-        print(f"No {'un' if is_paused else ''}paused DAGs were found")
+        if args.output in ("table", "plain"):
+            print(f"No {'un' if is_paused else ''}paused DAGs were found")
+        else:
+            AirflowConsole().print_as(data=[], output=args.output)
         return
 
     if not args.yes and args.treat_dag_id_as_regex:
diff --git a/airflow-core/src/airflow/cli/commands/plugins_command.py 
b/airflow-core/src/airflow/cli/commands/plugins_command.py
index 595ec7aba10..19d065becb5 100644
--- a/airflow-core/src/airflow/cli/commands/plugins_command.py
+++ b/airflow-core/src/airflow/cli/commands/plugins_command.py
@@ -28,7 +28,10 @@ def dump_plugins(args):
     """Dump plugins information."""
     plugins_info: list[dict[str, str]] = get_plugin_info()
     if not plugins_info:
-        print("No plugins loaded")
+        if args.output in ("table", "plain"):
+            print("No plugins loaded")
+        else:
+            AirflowConsole().print_as(plugins_info, output=args.output)
         return
 
     # Remove empty info
diff --git a/airflow-core/tests/unit/cli/commands/test_dag_command.py 
b/airflow-core/tests/unit/cli/commands/test_dag_command.py
index 592ee1a50cc..2714ed9d387 100644
--- a/airflow-core/tests/unit/cli/commands/test_dag_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_dag_command.py
@@ -29,6 +29,7 @@ import msgspec
 import pendulum
 import pytest
 import time_machine
+import yaml
 from sqlalchemy import func, select
 
 from airflow import settings
@@ -670,6 +671,19 @@ class TestCliDags:
         out = temp_stdout.splitlines()[-1]
         assert out == "No unpaused DAGs were found"
 
+    @pytest.mark.parametrize(("output", "loader"), [("json", json.loads), 
("yaml", yaml.safe_load)])
+    @pytest.mark.parametrize(
+        ("command", "subcommand"),
+        [(dag_command.dag_pause, "pause"), (dag_command.dag_unpause, 
"unpause")],
+    )
+    def test_pause_unpause_non_existing_dag_structured_output(
+        self, command, subcommand, output, loader, stdout_capture
+    ):
+        args = self.parser.parse_args(["dags", subcommand, "non_existing_dag", 
f"--output={output}"])
+        with stdout_capture as temp_stdout:
+            command(args)
+        assert loader(temp_stdout.getvalue()) == []
+
     def test_trigger_dag(self):
         dag_command.dag_trigger(
             self.parser.parse_args(
diff --git a/airflow-core/tests/unit/cli/commands/test_plugins_command.py 
b/airflow-core/tests/unit/cli/commands/test_plugins_command.py
index bb9d53e8247..00027fc26af 100644
--- a/airflow-core/tests/unit/cli/commands/test_plugins_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_plugins_command.py
@@ -23,6 +23,7 @@ import textwrap
 from contextlib import redirect_stdout
 
 import pytest
+import yaml
 
 from airflow.cli import cli_parser
 from airflow.cli.commands import plugins_command
@@ -61,13 +62,22 @@ class TestPluginsCommand:
     def setup_class(cls):
         cls.parser = cli_parser.get_parser()
 
+    @pytest.mark.parametrize("output", ["table", "plain"])
     @mock_plugin_manager(plugins=[])
-    def test_should_display_no_plugins(self):
+    def test_should_display_no_plugins(self, output):
         with redirect_stdout(io.StringIO()) as temp_stdout:
-            plugins_command.dump_plugins(self.parser.parse_args(["plugins", 
"--output=json"]))
+            plugins_command.dump_plugins(self.parser.parse_args(["plugins", 
f"--output={output}"]))
             stdout = temp_stdout.getvalue()
         assert "No plugins loaded" in stdout
 
+    @pytest.mark.parametrize(("output", "loader"), [("json", json.loads), 
("yaml", yaml.safe_load)])
+    @mock_plugin_manager(plugins=[])
+    def test_should_display_no_plugins_as_empty_list(self, output, loader):
+        with redirect_stdout(io.StringIO()) as temp_stdout:
+            plugins_command.dump_plugins(self.parser.parse_args(["plugins", 
f"--output={output}"]))
+            stdout = temp_stdout.getvalue()
+        assert loader(stdout) == []
+
     @pytest.mark.skipif(not flask_appbuilder_installed, reason="Flask 
AppBuilder is not installed")
     @mock_plugin_manager(plugins=[ComplexAirflowPlugin])
     def test_should_display_one_plugin(self):

Reply via email to