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 f081ffb0f38 CLI: Add --limit flag to airflow dags list-runs (#71729)
f081ffb0f38 is described below
commit f081ffb0f38b01951ad6f561c7404bf0263d97db
Author: Deepak kumar <[email protected]>
AuthorDate: Tue Sep 22 16:34:14 2026 -0700
CLI: Add --limit flag to airflow dags list-runs (#71729)
airflow dags list-jobs already accepts --limit but the neighbouring
list-runs command does not, so operators on busy DAGs have to pipe
through head or use date filters to shorten output. Mirroring the
existing flag closes that asymmetry and makes the two commands
predictable together.
---
airflow-core/newsfragments/71729.improvement.rst | 1 +
airflow-core/src/airflow/cli/cli_config.py | 9 ++++-
.../src/airflow/cli/commands/dag_command.py | 4 +++
.../tests/unit/cli/commands/test_dag_command.py | 38 ++++++++++++++++++++++
4 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/airflow-core/newsfragments/71729.improvement.rst
b/airflow-core/newsfragments/71729.improvement.rst
new file mode 100644
index 00000000000..d85f81ee6fe
--- /dev/null
+++ b/airflow-core/newsfragments/71729.improvement.rst
@@ -0,0 +1 @@
+Add ``--limit`` option to ``airflow dags list-runs`` to cap output to the N
most recent Dag runs after filters and sorting are applied, mirroring ``airflow
dags list-jobs --limit``.
diff --git a/airflow-core/src/airflow/cli/cli_config.py
b/airflow-core/src/airflow/cli/cli_config.py
index e6f1a01975f..0274fc4402e 100644
--- a/airflow-core/src/airflow/cli/cli_config.py
+++ b/airflow-core/src/airflow/cli/cli_config.py
@@ -291,6 +291,11 @@ ARG_DR_STATE = Arg(
metavar=", ".join(dagrun_states),
choices=dagrun_states,
)
+ARG_DR_LIMIT = Arg(
+ ("--limit",),
+ type=positive_int(allow_zero=False),
+ help="Return a limited number of Dag runs, ordered by most recent
run_after first",
+)
# list_jobs
ARG_DAG_ID_OPT = Arg(("-d", "--dag-id"), help="The id of the dag")
@@ -1253,13 +1258,15 @@ DAGS_COMMANDS = (
"dagruns with the given state. If no_backfill option is given, it
will filter out all "
"backfill dagruns for given dag id. If start_date is given, it
will filter out all the "
"dagruns that were executed before this date. If end_date is
given, it will filter out "
- "all the dagruns that were executed after this date. "
+ "all the dagruns that were executed after this date. If limit is
given, it will return "
+ "only the most recent N runs after filters and sorting are
applied."
),
func=lazy_load_command("airflow.cli.commands.dag_command.dag_list_dag_runs"),
args=(
ARG_DAG_ID,
ARG_NO_BACKFILL,
ARG_DR_STATE,
+ ARG_DR_LIMIT,
ARG_OUTPUT,
ARG_VERBOSE,
ARG_START_DATE,
diff --git a/airflow-core/src/airflow/cli/commands/dag_command.py
b/airflow-core/src/airflow/cli/commands/dag_command.py
index 9ab41b426c3..bd42dadf925 100644
--- a/airflow-core/src/airflow/cli/commands/dag_command.py
+++ b/airflow-core/src/airflow/cli/commands/dag_command.py
@@ -799,6 +799,10 @@ def dag_list_dag_runs(args, dag: DAG | None = None, *,
session: Session = NEW_SE
session=session,
)
dag_runs.sort(key=operator.attrgetter("run_after"), reverse=True)
+ # Slice after sorting so `--limit` reliably returns the most recent runs,
+ # independent of insertion order in DagRun.find().
+ if getattr(args, "limit", None):
+ dag_runs = dag_runs[: args.limit]
def _render_dagrun(dr: DagRun) -> dict[str, str]:
return {
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 2714ed9d387..fcf819910e6 100644
--- a/airflow-core/tests/unit/cli/commands/test_dag_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_dag_command.py
@@ -541,6 +541,44 @@ class TestCliDags:
)
dag_command.dag_list_dag_runs(args)
+ @pytest.mark.parametrize(
+ ("limit", "expected_count"),
+ [
+ (None, 3),
+ (1, 1),
+ (2, 2),
+ (5, 3),
+ ],
+ )
+ @mock.patch("airflow.cli.commands.dag_command.AirflowConsole")
+ def test_cli_list_dag_runs_limit(self, mock_console, limit,
expected_count):
+ """`--limit N` caps output to the N most recent runs; without it, all
runs are returned."""
+ for i in range(3):
+ dag_command.dag_trigger(
+ self.parser.parse_args(
+ ["dags", "trigger", "example_bash_operator", "--run-id",
f"cli_limit_test_{i}"]
+ )
+ )
+
+ argv = ["dags", "list-runs", "example_bash_operator"]
+ if limit is not None:
+ argv += ["--limit", str(limit)]
+ args = self.parser.parse_args(argv)
+ dag_command.dag_list_dag_runs(args)
+
+ printed = mock_console.return_value.print_as.call_args.kwargs["data"]
+ assert len(printed) == expected_count
+ # Slicing happens after the run_after DESC sort, so the returned rows
must
+ # remain monotonically non-increasing on run_after.
+ run_afters = [dr.run_after for dr in printed]
+ assert run_afters == sorted(run_afters, reverse=True)
+
+ @pytest.mark.parametrize("bad_value", ["0", "-1", "abc"])
+ def test_cli_list_dag_runs_limit_rejects_invalid(self, bad_value):
+ """`--limit` must be a positive int; argparse rejects invalid inputs
before the command runs."""
+ with pytest.raises(SystemExit):
+ self.parser.parse_args(["dags", "list-runs",
"example_bash_operator", "--limit", bad_value])
+
def test_cli_list_jobs_with_args(self):
args = self.parser.parse_args(
[