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):